cff 0.8.0 → 1.0.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,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018-2022 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
+ require_relative 'formatter'
18
+
19
+ ##
20
+ module CFF
21
+ module Formatters # :nodoc:
22
+ # Generates an BibTeX citation string
23
+ class BibTeX < Formatter # :nodoc:
24
+ # Fields without `!` have a simple one-to-one mapping between CFF and
25
+ # BibTeX. Those with `!` call out to a more complex getter.
26
+ ENTRY_TYPE_MAP = {
27
+ 'article' => %w[doi journal note! number! pages! volume],
28
+ 'book' => %w[address! doi editor! isbn number! pages! publisher! volume],
29
+ 'booklet' => %w[address! doi],
30
+ 'inproceedings' => %w[address! booktitle! doi editor! pages! publisher! series!],
31
+ 'manual' => %w[address! doi],
32
+ 'mastersthesis' => %w[address! doi school! type!],
33
+ 'misc' => %w[doi pages!],
34
+ 'phdthesis' => %w[address! doi school! type!],
35
+ 'proceedings' => %w[address! booktitle! doi editor! pages! publisher! series!],
36
+ 'software' => %w[doi license version],
37
+ 'techreport' => %w[address! doi institution! number!],
38
+ 'unpublished' => %w[doi note!]
39
+ }.freeze
40
+
41
+ def self.format(model:, preferred_citation: true) # rubocop:disable Metrics/AbcSize
42
+ model = select_and_check_model(model, preferred_citation)
43
+ return if model.nil?
44
+
45
+ values = {}
46
+ values['author'] = actor_list(model.authors)
47
+ values['title'] = "{#{model.title}}"
48
+
49
+ publication_type = bibtex_type(model)
50
+ publication_data_from_model(model, publication_type, values)
51
+
52
+ month, year = month_and_year_from_model(model)
53
+ values['month'] = month
54
+ values['year'] = year
55
+
56
+ values['url'] = url(model)
57
+
58
+ values['note'] ||= model.notes unless model.is_a?(Index)
59
+
60
+ values.reject! { |_, v| v.empty? }
61
+ sorted_values = values.sort.map do |key, value|
62
+ "#{key} = {#{value}}"
63
+ end
64
+ sorted_values.insert(0, generate_citekey(values))
65
+
66
+ "@#{publication_type}{#{sorted_values.join(",\n")}\n}"
67
+ end
68
+
69
+ # Get various bits of information about the reference publication.
70
+ # Reference: https://www.bibtex.com/format/
71
+ def self.publication_data_from_model(model, type, fields)
72
+ ENTRY_TYPE_MAP[type].each do |field|
73
+ if model.respond_to?(field)
74
+ fields[field] = model.send(field).to_s
75
+ else
76
+ field = field.chomp('!')
77
+ fields[field] = send("#{field}_from_model", model)
78
+ end
79
+ end
80
+ end
81
+
82
+ # BibTeX 'number' is CFF 'issue'.
83
+ def self.number_from_model(model)
84
+ model.issue.to_s
85
+ end
86
+
87
+ # BibTeX 'address' is taken from the publisher (book, others) or the
88
+ # conference (inproceedings).
89
+ def self.address_from_model(model)
90
+ entity = if model.type == 'conference-paper'
91
+ model.conference
92
+ else
93
+ model.publisher
94
+ end
95
+ return '' if entity.empty?
96
+
97
+ [entity.city, entity.region, entity.country].reject(&:empty?).join(', ')
98
+ end
99
+
100
+ # BibTeX 'institution' could be grabbed from an author's affiliation, or
101
+ # provided explicitly.
102
+ def self.institution_from_model(model)
103
+ return model.institution.name unless model.institution.empty?
104
+
105
+ model.authors.first.affiliation
106
+ end
107
+
108
+ # BibTeX 'school' is CFF 'institution'.
109
+ def self.school_from_model(model)
110
+ institution_from_model(model)
111
+ end
112
+
113
+ # BibTeX 'type' for theses is CFF 'thesis-type'.
114
+ def self.type_from_model(model)
115
+ model.thesis_type
116
+ end
117
+
118
+ # BibTeX 'booktitle' is CFF 'collection-title'.
119
+ def self.booktitle_from_model(model)
120
+ model.collection_title
121
+ end
122
+
123
+ # BibTeX 'editor' is CFF 'editors' or 'editors-series'.
124
+ def self.editor_from_model(model)
125
+ if model.editors.empty?
126
+ model.editors_series.empty? ? '' : actor_list(model.editors_series)
127
+ else
128
+ actor_list(model.editors)
129
+ end
130
+ end
131
+
132
+ def self.publisher_from_model(model)
133
+ model.publisher.empty? ? '' : model.publisher.name
134
+ end
135
+
136
+ def self.series_from_model(model)
137
+ model.conference.empty? ? '' : model.conference.name
138
+ end
139
+
140
+ # If we're citing a conference paper, try and use the date of the
141
+ # conference. Otherwise use the specified month and year, or the date
142
+ # of release.
143
+ def self.month_and_year_from_model(model)
144
+ if model.type == 'conference-paper' && !model.conference.empty?
145
+ date = model.conference.date_start
146
+ return month_and_year_from_date(date) unless date == ''
147
+ end
148
+
149
+ super
150
+ end
151
+
152
+ # Do what we can to map between CFF reference types and bibtex types.
153
+ # References:
154
+ # * https://www.bibtex.com/e/entry-types/
155
+ # * https://ctan.gutenberg.eu.org/macros/latex/contrib/biblatex-contrib/biblatex-software/software-biblatex.pdf
156
+ def self.bibtex_type(model) # rubocop:disable Metrics/CyclomaticComplexity
157
+ return 'software' if model.type.empty? || model.type.include?('software')
158
+
159
+ case model.type
160
+ when 'article', 'book', 'manual', 'unpublished', 'phdthesis', 'mastersthesis'
161
+ model.type
162
+ when 'conference', 'proceedings'
163
+ 'proceedings'
164
+ when 'conference-paper'
165
+ 'inproceedings'
166
+ when 'magazine-article', 'newspaper-article'
167
+ 'article'
168
+ when 'pamphlet'
169
+ 'booklet'
170
+ when 'report'
171
+ 'techreport'
172
+ else
173
+ 'misc'
174
+ end
175
+ end
176
+
177
+ def self.format_actor(author)
178
+ return "{#{author.name}}" if author.is_a?(Entity)
179
+
180
+ particle =
181
+ author.name_particle.empty? ? '' : "#{author.name_particle} "
182
+
183
+ [
184
+ "#{particle}#{author.family_names}",
185
+ author.name_suffix,
186
+ author.given_names
187
+ ].reject(&:empty?).join(', ')
188
+ end
189
+
190
+ def self.actor_list(actors)
191
+ actors.map { |actor| format_actor(actor) }.join(' and ')
192
+ end
193
+
194
+ def self.generate_citekey(fields)
195
+ reference = [
196
+ fields['author'].split(',', 2)[0],
197
+ fields['title'].split[0..2],
198
+ fields['year']
199
+ ].compact.join('_')
200
+
201
+ Util.parameterize(reference)
202
+ end
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018-2022 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
+ require 'date'
18
+
19
+ ##
20
+ module CFF
21
+ module Formatters # :nodoc:
22
+ # Formatter base class
23
+ class Formatter # :nodoc:
24
+ STATUS_TEXT_MAP = {
25
+ 'advance-online' => 'Advance online publication',
26
+ 'in-preparation' => 'Manuscript in preparation.',
27
+ 'submitted' => 'Manuscript submitted for publication.'
28
+ }.freeze
29
+
30
+ def self.label
31
+ @label ||= name.split('::')[-1]
32
+ end
33
+
34
+ def self.select_and_check_model(model, preferred_citation)
35
+ if preferred_citation && model.preferred_citation.is_a?(Reference)
36
+ model = model.preferred_citation
37
+ end
38
+
39
+ # Safe to assume valid `Index`s and `Reference`s will have these fields.
40
+ model.authors.empty? || model.title.empty? ? nil : model
41
+ end
42
+
43
+ def self.initials(name)
44
+ name.split.map { |part| part[0].capitalize }.join('. ')
45
+ end
46
+
47
+ def self.note_from_model(model)
48
+ STATUS_TEXT_MAP[model.status]
49
+ end
50
+
51
+ # Prefer `repository_code` over `url`
52
+ def self.url(model)
53
+ model.repository_code.empty? ? model.url : model.repository_code
54
+ end
55
+
56
+ def self.month_and_year_from_model(model) # rubocop:disable Metrics
57
+ return ['', 'in press'] if model.respond_to?(:status) && model.status == 'in-press'
58
+ if model.respond_to?(:year) && !model.year.to_s.empty?
59
+ return [model.month, model.year].map(&:to_s)
60
+ end
61
+
62
+ date = month_and_year_from_date(model.date_released)
63
+ if date == ['', ''] && model.respond_to?(:date_published)
64
+ date = month_and_year_from_date(model.date_published)
65
+ end
66
+ date
67
+ end
68
+
69
+ def self.month_and_year_from_date(value)
70
+ if value.is_a?(Date)
71
+ [value.month, value.year].map(&:to_s)
72
+ else
73
+ begin
74
+ date = Date.parse(value.to_s)
75
+ [date.month, date.year].map(&:to_s)
76
+ rescue ArgumentError
77
+ ['', '']
78
+ end
79
+ end
80
+ end
81
+
82
+ # CFF 'pages' is the number of pages, which has no equivalent in BibTeX
83
+ # or APA. References: https://www.bibtex.com/f/pages-field/,
84
+ # https://apastyle.apa.org/style-grammar-guidelines/references/examples
85
+ def self.pages_from_model(model, dash: '--')
86
+ return '' if !model.respond_to?(:start) || model.start.to_s.empty?
87
+
88
+ start = model.start.to_s
89
+ finish = model.end.to_s
90
+ if finish.empty?
91
+ start
92
+ else
93
+ start == finish ? start : "#{start}#{dash}#{finish}"
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018-2022 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
+ # A registry of output formatters for converting CFF files into citations.
20
+ module Formatters
21
+ @formatters = {}
22
+
23
+ # :call-seq:
24
+ # formatters -> Array
25
+ #
26
+ # Return the list of formatters that are available.
27
+ def self.formatters
28
+ @formatters.keys
29
+ end
30
+
31
+ # :call-seq:
32
+ # register_formatter(class)
33
+ #
34
+ # Register a citation formatter. To be registered as a formatter, a
35
+ # class should at least provide the following class methods:
36
+ #
37
+ # * `format`, which takes the model to be formatted
38
+ # as a named parameter, and the option to cite a CFF file's
39
+ # `preferred-citation`:
40
+ # ```ruby
41
+ # def self.format(model:, preferred_citation: true); end
42
+ # ```
43
+ # * `label`, which returns a short name for the formatter, e.g.
44
+ # `'BibTeX'`. If your formatter class subclasses `CFF::Formatter`,
45
+ # then `label` is provided for you.
46
+ def self.register_formatter(clazz)
47
+ return unless clazz.singleton_methods.include?(:format)
48
+ return if @formatters.has_value?(clazz)
49
+
50
+ format = clazz.label.downcase.to_sym
51
+ @formatters[format] = clazz
52
+ Citable.add_to_format_method(format) if defined?(Citable)
53
+ end
54
+
55
+ def self.formatter_for(format) # :nodoc:
56
+ @formatters[format.downcase.to_sym]
57
+ end
58
+ end
59
+ end
60
+
61
+ require_relative 'formatters/all'
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2018-2021 The Ruby Citation File Format Developers.
3
+ # Copyright (c) 2018-2022 The Ruby Citation File Format Developers.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
@@ -14,9 +14,11 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
 
