cff 0.2.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- # Copyright (c) 2018 Robert Haines.
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
- class Model
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 Util
48
+ include Licensable
49
+ include Validatable
23
50
 
24
51
  ALLOWED_FIELDS = [
25
- 'abstract',
26
- 'cff-version',
27
- 'commit',
28
- 'date-released',
29
- 'doi',
30
- 'license',
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 = "If you use this software in your work, please cite it using the following metadata"
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
- @authors = []
50
- @contact = []
51
- @keywords = []
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
- # date_released = date
225
+ # contact = array_of_contacts -> Array
95
226
  #
96
- # Set the `date-released` field. If a non-Date object is passed in it will
97
- # be parsed into a Date.
98
- def date_released=(date)
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
- @fields['date-released'] = date
104
- end
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 to a CFF file.
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
- # version = version
267
+ # keywords = array_of_keywords -> Array
123
268
  #
124
- # Set the `version` field.
125
- def version=(version)
126
- @fields['version'] = version.to_s
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
- @fields = delete_from_hash(fields, 'authors', 'contact', 'keywords')
157
- end
273
+ ##
274
+ # :method: preferred_citation
275
+ # :call-seq:
276
+ # preferred_citation -> Reference
277
+ #
278
+ # Return the preferred citation for this citation.
158
279
 
159
- def build_entity_collection(field, source)
160
- source.each do |s|
161
- field << (s.has_key?('given-names') ? Person.new(s) : Entity.new(s))
162
- end
163
- end
280
+ ##
281
+ # :method: preferred_citation=
282
+ # :call-seq:
283
+ # preferred_citation = Reference
284
+ #
285
+ # Replace the preferred citation for this citation.
164
286
 
165
- def array_field_to_yaml(field)
166
- field.reject do |f|
167
- !f.respond_to?(:fields)
168
- end.map { |f| f.fields }
169
- end
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