cff 0.8.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
##
|