17
+ require_relative 'model_part'
18
+ require_relative 'schema'
19
+
17
20
  ##
18
21
  module CFF
19
-
20
22
  # An Identifier represents an identifier in a CITATION.cff file.
21
23
  #
22
24
  # Identifier implements all of the fields listed in the
@@ -25,14 +27,17 @@ module CFF
25
27
  # will return the empty string. The simple fields are (with defaults in
26
28
  # parentheses):
27
29
  #
30
+ # * `description`
28
31
  # * `type`
29
32
  # * `value`
30
33
  class Identifier < ModelPart
31
-
32
- ALLOWED_FIELDS = ['type', 'value'].freeze # :nodoc:
34
+ ALLOWED_FIELDS = # :nodoc:
35
+ SCHEMA_FILE['definitions']['identifier']['anyOf'].first['properties'].keys.dup.freeze
33
36
 
34
37
  # 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
38
+ IDENTIFIER_TYPES = SCHEMA_FILE['definitions']['identifier']['anyOf'].map do |id|
39
+ id['properties']['type']['enum'].first
40
+ end.freeze
36
41
 
37
42
  # :call-seq:
38
43
  # new -> Identifier
@@ -43,15 +48,16 @@ module CFF
43
48
  # Create a new Identifier with the optionally supplied type and value.
