cff 0.2.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGES.md +252 -0
- data/CITATION.cff +83 -0
- data/Gemfile +3 -1
- data/LICENCE +1 -1
- data/README.md +177 -22
- data/Rakefile +17 -12
- data/bin/console +4 -3
- data/cff.gemspec +41 -22
- data/lib/cff.rb +25 -11
- data/lib/cff/entity.rb +35 -25
- data/lib/cff/errors.rb +53 -0
- data/lib/cff/file.rb +209 -18
- data/lib/cff/formatter/apa_formatter.rb +77 -0
- data/lib/cff/formatter/bibtex_formatter.rb +122 -0
- data/lib/cff/formatter/formatter.rb +63 -0
- data/lib/cff/identifier.rb +72 -0
- data/lib/cff/licensable.rb +45 -0
- data/lib/cff/model.rb +223 -89
- data/lib/cff/{model-part.rb → model_part.rb} +17 -9
- data/lib/cff/person.rb +45 -24
- data/lib/cff/reference.rb +564 -0
- data/lib/cff/util.rb +48 -17
- data/lib/cff/validatable.rb +54 -0
- data/lib/cff/version.rb +7 -4
- data/lib/schema/1.2.0.json +1882 -0
- metadata +124 -29
- data/.gitignore +0 -30
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/.travis.yml +0 -20
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2018-2021 The Ruby Citation File Format Developers.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
##
|
18
|
+
module CFF
|
19
|
+
# Generates an APALIKE citation string
|
20
|
+
class ApaFormatter < Formatter # :nodoc:
|
21
|
+
|
22
|
+
def self.format(model:, preferred_citation: true) # rubocop:disable Metrics/AbcSize
|
23
|
+
model = select_and_check_model(model, preferred_citation)
|
24
|
+
return if model.nil?
|
25
|
+
|
26
|
+
output = []
|
27
|
+
output << combine_authors(
|
28
|
+
model.authors.map { |author| format_author(author) }
|
29
|
+
)
|
30
|
+
|
31
|
+
_, year = month_and_year_from_model(model)
|
32
|
+
output << "(#{year})" unless year.empty?
|
33
|
+
|
34
|
+
version = " (Version #{model.version})" unless model.version.to_s.empty?
|
35
|
+
output << "#{model.title}#{version}#{software_label(model)}"
|
36
|
+
output << publication_data_from_model(model)
|
37
|
+
output << url(model)
|
38
|
+
|
39
|
+
output.reject(&:empty?).join('. ')
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.publication_data_from_model(model)
|
43
|
+
return '' unless model.respond_to?(:journal) && !model.journal.empty?
|
44
|
+
|
45
|
+
vol = model.volume.to_s.empty? ? '' : "#{model.volume}(#{model.issue})"
|
46
|
+
|
47
|
+
[model.journal, vol, model.start.to_s].reject(&:empty?).join(', ')
|
48
|
+
end
|
49
|
+
|
50
|
+
# Prefer a DOI over the other URI options.
|
51
|
+
def self.url(model)
|
52
|
+
model.doi.empty? ? super : "https://doi.org/#{model.doi}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.software_label(model)
|
56
|
+
return '' if model.is_a?(Reference) && !model.type.include?('software')
|
57
|
+
|
58
|
+
' [Computer software]'
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.combine_authors(authors)
|
62
|
+
return authors[0].chomp('.') if authors.length == 1
|
63
|
+
|
64
|
+
"#{authors[0..-2].join(', ')}, & #{authors[-1]}".chomp('.')
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.format_author(author)
|
68
|
+
return author.name if author.is_a?(Entity)
|
69
|
+
|
70
|
+
particle =
|
71
|
+
author.name_particle.empty? ? '' : "#{author.name_particle} "
|
72
|
+
suffix = author.name_suffix.empty? ? '.' : "., #{author.name_suffix}"
|
73
|
+
|
74
|
+
"#{particle}#{author.family_names}, #{initials(author.given_names)}#{suffix}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2018-2021 The Ruby Citation File Format Developers.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
##
|
18
|
+
module CFF
|
19
|
+
# Generates an BibTex citation string
|
20
|
+
class BibtexFormatter < Formatter # :nodoc:
|
21
|
+
|
22
|
+
def self.format(model:, preferred_citation: true) # rubocop:disable Metrics/AbcSize
|
23
|
+
model = select_and_check_model(model, preferred_citation)
|
24
|
+
return if model.nil?
|
25
|
+
|
26
|
+
values = {}
|
27
|
+
values['author'] = combine_authors(
|
28
|
+
model.authors.map { |author| format_author(author) }
|
29
|
+
)
|
30
|
+
values['title'] = "{#{model.title}}"
|
31
|
+
|
32
|
+
publication_data_from_model(model, values)
|
33
|
+
|
34
|
+
month, year = month_and_year_from_model(model)
|
35
|
+
values['month'] = month
|
36
|
+
values['year'] = year
|
37
|
+
|
38
|
+
values['url'] = url(model)
|
39
|
+
|
40
|
+
values.reject! { |_, v| v.empty? }
|
41
|
+
sorted_values = values.sort.map do |key, value|
|
42
|
+
"#{key} = {#{value}}"
|
43
|
+
end
|
44
|
+
sorted_values.insert(0, generate_reference(values))
|
45
|
+
|
46
|
+
"@#{bibtex_type(model)}{#{sorted_values.join(",\n")}\n}"
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get various bits of information about the reference publication.
|
50
|
+
# Reference: https://www.bibtex.com/format/
|
51
|
+
def self.publication_data_from_model(model, fields)
|
52
|
+
%w[doi journal volume].each do |field|
|
53
|
+
fields[field] = model.send(field).to_s if model.respond_to?(field)
|
54
|
+
end
|
55
|
+
|
56
|
+
# BibTeX 'number' is CFF 'issue'.
|
57
|
+
fields['number'] = model.issue.to_s if model.respond_to?(:issue)
|
58
|
+
|
59
|
+
fields['pages'] = pages_from_model(model)
|
60
|
+
end
|
61
|
+
|
62
|
+
# CFF 'pages' is the number of pages, which has no equivalent in BibTeX.
|
63
|
+
# Reference: https://www.bibtex.com/f/pages-field/
|
64
|
+
def self.pages_from_model(model)
|
65
|
+
return '' if !model.respond_to?(:start) || model.start.to_s.empty?
|
66
|
+
|
67
|
+
start = model.start.to_s
|
68
|
+
finish = model.end.to_s
|
69
|
+
if finish.empty?
|
70
|
+
start
|
71
|
+
else
|
72
|
+
start == finish ? start : "#{start}--#{finish}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Do what we can to map between CFF reference types and bibtex types.
|
77
|
+
# Reference: https://www.bibtex.com/e/entry-types/
|
78
|
+
def self.bibtex_type(model)
|
79
|
+
return 'misc' unless model.is_a?(Reference)
|
80
|
+
|
81
|
+
case model.type
|
82
|
+
when 'article', 'book', 'manual', 'unpublished'
|
83
|
+
model.type
|
84
|
+
when 'conference', 'proceedings'
|
85
|
+
'proceedings'
|
86
|
+
when 'conference-paper'
|
87
|
+
'inproceedings'
|
88
|
+
when 'magazine-article', 'newspaper-article'
|
89
|
+
'article'
|
90
|
+
when 'pamphlet'
|
91
|
+
'booklet'
|
92
|
+
else
|
93
|
+
'misc'
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.format_author(author)
|
98
|
+
return "{#{author.name}}" if author.is_a?(Entity)
|
99
|
+
|
100
|
+
particle =
|
101
|
+
author.name_particle.empty? ? '' : "#{author.name_particle} "
|
102
|
+
|
103
|
+
[
|
104
|
+
"#{particle}#{author.family_names}",
|
105
|
+
author.name_suffix,
|
106
|
+
author.given_names
|
107
|
+
].reject(&:empty?).join(', ')
|
108
|
+
end
|
109
|
+
|
110
|
+
def self.combine_authors(authors)
|
111
|
+
authors.join(' and ')
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.generate_reference(fields)
|
115
|
+
[
|
116
|
+
fields['author'].split(',', 2)[0].tr(' -', '_'),
|
117
|
+
fields['title'].split[0..2],
|
118
|
+
fields['year']
|
119
|
+
].compact.join('_').tr('-$£%&(){}+!?/\\:;\'"~#', '')
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2018-2021 The Ruby Citation File Format Developers.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
##
|
18
|
+
module CFF
|
19
|
+
# Formatter base class
|
20
|
+
class Formatter # :nodoc:
|
21
|
+
|
22
|
+
def self.select_and_check_model(model, preferred_citation)
|
23
|
+
if preferred_citation && model.preferred_citation.is_a?(Reference)
|
24
|
+
model = model.preferred_citation
|
25
|
+
end
|
26
|
+
|
27
|
+
# Safe to assume valid `Model`s and `Reference`s will have these fields.
|
28
|
+
model.authors.empty? || model.title.empty? ? nil : model
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.initials(name)
|
32
|
+
name.split.map { |part| part[0].capitalize }.join('. ')
|
33
|
+
end
|
34
|
+
|
35
|
+
# Prefer `repository_code` over `url`
|
36
|
+
def self.url(model)
|
37
|
+
model.repository_code.empty? ? model.url : model.repository_code
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.month_and_year_from_model(model)
|
41
|
+
if model.respond_to?(:year)
|
42
|
+
result = [model.month, model.year].map(&:to_s)
|
43
|
+
|
44
|
+
return result unless result.any?(&:empty?)
|
45
|
+
end
|
46
|
+
|
47
|
+
month_and_year_from_date(model.date_released)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.month_and_year_from_date(value)
|
51
|
+
if value.is_a?(Date)
|
52
|
+
[value.month, value.year].map(&:to_s)
|
53
|
+
else
|
54
|
+
begin
|
55
|
+
date = Date.parse(value.to_s)
|
56
|
+
[date.month, date.year].map(&:to_s)
|
57
|
+
rescue ArgumentError
|
58
|
+
['', '']
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2018-2021 The Ruby Citation File Format Developers.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
##
|
18
|
+
module CFF
|
19
|
+
|
20
|
+
# An Identifier represents an identifier in a CITATION.cff file.
|
21
|
+
#
|
22
|
+
# Identifier implements all of the fields listed in the
|
23
|
+
# [CFF standard](https://citation-file-format.github.io/). All fields
|
24
|
+
# are simple strings and can be set as such. A field which has not been set
|
25
|
+
# will return the empty string. The simple fields are (with defaults in
|
26
|
+
# parentheses):
|
27
|
+
#
|
28
|
+
# * `description`
|
29
|
+
# * `type`
|
30
|
+
# * `value`
|
31
|
+
class Identifier < ModelPart
|
32
|
+
|
33
|
+
ALLOWED_FIELDS = ['description', 'type', 'value'].freeze # :nodoc:
|
34
|
+
|
35
|
+
# The [defined set of identifier types](https://github.com/citation-file-format/citation-file-format/blob/main/README.md#identifier-type-strings).
|
36
|
+
IDENTIFIER_TYPES = ['doi', 'url', 'swh', 'other'].freeze
|
37
|
+
|
38
|
+
# :call-seq:
|
39
|
+
# new -> Identifier
|
40
|
+
# new { |id| block } -> Identifier
|
41
|
+
# new(type, value) -> Identifier
|
42
|
+
# new(type, value) { |id| block } -> Identifier
|
43
|
+
#
|
44
|
+
# Create a new Identifier with the optionally supplied type and value.
|
45
|
+
# If the supplied type is invalid, then neither the type or value are set.
|
46
|
+
def initialize(param = nil, *more)
|
47
|
+
if param.is_a?(Hash)
|
48
|
+
@fields = param
|
49
|
+
@fields.default = ''
|
50
|
+
else
|
51
|
+
@fields = Hash.new('')
|
52
|
+
|
53
|
+
unless param.nil?
|
54
|
+
self.type = param
|
55
|
+
@fields['value'] = more[0] unless @fields['type'].empty?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
yield self if block_given?
|
60
|
+
end
|
61
|
+
|
62
|
+
# :call-seq:
|
63
|
+
# type = type
|
64
|
+
#
|
65
|
+
# Sets the type of this Identifier. The type is restricted to a
|
66
|
+
# [defined set of identifier types](https://github.com/citation-file-format/citation-file-format/blob/main/README.md#identifier-type-strings).
|
67
|
+
def type=(type)
|
68
|
+
type = type.downcase
|
69
|
+
@fields['type'] = type if IDENTIFIER_TYPES.include?(type)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2018-2021 The Ruby Citation File Format Developers.
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
##
|
18
|
+
module CFF
|
19
|
+
|
20
|
+
# Functionality to add licence(s) to parts of the CFF model.
|
21
|
+
module Licensable
|
22
|
+
|
23
|
+
LICENSES = SCHEMA_FILE['definitions']['license-enum']['enum'].dup.freeze # :nodoc:
|
24
|
+
|
25
|
+
# :call-seq:
|
26
|
+
# license = license
|
27
|
+
# license = Array
|
28
|
+
#
|
29
|
+
# Set the license, or licenses, of this work. Only licenses that conform
|
30
|
+
# to the [SPDX License List](https://spdx.org/licenses/) will be accepted.
|
31
|
+
# If you need specify a different license you should set `license-url`
|
32
|
+
# with a link to the license instead.
|
33
|
+
def license=(lic)
|
34
|
+
list = [*lic].select { |l| LICENSES.include?(l) }
|
35
|
+
@fields['license'] = case list.length
|
36
|
+
when 0
|
37
|
+
@fields['license']
|
38
|
+
when 1
|
39
|
+
list[0]
|
40
|
+
else
|
41
|
+
list
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/cff/model.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2018-2021 The Ruby Citation File Format Developers.
|
2
4
|
#
|
3
5
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
6
|
# you may not use this file except in compliance with the License.
|
@@ -12,54 +14,176 @@
|
|
12
14
|
# See the License for the specific language governing permissions and
|
13
15
|
# limitations under the License.
|
14
16
|
|
15
|
-
|
17
|
+
##
|
16
18
|
module CFF
|
17
19
|
|
18
20
|
# Model is the core data structure for a CITATION.cff file. It can be
|
19
21
|
# accessed direcly, or via File.
|
20
|
-
|
22
|
+
#
|
23
|
+
# Model implements all of the fields listed in the
|
24
|
+
# [CFF standard](https://citation-file-format.github.io/). Complex
|
25
|
+
# fields - `authors`, `contact`, `identifiers`, `keywords`,
|
26
|
+
# `preferred-citation` and `references` - are documented below. All other
|
27
|
+
# fields are simple strings and can be set as such. A field which has not
|
28
|
+
# been set will return the empty string. The simple fields are (with defaults
|
29
|
+
# in parentheses):
|
30
|
+
#
|
31
|
+
# * `abstract`
|
32
|
+
# * `cff_version`
|
33
|
+
# * `commit`
|
34
|
+
# * `date_released` - *Note:* returns a `Date` object
|
35
|
+
# * `doi`
|
36
|
+
# * `license`
|
37
|
+
# * `license_url`
|
38
|
+
# * `message` (If you use this software in your work, please cite it using
|
39
|
+
# the following metadata)
|
40
|
+
# * `repository`
|
41
|
+
# * `repository_artifact`
|
42
|
+
# * `repository_code`
|
43
|
+
# * `title`
|
44
|
+
# * `url`
|
45
|
+
# * `version`
|
46
|
+
class Model < ModelPart
|
21
47
|
|
22
|
-
include
|
48
|
+
include Licensable
|
49
|
+
include Validatable
|
23
50
|
|
24
51
|
ALLOWED_FIELDS = [
|
25
|
-
'abstract',
|
26
|
-
'
|
27
|
-
'
|
28
|
-
'
|
29
|
-
'
|
30
|
-
|
31
|
-
'license-url',
|
32
|
-
'message',
|
33
|
-
'repository',
|
34
|
-
'repository-artifact',
|
35
|
-
'repository-code',
|
36
|
-
'title',
|
37
|
-
'url',
|
38
|
-
'version'
|
39
|
-
].freeze # :nodoc:
|
52
|
+
'abstract', 'authors', 'cff-version', 'contact', 'commit',
|
53
|
+
'date-released', 'doi', 'identifiers', 'keywords', 'license',
|
54
|
+
'license-url', 'message', 'preferred-citation', 'references',
|
55
|
+
'repository', 'repository-artifact', 'repository-code', 'title',
|
56
|
+
'url', 'version'
|
57
|
+
].freeze # :nodoc:
|
40
58
|
|
41
59
|
# The default message to use if none is explicitly set.
|
42
|
-
DEFAULT_MESSAGE =
|
60
|
+
DEFAULT_MESSAGE = 'If you use this software in your work, please cite ' \
|
61
|
+
'it using the following metadata'
|
43
62
|
|
44
63
|
# :call-seq:
|
45
64
|
# new(title) -> Model
|
65
|
+
# new(title) { |model| block } -> Model
|
46
66
|
#
|
47
67
|
# Initialize a new Model with the supplied title.
|
48
68
|
def initialize(param)
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
if Hash === param
|
54
|
-
build_model(param)
|
69
|
+
if param.is_a?(Hash)
|
70
|
+
@fields = build_model(param)
|
71
|
+
@fields.default = ''
|
55
72
|
else
|
56
73
|
@fields = Hash.new('')
|
57
74
|
@fields['cff-version'] = DEFAULT_SPEC_VERSION
|
58
75
|
@fields['message'] = DEFAULT_MESSAGE
|
59
76
|
@fields['title'] = param
|
60
77
|
end
|
78
|
+
|
79
|
+
%w[authors contact identifiers keywords references].each do |field|
|
80
|
+
@fields[field] = [] if @fields[field].empty?
|
81
|
+
end
|
82
|
+
|
83
|
+
yield self if block_given?
|
84
|
+
end
|
85
|
+
|
86
|
+
# :call-seq:
|
87
|
+
# read(String) -> Model
|
88
|
+
#
|
89
|
+
# Read a CFF Model from a String and parse it for subsequent manipulation.
|
90
|
+
def self.read(model)
|
91
|
+
new(YAML.safe_load(model, permitted_classes: [Date, Time]))
|
92
|
+
end
|
93
|
+
|
94
|
+
# :call-seq:
|
95
|
+
# open(String) -> Model
|
96
|
+
# open(String) { |cff| block } -> Model
|
97
|
+
#
|
98
|
+
# With no associated block, Model.open is a synonym for ::read. If the
|
99
|
+
# optional code block is given, it will be passed the parsed model as an
|
100
|
+
# argument and the Model will be returned when the block terminates.
|
101
|
+
def self.open(model)
|
102
|
+
cff = Model.read(model)
|
103
|
+
|
104
|
+
yield cff if block_given?
|
105
|
+
|
106
|
+
cff
|
107
|
+
end
|
108
|
+
|
109
|
+
# :call-seq:
|
110
|
+
# date_released = date
|
111
|
+
#
|
112
|
+
# Set the `date-released` field. If a non-Date object is passed in it will
|
113
|
+
# be parsed into a Date.
|
114
|
+
def date_released=(date)
|
115
|
+
date = Date.parse(date) unless date.is_a?(Date)
|
116
|
+
|
117
|
+
@fields['date-released'] = date
|
118
|
+
end
|
119
|
+
|
120
|
+
def to_yaml # :nodoc:
|
121
|
+
YAML.dump fields, line_width: -1, indentation: 2
|
122
|
+
end
|
123
|
+
|
124
|
+
# :call-seq:
|
125
|
+
# to_apalike(preferred_citation: true) -> String
|
126
|
+
#
|
127
|
+
# Output this Model in an APA-like format. Setting
|
128
|
+
# `preferred_citation: true` will honour the `preferred_citation` field in
|
129
|
+
# the model if one is present (default).
|
130
|
+
#
|
131
|
+
# *Note:* This method assumes that this Model is valid when called.
|
132
|
+
def to_apalike(preferred_citation: true)
|
133
|
+
CFF::ApaFormatter.format(
|
134
|
+
model: self, preferred_citation: preferred_citation
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
138
|
+
# :call-seq:
|
139
|
+
# to_bibtex(preferred_citation: true) -> String
|
140
|
+
#
|
141
|
+
# Output this Model in BibTeX format. Setting
|
142
|
+
# `preferred_citation: true` will honour the `preferred_citation` field in
|
143
|
+
# the model if one is present (default).
|
144
|
+
#
|
145
|
+
# *Note:* This method assumes that this Model is valid when called.
|
146
|
+
def to_bibtex(preferred_citation: true)
|
147
|
+
CFF::BibtexFormatter.format(
|
148
|
+
model: self, preferred_citation: preferred_citation
|
149
|
+
)
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
|
154
|
+
def fields
|
155
|
+
%w[authors contact identifiers references].each do |field|
|
156
|
+
normalize_modelpart_array!(@fields[field])
|
157
|
+
end
|
158
|
+
|
159
|
+
fields_to_hash(@fields)
|
61
160
|
end
|
62
161
|
|
162
|
+
def build_model(fields) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
163
|
+
build_actor_collection!(fields['authors'] || [])
|
164
|
+
build_actor_collection!(fields['contact'] || [])
|
165
|
+
(fields['identifiers'] || []).map! do |i|
|
166
|
+
Identifier.new(i)
|
167
|
+
end
|
168
|
+
(fields['references'] || []).map! do |r|
|
169
|
+
Reference.new(r)
|
170
|
+
end
|
171
|
+
fields['preferred-citation'] &&=
|
172
|
+
Reference.new(fields['preferred-citation'])
|
173
|
+
|
174
|
+
# Only attempt an update of the `cff-version` field if it is present.
|
175
|
+
fields['cff-version'] &&= update_cff_version(fields['cff-version'])
|
176
|
+
|
177
|
+
fields
|
178
|
+
end
|
179
|
+
|
180
|
+
public
|
181
|
+
|
182
|
+
# Some documentation of "hidden" methods is provided here, out of the
|
183
|
+
# way of the main class code.
|
184
|
+
|
185
|
+
##
|
186
|
+
# :method: authors
|
63
187
|
# :call-seq:
|
64
188
|
# authors -> Array
|
65
189
|
#
|
@@ -71,10 +195,18 @@ module CFF
|
|
71
195
|
# ```
|
72
196
|
#
|
73
197
|
# Authors can be a Person or Entity.
|
74
|
-
def authors
|
75
|
-
@authors
|
76
|
-
end
|
77
198
|
|
199
|
+
##
|
200
|
+
# :method: authors=
|
201
|
+
# :call-seq:
|
202
|
+
# authors = array_of_authors -> Array
|
203
|
+
#
|
204
|
+
# Replace the list of authors for this citation.
|
205
|
+
#
|
206
|
+
# Authors can be a Person or Entity.
|
207
|
+
|
208
|
+
##
|
209
|
+
# :method: contact
|
78
210
|
# :call-seq:
|
79
211
|
# contact -> Array
|
80
212
|
#
|
@@ -86,23 +218,37 @@ module CFF
|
|
86
218
|
# ```
|
87
219
|
#
|
88
220
|
# Contacts can be a Person or Entity.
|
89
|
-
def contact
|
90
|
-
@contact
|
91
|
-
end
|
92
221
|
|
222
|
+
##
|
223
|
+
# :method: contact=
|
93
224
|
# :call-seq:
|
94
|
-
#
|
225
|
+
# contact = array_of_contacts -> Array
|
95
226
|
#
|
96
|
-
#
|
97
|
-
#
|
98
|
-
|
99
|
-
unless Date === date
|
100
|
-
date = Date.parse(date)
|
101
|
-
end
|
227
|
+
# Replace the list of contacts for this citation.
|
228
|
+
#
|
229
|
+
# Contacts can be a Person or Entity.
|
102
230
|
|
103
|
-
|
104
|
-
|
231
|
+
##
|
232
|
+
# :method: identifiers
|
233
|
+
# :call-seq:
|
234
|
+
# identifiers -> Array
|
235
|
+
#
|
236
|
+
# Return the list of identifiers for this citation. To add a identifier to
|
237
|
+
# the list, use:
|
238
|
+
#
|
239
|
+
# ```
|
240
|
+
# model.identifiers << identifier
|
241
|
+
# ```
|
105
242
|
|
243
|
+
##
|
244
|
+
# :method: identifiers=
|
245
|
+
# :call-seq:
|
246
|
+
# identifiers = array_of_identifiers -> Array
|
247
|
+
#
|
248
|
+
# Replace the list of identifiers for this citation.
|
249
|
+
|
250
|
+
##
|
251
|
+
# :method: keywords
|
106
252
|
# :call-seq:
|
107
253
|
# keywords -> Array
|
108
254
|
#
|
@@ -113,60 +259,48 @@ module CFF
|
|
113
259
|
# model.keywords << keyword
|
114
260
|
# ```
|
115
261
|
#
|
116
|
-
# Keywords will be converted to Strings on output
|
117
|
-
def keywords
|
118
|
-
@keywords
|
119
|
-
end
|
262
|
+
# Keywords will be converted to Strings on output.
|
120
263
|
|
264
|
+
##
|
265
|
+
# :method: keywords=
|
121
266
|
# :call-seq:
|
122
|
-
#
|
267
|
+
# keywords = array_of_keywords -> Array
|
123
268
|
#
|
124
|
-
#
|
125
|
-
|
126
|
-
|
127
|
-
end
|
128
|
-
|
129
|
-
def to_yaml # :nodoc:
|
130
|
-
fields = @fields.dup
|
131
|
-
fields['authors'] = array_field_to_yaml(@authors) unless @authors.empty?
|
132
|
-
fields['contact'] = array_field_to_yaml(@contact) unless @contact.empty?
|
133
|
-
fields['keywords'] = @keywords.map { |k| k.to_s } unless @keywords.empty?
|
134
|
-
|
135
|
-
YAML.dump fields, :line_width => -1, :indentation => 2
|
136
|
-
end
|
137
|
-
|
138
|
-
def method_missing(name, *args) # :nodoc:
|
139
|
-
n = method_to_field(name.id2name)
|
140
|
-
super unless ALLOWED_FIELDS.include?(n.chomp('='))
|
141
|
-
|
142
|
-
if n.end_with?('=')
|
143
|
-
@fields[n.chomp('=')] = args[0] || ''
|
144
|
-
else
|
145
|
-
@fields[n]
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
private
|
150
|
-
|
151
|
-
def build_model(fields)
|
152
|
-
build_entity_collection(@authors, fields['authors'])
|
153
|
-
build_entity_collection(@contact, fields['contact'])
|
154
|
-
@keywords = fields['keywords']
|
269
|
+
# Replace the list of keywords for this citation.
|
270
|
+
#
|
271
|
+
# Keywords will be converted to Strings on output.
|
155
272
|
|
156
|
-
|
157
|
-
|
273
|
+
##
|
274
|
+
# :method: preferred_citation
|
275
|
+
# :call-seq:
|
276
|
+
# preferred_citation -> Reference
|
277
|
+
#
|
278
|
+
# Return the preferred citation for this citation.
|
158
279
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
280
|
+
##
|
281
|
+
# :method: preferred_citation=
|
282
|
+
# :call-seq:
|
283
|
+
# preferred_citation = Reference
|
284
|
+
#
|
285
|
+
# Replace the preferred citation for this citation.
|
164
286
|
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
287
|
+
##
|
288
|
+
# :method: references
|
289
|
+
# :call-seq:
|
290
|
+
# references -> Array
|
291
|
+
#
|
292
|
+
# Return the list of references for this citation. To add a reference to the
|
293
|
+
# list, use:
|
294
|
+
#
|
295
|
+
# ```
|
296
|
+
# model.references << reference
|
297
|
+
# ```
|
170
298
|
|
299
|
+
##
|
300
|
+
# :method: references=
|
301
|
+
# :call-seq:
|
302
|
+
# references = array_of_references -> Array
|
303
|
+
#
|
304
|
+
# Replace the list of references for this citation.
|
171
305
|
end
|
172
306
|
end
|