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.
- checksums.yaml +4 -4
- data/CHANGES.md +91 -0
- data/CITATION.cff +62 -5
- data/CONTRIBUTING.md +71 -0
- data/LICENCE +1 -1
- data/README.md +57 -17
- data/Rakefile +9 -8
- data/cff.gemspec +12 -11
- data/lib/cff/citable.rb +72 -0
- data/lib/cff/entity.rb +11 -31
- data/lib/cff/errors.rb +14 -9
- data/lib/cff/file.rb +101 -31
- data/lib/cff/formatters/all.rb +26 -0
- data/lib/cff/formatters/apalike.rb +145 -0
- data/lib/cff/formatters/bibtex.rb +205 -0
- data/lib/cff/formatters/formatter.rb +98 -0
- data/lib/cff/formatters.rb +61 -0
- data/lib/cff/identifier.rb +14 -8
- data/lib/cff/{model.rb → index.rb} +73 -66
- data/lib/cff/licensable.rb +4 -5
- data/lib/cff/model_part.rb +46 -10
- data/lib/cff/person.rb +8 -10
- data/lib/cff/reference.rb +76 -112
- data/lib/cff/schema.rb +23 -0
- data/lib/{schema → cff/schemas}/1.2.0.json +1 -1
- data/lib/cff/util.rb +63 -6
- data/lib/cff/validatable.rb +13 -13
- data/lib/cff/version.rb +2 -2
- data/lib/cff.rb +4 -27
- metadata +37 -31
- data/lib/cff/formatter/apa_formatter.rb +0 -77
- data/lib/cff/formatter/bibtex_formatter.rb +0 -122
- data/lib/cff/formatter/formatter.rb +0 -63
@@ -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'
|
data/lib/cff/identifier.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Copyright (c) 2018-
|
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
|
-
|
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 = ['
|
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 =
|
56
|
+
@fields = {}
|
51
57
|
|
52
58
|
unless param.nil?
|
53
59
|
self.type = param
|
54
|
-
@fields['value'] = more[0] unless @fields['type'].
|
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-
|
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
|
-
#
|
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 `
|
27
|
-
# fields are simple strings and can be set as such. A field which has
|
28
|
-
# been set will return the empty string. The simple fields are (with
|
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
|
47
|
-
|
58
|
+
class Index < ModelPart
|
59
|
+
include Citable
|
48
60
|
include Licensable
|
49
61
|
include Validatable
|
50
62
|
|
51
|
-
ALLOWED_FIELDS = [
|
52
|
-
|
53
|
-
|
54
|
-
|
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) ->
|
65
|
-
# new(title) { |
|
75
|
+
# new(title) -> Index
|
76
|
+
# new(title) { |index| block } -> Index
|
66
77
|
#
|
67
|
-
# Initialize a new
|
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 =
|
71
|
-
@fields.default = ''
|
83
|
+
@fields = build_index(param)
|
72
84
|
else
|
73
|
-
@fields =
|
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
|
-
#
|
99
|
+
# read(String) -> Index
|
88
100
|
#
|
89
|
-
#
|
90
|
-
|
91
|
-
|
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
|
-
#
|
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
|
-
#
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
-
#
|
122
|
+
# type = type
|
117
123
|
#
|
118
|
-
#
|
119
|
-
#
|
120
|
-
#
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
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
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
280
|
+
# index.references << reference
|
274
281
|
# ```
|
275
282
|
|
276
283
|
##
|