44
49
  # If the supplied type is invalid, then neither the type or value are set.
45
50
  def initialize(param = nil, *more)
51
+ super()
52
+
46
53
  if param.is_a?(Hash)
47
54
  @fields = param
48
- @fields.default = ''
49
55
  else
50
- @fields = Hash.new('')
56
+ @fields = {}
51
57
 
52
58
  unless param.nil?
53
59
  self.type = param
54
- @fields['value'] = more[0] unless @fields['type'].empty?
60
+ @fields['value'] = more[0] unless @fields['type'].nil?
55
61
  end
56
62
  end
57
63
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Copyright (c) 2018-2021 The Ruby Citation File Format Developers.
3
+ # Copyright (c) 2018-2022 The Ruby Citation File Format Developers.
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
6
6
  # you may not use this file except in compliance with the License.
@@ -14,19 +14,31 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
 
17
+ require_relative 'util'
18
+ require_relative 'model_part'
19
+ require_relative 'entity'
20
+ require_relative 'identifier'
21
+ require_relative 'licensable'
22
+ require_relative 'person'
23
+ require_relative 'reference'
24
+ require_relative 'schema'
25
+ require_relative 'validatable'
26
+ require_relative 'citable'
27
+
28
+ require 'yaml'
29
+
17
30
  ##
