cff 0.1.0 → 0.8.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.
@@ -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)
44
+
45
+ vol = "#{model.volume}(#{model.issue})" unless model.volume.to_s.empty?
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,71 @@
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
+ # * `type`
29
+ # * `value`
30
+ class Identifier < ModelPart
31
+
32
+ ALLOWED_FIELDS = ['type', 'value'].freeze # :nodoc:
33
+
34
+ # The [defined set of identifier types](https://github.com/citation-file-format/citation-file-format/blob/main/README.md#identifier-type-strings).
35
+ IDENTIFIER_TYPES = ['doi', 'url', 'swh', 'other'].freeze
36
+
37
+ # :call-seq:
38
+ # new -> Identifier
39
+ # new { |id| block } -> Identifier
40
+ # new(type, value) -> Identifier
41
+ # new(type, value) { |id| block } -> Identifier
42
+ #
43
+ # Create a new Identifier with the optionally supplied type and value.
44
+ # If the supplied type is invalid, then neither the type or value are set.
45
+ def initialize(param = nil, *more)
46
+ if param.is_a?(Hash)
47
+ @fields = param
48
+ @fields.default = ''
49
+ else
50
+ @fields = Hash.new('')
51
+
52
+ unless param.nil?
53
+ self.type = param
54
+ @fields['value'] = more[0] unless @fields['type'].empty?
55
+ end
56
+ end
57
+
58
+ yield self if block_given?
59
+ end
60
+
61
+ # :call-seq:
62
+ # type = type
63
+ #
64
+ # Sets the type of this Identifier. The type is restricted to a
65
+ # [defined set of identifier types](https://github.com/citation-file-format/citation-file-format/blob/main/README.md#identifier-type-strings).
66
+ def type=(type)
67
+ type = type.downcase
68
+ @fields['type'] = type if IDENTIFIER_TYPES.include?(type)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,46 @@
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
+ # :nodoc:
24
+ LICENSES = SCHEMA_FILE['definitions']['license-enum']['enum'].dup.freeze
25
+
26
+ # :call-seq:
27
+ # license = license
28
+ # license = Array
29
+ #
30
+ # Set the license, or licenses, of this work. Only licenses that conform
31
+ # to the [SPDX License List](https://spdx.org/licenses/) will be accepted.
32
+ # If you need specify a different license you should set `license-url`
33
+ # with a link to the license instead.
34
+ def license=(lic)
35
+ list = [*lic].select { |l| LICENSES.include?(l) }
36
+ @fields['license'] = case list.length
37
+ when 0
38
+ @fields['license']
39
+ when 1
40
+ list[0]
41
+ else
42
+ list
43
+ end
44
+ end
45
+ end
46
+ 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,33 +14,61 @@
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
21
-
22
- ALLOWED_METHODS = [
23
- :cff_version,
24
- :date_released,
25
- :message,
26
- :message=,
27
- :title,
28
- :title=,
29
- :version
30
- ] # :nodoc:
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
47
+
48
+ include Licensable
49
+ include Validatable
50
+
51
+ ALLOWED_FIELDS = [
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:
31
58
 
32
59
  # The default message to use if none is explicitly set.
33
- 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'
34
62
 
35
63
  # :call-seq:
36
64
  # new(title) -> Model
65
+ # new(title) { |model| block } -> Model
37
66
  #
38
67
  # Initialize a new Model with the supplied title.
39
68
  def initialize(param)
40
- if Hash === param
41
- @fields = param
69
+ if param.is_a?(Hash)
70
+ @fields = build_model(param)
71
+ @fields.default = ''
42
72
  else
43
73
  @fields = Hash.new('')
44
74
  @fields['cff-version'] = DEFAULT_SPEC_VERSION
@@ -46,9 +76,91 @@ module CFF
46
76
  @fields['title'] = param
47
77
  end
48
78
 
49
- @authors = []
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
+ # date_released = date
88
+ #
89
+ # Set the `date-released` field. If a non-Date object is passed in it will
90
+ # be parsed into a Date.
91
+ def date_released=(date)
92
+ date = Date.parse(date) unless date.is_a?(Date)
93
+
94
+ @fields['date-released'] = date
95
+ end
96
+
97
+ def to_yaml # :nodoc:
98
+ YAML.dump fields, line_width: -1, indentation: 2
99
+ end
100
+
101
+ # :call-seq:
102
+ # to_apalike(preferred_citation: true) -> String
103
+ #
104
+ # Output this Model in an APA-like format. Setting
105
+ # `preferred_citation: true` will honour the `preferred_citation` field in
106
+ # the model if one is present (default).
107
+ #
108
+ # *Note:* This method assumes that this Model is valid when called.
109
+ def to_apalike(preferred_citation: true)
110
+ CFF::ApaFormatter.format(
111
+ model: self, preferred_citation: preferred_citation
112
+ )
113
+ end
114
+
115
+ # :call-seq:
116
+ # to_bibtex(preferred_citation: true) -> String
117
+ #
118
+ # Output this Model in BibTeX format. Setting
119
+ # `preferred_citation: true` will honour the `preferred_citation` field in
120
+ # the model if one is present (default).
121
+ #
122
+ # *Note:* This method assumes that this Model is valid when called.
123
+ def to_bibtex(preferred_citation: true)
124
+ CFF::BibtexFormatter.format(
125
+ model: self, preferred_citation: preferred_citation
126
+ )
127
+ end
128
+
129
+ private
130
+
131
+ def fields
132
+ %w[authors contact identifiers references].each do |field|
133
+ normalize_modelpart_array!(@fields[field])
134
+ end
135
+
136
+ fields_to_hash(@fields)
50
137
  end
51
138
 
139
+ def build_model(fields) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
140
+ build_actor_collection!(fields['authors'] || [])
141
+ build_actor_collection!(fields['contact'] || [])
142
+ (fields['identifiers'] || []).map! do |i|
143
+ Identifier.new(i)
144
+ end
145
+ (fields['references'] || []).map! do |r|
146
+ Reference.new(r)
147
+ end
148
+ fields['preferred-citation'] &&=
149
+ Reference.new(fields['preferred-citation'])
150
+
151
+ # Only attempt an update of the `cff-version` field if it is present.
152
+ fields['cff-version'] &&= update_cff_version(fields['cff-version'])
153
+
154
+ fields
155
+ end
156
+
157
+ public
158
+
159
+ # Some documentation of "hidden" methods is provided here, out of the
160
+ # way of the main class code.
161
+
162
+ ##
163
+ # :method: authors
52
164
  # :call-seq:
53
165
  # authors -> Array
54
166
  #
@@ -60,56 +172,112 @@ module CFF
60
172
  # ```
61
173
  #
62
174
  # Authors can be a Person or Entity.
63
- def authors
64
- @authors
65
- end
66
175
 
176
+ ##
177
+ # :method: authors=
67
178
  # :call-seq:
68
- # date_released = date
179
+ # authors = array_of_authors -> Array
69
180
  #
70
- # Set the `date-released` field. If a non-Date object is passed in it will
71
- # be parsed into a Date.
72
- def date_released=(date)
73
- unless Date === date
74
- date = Date.parse(date)
75
- end
181
+ # Replace the list of authors for this citation.
182
+ #
183
+ # Authors can be a Person or Entity.
76
184
 
77
- @fields['date-released'] = date
78
- end
185
+ ##
186
+ # :method: contact
187
+ # :call-seq:
188
+ # contact -> Array
189
+ #
190
+ # Return the list of contacts for this citation. To add a contact to the
191
+ # list, use:
192
+ #
193
+ # ```
194
+ # model.contact << contact
195
+ # ```
196
+ #
197
+ # Contacts can be a Person or Entity.
79
198
 
199
+ ##
200
+ # :method: contact=
80
201
  # :call-seq:
81
- # version = version
202
+ # contact = array_of_contacts -> Array
82
203
  #
83
- # Set the `version` field.
84
- def version=(version)
85
- @fields['version'] = version.to_s
86
- end
204
+ # Replace the list of contacts for this citation.
205
+ #
206
+ # Contacts can be a Person or Entity.
87
207
 
88
- def to_yaml # :nodoc:
89
- fields = @fields.dup
90
- fields['authors'] = @authors.reject do |a|
91
- !a.respond_to?(:fields)
92
- end.map { |a| a.fields }
208
+ ##
209
+ # :method: identifiers
210
+ # :call-seq:
211
+ # identifiers -> Array
212
+ #
213
+ # Return the list of identifiers for this citation. To add a identifier to
214
+ # the list, use:
215
+ #
216
+ # ```
217
+ # model.identifiers << identifier
218
+ # ```
93
219
 
94
- YAML.dump fields, :line_width => -1, :indentation => 2
95
- end
220
+ ##
221
+ # :method: identifiers=
222
+ # :call-seq:
223
+ # identifiers = array_of_identifiers -> Array
224
+ #
225
+ # Replace the list of identifiers for this citation.
96
226
 
97
- def method_missing(name, *args) # :nodoc:
98
- super unless ALLOWED_METHODS.include?(name)
227
+ ##
228
+ # :method: keywords
229
+ # :call-seq:
230
+ # keywords -> Array
231
+ #
232
+ # Return the list of keywords for this citation. To add a keyword to the
233
+ # list, use:
234
+ #
235
+ # ```
236
+ # model.keywords << keyword
237
+ # ```
238
+ #
239
+ # Keywords will be converted to Strings on output.
99
240
 
100
- n = method_to_field(name.id2name)
101
- if n.end_with?('=')
102
- @fields[n.chomp('=')] = args[0] || ''
103
- else
104
- @fields[n]
105
- end
106
- end
241
+ ##
242
+ # :method: keywords=
243
+ # :call-seq:
244
+ # keywords = array_of_keywords -> Array
245
+ #
246
+ # Replace the list of keywords for this citation.
247
+ #
248
+ # Keywords will be converted to Strings on output.
107
249
 
108
- private
250
+ ##
251
+ # :method: preferred_citation
252
+ # :call-seq:
253
+ # preferred_citation -> Reference
254
+ #
255
+ # Return the preferred citation for this citation.
109
256
 
110
- def method_to_field(name)
111
- name.gsub('_', '-')
112
- end
257
+ ##
258
+ # :method: preferred_citation=
259
+ # :call-seq:
260
+ # preferred_citation = Reference
261
+ #
262
+ # Replace the preferred citation for this citation.
263
+
264
+ ##
265
+ # :method: references
266
+ # :call-seq:
267
+ # references -> Array
268
+ #
269
+ # Return the list of references for this citation. To add a reference to the
270
+ # list, use:
271
+ #
272
+ # ```
273
+ # model.references << reference
274
+ # ```
113
275
 
276
+ ##
277
+ # :method: references=
278
+ # :call-seq:
279
+ # references = array_of_references -> Array
280
+ #
281
+ # Replace the list of references for this citation.
114
282
  end
115
283
  end