cff 0.4.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.
- checksums.yaml +4 -4
- data/CHANGES.md +68 -0
- data/CITATION.cff +9 -4
- data/Gemfile +2 -0
- data/LICENCE +1 -1
- data/README.md +136 -20
- data/Rakefile +3 -1
- data/bin/console +1 -0
- data/cff.gemspec +29 -15
- data/lib/cff.rb +22 -10
- data/lib/cff/entity.rb +28 -1
- data/lib/cff/errors.rb +45 -0
- data/lib/cff/file.rb +132 -13
- data/lib/cff/formatter/apa_formatter.rb +77 -0
- data/lib/cff/formatter/bibtex_formatter.rb +122 -0
- data/lib/cff/formatter/formatter.rb +63 -0
- data/lib/cff/identifier.rb +71 -0
- data/lib/cff/licensable.rb +46 -0
- data/lib/cff/model.rb +108 -14
- data/lib/cff/model_part.rb +3 -1
- data/lib/cff/person.rb +42 -8
- data/lib/cff/reference.rb +141 -42
- data/lib/cff/util.rb +15 -1
- data/lib/cff/validatable.rb +55 -0
- data/lib/cff/version.rb +6 -3
- data/lib/schema/1.2.0.json +1882 -0
- metadata +82 -28
@@ -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
|
-
#
|
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.
|
@@ -17,21 +19,50 @@ 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.
|
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`
|
20
46
|
class Model < ModelPart
|
21
47
|
|
48
|
+
include Licensable
|
49
|
+
include Validatable
|
50
|
+
|
22
51
|
ALLOWED_FIELDS = [
|
23
52
|
'abstract', 'authors', 'cff-version', 'contact', 'commit',
|
24
|
-
'date-released', 'doi', '
|
25
|
-
'
|
26
|
-
'
|
53
|
+
'date-released', 'doi', 'identifiers', 'keywords', 'license',
|
54
|
+
'license-url', 'message', 'preferred-citation', 'references',
|
55
|
+
'repository', 'repository-artifact', 'repository-code', 'title',
|
56
|
+
'url', 'version'
|
27
57
|
].freeze # :nodoc:
|
28
58
|
|
29
59
|
# The default message to use if none is explicitly set.
|
30
60
|
DEFAULT_MESSAGE = 'If you use this software in your work, please cite ' \
|
31
|
-
|
61
|
+
'it using the following metadata'
|
32
62
|
|
33
63
|
# :call-seq:
|
34
64
|
# new(title) -> Model
|
65
|
+
# new(title) { |model| block } -> Model
|
35
66
|
#
|
36
67
|
# Initialize a new Model with the supplied title.
|
37
68
|
def initialize(param)
|
@@ -45,9 +76,11 @@ module CFF
|
|
45
76
|
@fields['title'] = param
|
46
77
|
end
|
47
78
|
|
48
|
-
%w[authors contact keywords references].each do |field|
|
79
|
+
%w[authors contact identifiers keywords references].each do |field|
|
49
80
|
@fields[field] = [] if @fields[field].empty?
|
50
81
|
end
|
82
|
+
|
83
|
+
yield self if block_given?
|
51
84
|
end
|
52
85
|
|
53
86
|
# :call-seq:
|
@@ -61,34 +94,62 @@ module CFF
|
|
61
94
|
@fields['date-released'] = date
|
62
95
|
end
|
63
96
|
|
97
|
+
def to_yaml # :nodoc:
|
98
|
+
YAML.dump fields, line_width: -1, indentation: 2
|
99
|
+
end
|
100
|
+
|
64
101
|
# :call-seq:
|
65
|
-
#
|
102
|
+
# to_apalike(preferred_citation: true) -> String
|
66
103
|
#
|
67
|
-
#
|
68
|
-
|
69
|
-
|
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
|
+
)
|
70
113
|
end
|
71
114
|
|
72
|
-
|
73
|
-
|
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
|
+
)
|
74
127
|
end
|
75
128
|
|
76
129
|
private
|
77
130
|
|
78
131
|
def fields
|
79
|
-
%w[authors contact references].each do |field|
|
132
|
+
%w[authors contact identifiers references].each do |field|
|
80
133
|
normalize_modelpart_array!(@fields[field])
|
81
134
|
end
|
82
135
|
|
83
136
|
fields_to_hash(@fields)
|
84
137
|
end
|
85
138
|
|
86
|
-
def build_model(fields)
|
139
|
+
def build_model(fields) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
87
140
|
build_actor_collection!(fields['authors'] || [])
|
88
141
|
build_actor_collection!(fields['contact'] || [])
|
142
|
+
(fields['identifiers'] || []).map! do |i|
|
143
|
+
Identifier.new(i)
|
144
|
+
end
|
89
145
|
(fields['references'] || []).map! do |r|
|
90
146
|
Reference.new(r)
|
91
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'])
|
92
153
|
|
93
154
|
fields
|
94
155
|
end
|
@@ -144,6 +205,25 @@ module CFF
|
|
144
205
|
#
|
145
206
|
# Contacts can be a Person or Entity.
|
146
207
|
|
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
|
+
# ```
|
219
|
+
|
220
|
+
##
|
221
|
+
# :method: identifiers=
|
222
|
+
# :call-seq:
|
223
|
+
# identifiers = array_of_identifiers -> Array
|
224
|
+
#
|
225
|
+
# Replace the list of identifiers for this citation.
|
226
|
+
|
147
227
|
##
|
148
228
|
# :method: keywords
|
149
229
|
# :call-seq:
|
@@ -167,6 +247,20 @@ module CFF
|
|
167
247
|
#
|
168
248
|
# Keywords will be converted to Strings on output.
|
169
249
|
|
250
|
+
##
|
251
|
+
# :method: preferred_citation
|
252
|
+
# :call-seq:
|
253
|
+
# preferred_citation -> Reference
|
254
|
+
#
|
255
|
+
# Return the preferred citation for this citation.
|
256
|
+
|
257
|
+
##
|
258
|
+
# :method: preferred_citation=
|
259
|
+
# :call-seq:
|
260
|
+
# preferred_citation = Reference
|
261
|
+
#
|
262
|
+
# Replace the preferred citation for this citation.
|
263
|
+
|
170
264
|
##
|
171
265
|
# :method: references
|
172
266
|
# :call-seq:
|
data/lib/cff/model_part.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
#
|
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.
|
data/lib/cff/person.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
#
|
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.
|
@@ -17,26 +19,58 @@ module CFF
|
|
17
19
|
|
18
20
|
# A Person represents a person in a CITATION.cff file. A Person might have a
|
19
21
|
# number of roles, such as author, contact, editor, etc.
|
22
|
+
#
|
23
|
+
# Person implements all of the fields listed in the
|
24
|
+
# [CFF standard](https://citation-file-format.github.io/). All fields
|
25
|
+
# are simple strings and can be set as such. A field which has not been set
|
26
|
+
# will return the empty string. The simple fields are (with defaults in
|
27
|
+
# parentheses):
|
28
|
+
#
|
29
|
+
# * `address`
|
30
|
+
# * `affiliation`
|
31
|
+
# * `alias`
|
32
|
+
# * `city`
|
33
|
+
# * `country`
|
34
|
+
# * `email`
|
35
|
+
# * `family_names`
|
36
|
+
# * `fax`
|
37
|
+
# * `given_names`
|
38
|
+
# * `name_particle`
|
39
|
+
# * `name_suffix`
|
40
|
+
# * `orcid`
|
41
|
+
# * `post_code`
|
42
|
+
# * `region`
|
43
|
+
# * `tel`
|
44
|
+
# * `website`
|
20
45
|
class Person < ModelPart
|
21
46
|
|
22
47
|
ALLOWED_FIELDS = [
|
23
|
-
'address', 'affiliation', '
|
24
|
-
'fax', 'given-names', 'name-particle', 'name-suffix',
|
25
|
-
'post-code', 'region', 'tel', 'website'
|
48
|
+
'address', 'affiliation', 'alias', 'city', 'country', 'email',
|
49
|
+
'family-names', 'fax', 'given-names', 'name-particle', 'name-suffix',
|
50
|
+
'orcid', 'post-code', 'region', 'tel', 'website'
|
26
51
|
].freeze # :nodoc:
|
27
52
|
|
28
53
|
# :call-seq:
|
54
|
+
# new -> Person
|
55
|
+
# new { |person| block } -> Person
|
29
56
|
# new(given_name, family_name) -> Person
|
57
|
+
# new(given_name, family_name) { |person| block } -> Person
|
30
58
|
#
|
31
|
-
# Create a new Person with the supplied given and family names.
|
32
|
-
def initialize(param, *more)
|
59
|
+
# Create a new Person with the optionally supplied given and family names.
|
60
|
+
def initialize(param = nil, *more)
|
33
61
|
if param.is_a?(Hash)
|
34
62
|
@fields = param
|
63
|
+
@fields.default = ''
|
35
64
|
else
|
36
65
|
@fields = Hash.new('')
|
37
|
-
|
38
|
-
|
66
|
+
|
67
|
+
unless param.nil?
|
68
|
+
@fields['family-names'] = more[0]
|
69
|
+
@fields['given-names'] = param
|
70
|
+
end
|
39
71
|
end
|
72
|
+
|
73
|
+
yield self if block_given?
|
40
74
|
end
|
41
75
|
end
|
42
76
|
end
|