18
31
  module CFF
19
-
20
- # Model is the core data structure for a CITATION.cff file. It can be
32
+ # Index is the core data structure for a CITATION.cff file. It can be
21
33
  # accessed direcly, or via File.
22
34
  #
23
- # Model implements all of the fields listed in the
35
+ # Index implements all of the fields listed in the
24
36
  # [CFF standard](https://citation-file-format.github.io/). Complex
25
37
  # 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):
38
+ # `preferred-citation`, `references` and `type` - are documented below. All
39
+ # other fields are simple strings and can be set as such. A field which has
40
+ # not been set will return the empty string. The simple fields are (with
41
+ # defaults in parentheses):
30
42
  #
31
43
  # * `abstract`
32
44
  # * `cff_version`
@@ -43,102 +55,97 @@ module CFF
43
55
  # * `title`
44
56
  # * `url`
45
57
  # * `version`
46
- class Model < ModelPart
47
-
58
+ class Index < ModelPart
59
+ include Citable
48
60
  include Licensable
49
61
  include Validatable
50
62
 
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:
63
+ ALLOWED_FIELDS = SCHEMA_FILE['properties'].keys.freeze # :nodoc:
64
+
65
+ # The allowed CFF [types](https://github.com/citation-file-format/citation-file-format/blob/main/schema-guide.md#type).
66
+ MODEL_TYPES = SCHEMA_FILE['properties']['type']['enum'].dup.freeze
58
67
 
59
68
  # The default message to use if none is explicitly set.
60
69
  DEFAULT_MESSAGE = 'If you use this software in your work, please cite ' \
61
70
  'it using the following metadata'
62
71
 
72
+ attr_date :date_released
73
+
63
74
  # :call-seq:
64
- # new(title) -> Model
65
- # new(title) { |model| block } -> Model
75
+ # new(title) -> Index
76
+ # new(title) { |index| block } -> Index
66
77
  #
67
- # Initialize a new Model with the supplied title.
78
+ # Initialize a new Index with the supplied title.
68
79
  def initialize(param)
80
+ super()
81
+
69
82
  if param.is_a?(Hash)
70
- @fields = build_model(param)
71
- @fields.default = ''
83
+ @fields = build_index(param)
72
84
  else
73
- @fields = Hash.new('')
85
+ @fields = {}
74
86
  @fields['cff-version'] = DEFAULT_SPEC_VERSION
75
87
  @fields['message'] = DEFAULT_MESSAGE
76
88
  @fields['title'] = param
77
89
  end
78
90
 
79
91
  %w[authors contact identifiers keywords references].each do |field|
80
- @fields[field] = [] if @fields[field].empty?
92
+ @fields[field] = [] if @fields[field].nil? || @fields[field].empty?
81
93
  end
82
94
 
83
95
  yield self if block_given?
84
96
  end
85
97
 
86
98
  # :call-seq:
87
- # date_released = date
99
+ # read(String) -> Index
88
100
  #
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
101
+ # Read a CFF Index from a String and parse it for subsequent manipulation.
102
+ def self.read(index)
103
+ new(YAML.safe_load(index, permitted_classes: [Date, Time]))
99
104
  end
100
105
 
101
106
  # :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
+ # open(String) -> Index
108
+ # open(String) { |cff| block } -> Index
107
109
  #
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
- )
110
+ # With no associated block, Index.open is a synonym for ::read. If the
111
+ # optional code block is given, it will be passed the parsed index as an
112
+ # argument and the Index will be returned when the block terminates.
113
+ def self.open(index)
114
+ cff = Index.read(index)
115
+
116
+ yield cff if block_given?
117
+
118
+ cff
113
119
  end
114
120
 
115
121
  # :call-seq:
116
- # to_bibtex(preferred_citation: true) -> String
122
+ # type = type
117
123
  #
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
- )
124
+ # Sets the type of this CFF Index. The type is currently restricted to one
125
+ # of `software` or `dataset`. If this field is not set then you should
126
+ # assume that the type is `software`.
127
+ def type=(type)
128
+ type = type.downcase
129
+ @fields['type'] = type if MODEL_TYPES.include?(type)
130
+ end
131
+
132
+ def to_yaml # :nodoc:
133
+ YAML.dump fields, line_width: -1, indentation: 2
127
134
  end
128
135
 
129
136
  private
130
137
 
131
138
  def fields
132
139
  %w[authors contact identifiers references].each do |field|
133
- normalize_modelpart_array!(@fields[field])
140
+ Util.normalize_modelpart_array!(@fields[field])
134
141
  end
135
142
 
136
- fields_to_hash(@fields)
143
+ Util.fields_to_hash(@fields)
137
144
  end
138
145
 
139
- def build_model(fields) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
140
- build_actor_collection!(fields['authors'] || [])
141
- build_actor_collection!(fields['contact'] || [])
146
+ def build_index(fields) # rubocop:disable Metrics
147
+ Util.build_actor_collection!(fields['authors'] || [])
148
+ Util.build_actor_collection!(fields['contact'] || [])
142
149
  (fields['identifiers'] || []).map! do |i|
143
150
  Identifier.new(i)
144
151
  end
@@ -149,7 +156,7 @@ module CFF
149
156
  Reference.new(fields['preferred-citation'])
150
157
 
151
158
  # Only attempt an update of the `cff-version` field if it is present.
152
- fields['cff-version'] &&= update_cff_version(fields['cff-version'])
159
+ fields['cff-version'] &&= Util.update_cff_version(fields['cff-version'])
153
160
 
154
161
  fields
155
162
  end
@@ -168,7 +175,7 @@ module CFF
168
175
  # list, use:
169
176
  #
170
177
  # ```
171
- # model.authors << author
178
+ # index.authors << author
172
179
  # ```
173
180
  #
174
181
  # Authors can be a Person or Entity.
@@ -191,7 +198,7 @@ module CFF
191
198
  # list, use:
192
199
  #
193
200
  # ```
194
- # model.contact << contact
201
+ # index.contact << contact
195
202
  # ```
196
203
  #
197
204
  # Contacts can be a Person or Entity.
@@ -214,7 +221,7 @@ module CFF
214
221
  # the list, use:
215
222
  #
216
223
  # ```
217
- # model.identifiers << identifier
224
+ # index.identifiers << identifier
218
225
  # ```
219
226
 
220
227
  ##
@@ -233,7 +240,7 @@ module CFF
233
240
  # list, use:
234
241
  #
235
242
  # ```
236
- # model.keywords << keyword
243
+ # index.keywords << keyword
237
244
  # ```
238
245
  #
239
246
  # Keywords will be converted to Strings on output.
@@ -270,7 +277,7 @@ module CFF
270
277
  # list, use:
271
278
  #
272
279
  # ```
273
- # model.references << reference
280
+ # index.references << reference
274
281
  # ```
275
282
 
276
283
  ##