cff 0.9.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 +73 -0
- data/CITATION.cff +3 -3
- data/CONTRIBUTING.md +71 -0
- data/LICENCE +1 -1
- data/README.md +27 -21
- 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 +2 -5
- data/lib/cff/file.rb +23 -18
- 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 +13 -8
- data/lib/cff/{model.rb → index.rb} +65 -81
- data/lib/cff/licensable.rb +3 -3
- data/lib/cff/model_part.rb +46 -10
- data/lib/cff/person.rb +8 -10
- data/lib/cff/reference.rb +55 -114
- data/lib/cff/schema.rb +23 -0
- data/lib/{schema → cff/schemas}/1.2.0.json +0 -0
- data/lib/cff/util.rb +63 -6
- data/lib/cff/validatable.rb +12 -11
- 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
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
|
@@ -29,11 +31,13 @@ module CFF
|
|
29
31
|
# * `type`
|
30
32
|
# * `value`
|
31
33
|
class Identifier < ModelPart
|
32
|
-
|
33
|
-
|
34
|
+
ALLOWED_FIELDS = # :nodoc:
|
35
|
+
SCHEMA_FILE['definitions']['identifier']['anyOf'].first['properties'].keys.dup.freeze
|
34
36
|
|
35
37
|
# The [defined set of identifier types](https://github.com/citation-file-format/citation-file-format/blob/main/README.md#identifier-type-strings).
|
36
|
-
IDENTIFIER_TYPES = ['
|
38
|
+
IDENTIFIER_TYPES = SCHEMA_FILE['definitions']['identifier']['anyOf'].map do |id|
|
39
|
+
id['properties']['type']['enum'].first
|
40
|
+
end.freeze
|
37
41
|
|
38
42
|
# :call-seq:
|
39
43
|
# new -> Identifier
|
@@ -44,15 +48,16 @@ module CFF
|
|
44
48
|
# Create a new Identifier with the optionally supplied type and value.
|
45
49
|
# If the supplied type is invalid, then neither the type or value are set.
|
46
50
|
def initialize(param = nil, *more)
|
51
|
+
super()
|
52
|
+
|
47
53
|
if param.is_a?(Hash)
|
48
54
|
@fields = param
|
49
|
-
@fields.default = ''
|
50
55
|
else
|
51
|
-
@fields =
|
56
|
+
@fields = {}
|
52
57
|
|
53
58
|
unless param.nil?
|
54
59
|
self.type = param
|
55
|
-
@fields['value'] = more[0] unless @fields['type'].
|
60
|
+
@fields['value'] = more[0] unless @fields['type'].nil?
|
56
61
|
end
|
57
62
|
end
|
58
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,63 +55,63 @@ 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
|
-
# read(String) ->
|
99
|
+
# read(String) -> Index
|
88
100
|
#
|
89
|
-
# Read a CFF
|
90
|
-
def self.read(
|
91
|
-
new(YAML.safe_load(
|
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]))
|
92
104
|
end
|
93
105
|
|
94
106
|
# :call-seq:
|
95
|
-
# open(String) ->
|
96
|
-
# open(String) { |cff| block } ->
|
107
|
+
# open(String) -> Index
|
108
|
+
# open(String) { |cff| block } -> Index
|
97
109
|
#
|
98
|
-
# With no associated block,
|
99
|
-
# optional code block is given, it will be passed the parsed
|
100
|
-
# argument and the
|
101
|
-
def self.open(
|
102
|
-
cff =
|
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)
|
103
115
|
|
104
116
|
yield cff if block_given?
|
105
117
|
|
@@ -107,61 +119,33 @@ module CFF
|
|
107
119
|
end
|
108
120
|
|
109
121
|
# :call-seq:
|
110
|
-
#
|
122
|
+
# type = type
|
111
123
|
#
|
112
|
-
#
|
113
|
-
#
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
@fields['
|
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)
|
118
130
|
end
|
119
131
|
|
120
132
|
def to_yaml # :nodoc:
|
121
133
|
YAML.dump fields, line_width: -1, indentation: 2
|
122
134
|
end
|
123
135
|
|
124
|
-
# :call-seq:
|
125
|
-
# to_apalike(preferred_citation: true) -> String
|
126
|
-
#
|
127
|
-
# Output this Model in an APA-like format. Setting
|
128
|
-
# `preferred_citation: true` will honour the `preferred_citation` field in
|
129
|
-
# the model if one is present (default).
|
130
|
-
#
|
131
|
-
# *Note:* This method assumes that this Model is valid when called.
|
132
|
-
def to_apalike(preferred_citation: true)
|
133
|
-
CFF::ApaFormatter.format(
|
134
|
-
model: self, preferred_citation: preferred_citation
|
135
|
-
)
|
136
|
-
end
|
137
|
-
|
138
|
-
# :call-seq:
|
139
|
-
# to_bibtex(preferred_citation: true) -> String
|
140
|
-
#
|
141
|
-
# Output this Model in BibTeX format. Setting
|
142
|
-
# `preferred_citation: true` will honour the `preferred_citation` field in
|
143
|
-
# the model if one is present (default).
|
144
|
-
#
|
145
|
-
# *Note:* This method assumes that this Model is valid when called.
|
146
|
-
def to_bibtex(preferred_citation: true)
|
147
|
-
CFF::BibtexFormatter.format(
|
148
|
-
model: self, preferred_citation: preferred_citation
|
149
|
-
)
|
150
|
-
end
|
151
|
-
|
152
136
|
private
|
153
137
|
|
154
138
|
def fields
|
155
139
|
%w[authors contact identifiers references].each do |field|
|
156
|
-
normalize_modelpart_array!(@fields[field])
|
140
|
+
Util.normalize_modelpart_array!(@fields[field])
|
157
141
|
end
|
158
142
|
|
159
|
-
fields_to_hash(@fields)
|
143
|
+
Util.fields_to_hash(@fields)
|
160
144
|
end
|
161
145
|
|
162
|
-
def
|
163
|
-
build_actor_collection!(fields['authors'] || [])
|
164
|
-
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'] || [])
|
165
149
|
(fields['identifiers'] || []).map! do |i|
|
166
150
|
Identifier.new(i)
|
167
151
|
end
|
@@ -172,7 +156,7 @@ module CFF
|
|
172
156
|
Reference.new(fields['preferred-citation'])
|
173
157
|
|
174
158
|
# Only attempt an update of the `cff-version` field if it is present.
|
175
|
-
fields['cff-version'] &&= update_cff_version(fields['cff-version'])
|
159
|
+
fields['cff-version'] &&= Util.update_cff_version(fields['cff-version'])
|
176
160
|
|
177
161
|
fields
|
178
162
|
end
|
@@ -191,7 +175,7 @@ module CFF
|
|
191
175
|
# list, use:
|
192
176
|
#
|
193
177
|
# ```
|
194
|
-
#
|
178
|
+
# index.authors << author
|
195
179
|
# ```
|
196
180
|
#
|
197
181
|
# Authors can be a Person or Entity.
|
@@ -214,7 +198,7 @@ module CFF
|
|
214
198
|
# list, use:
|
215
199
|
#
|
216
200
|
# ```
|
217
|
-
#
|
201
|
+
# index.contact << contact
|
218
202
|
# ```
|
219
203
|
#
|
220
204
|
# Contacts can be a Person or Entity.
|
@@ -237,7 +221,7 @@ module CFF
|
|
237
221
|
# the list, use:
|
238
222
|
#
|
239
223
|
# ```
|
240
|
-
#
|
224
|
+
# index.identifiers << identifier
|
241
225
|
# ```
|
242
226
|
|
243
227
|
##
|
@@ -256,7 +240,7 @@ module CFF
|
|
256
240
|
# list, use:
|
257
241
|
#
|
258
242
|
# ```
|
259
|
-
#
|
243
|
+
# index.keywords << keyword
|
260
244
|
# ```
|
261
245
|
#
|
262
246
|
# Keywords will be converted to Strings on output.
|
@@ -293,7 +277,7 @@ module CFF
|
|
293
277
|
# list, use:
|
294
278
|
#
|
295
279
|
# ```
|
296
|
-
#
|
280
|
+
# index.references << reference
|
297
281
|
# ```
|
298
282
|
|
299
283
|
##
|
data/lib/cff/licensable.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,12 +14,12 @@
|
|
14
14
|
# See the License for the specific language governing permissions and
|
15
15
|
# limitations under the License.
|
16
16
|
|
17
|
+
require_relative 'schema'
|
18
|
+
|
17
19
|
##
|
18
20
|
module CFF
|
19
|
-
|
20
21
|
# Functionality to add licence(s) to parts of the CFF model.
|
21
22
|
module Licensable
|
22
|
-
|
23
23
|
LICENSES = SCHEMA_FILE['definitions']['license-enum']['enum'].dup.freeze # :nodoc:
|
24
24
|
|
25
25
|
# :call-seq:
|
data/lib/cff/model_part.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,18 +14,16 @@
|
|
14
14
|
# See the License for the specific language governing permissions and
|
15
15
|
# limitations under the License.
|
16
16
|
|
17
|
+
require 'date'
|
18
|
+
|
17
19
|
##
|
18
20
|
module CFF
|
19
|
-
|
20
|
-
#
|
21
|
-
# This includes Model, Person, Entity and Reference.
|
21
|
+
# ModelPart is the superclass of anything that makes up part of the CFF Index.
|
22
|
+
# This includes Index, Person, Entity and Reference.
|
22
23
|
#
|
23
|
-
# ModelPart
|
24
|
+
# ModelPart provides only one method for the public API: `empty?`.
|
24
25
|
class ModelPart
|
25
|
-
|
26
26
|
# :stopdoc:
|
27
|
-
include Util
|
28
|
-
|
29
27
|
attr_reader :fields
|
30
28
|
|
31
29
|
def method_missing(name, *args)
|
@@ -35,7 +33,7 @@ module CFF
|
|
35
33
|
if n.end_with?('=')
|
36
34
|
@fields[n.chomp('=')] = args[0] || ''
|
37
35
|
else
|
38
|
-
@fields[n]
|
36
|
+
@fields[n].nil? ? '' : @fields[n]
|
39
37
|
end
|
40
38
|
end
|
41
39
|
|
@@ -43,7 +41,45 @@ module CFF
|
|
43
41
|
n = method_to_field(name.id2name)
|
44
42
|
self.class::ALLOWED_FIELDS.include?(n.chomp('=')) || super
|
45
43
|
end
|
46
|
-
|
47
44
|
# :startdoc:
|
45
|
+
|
46
|
+
# :call-seq:
|
47
|
+
# empty? -> false
|
48
|
+
#
|
49
|
+
# Define `empty?` for CFF classes so that they can be tested in the
|
50
|
+
# same way as strings and arrays.
|
51
|
+
#
|
52
|
+
# This always returns `false` because CFF classes always return something
|
53
|
+
# from all of their methods.
|
54
|
+
def empty?
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.attr_date(*symbols) # :nodoc:
|
59
|
+
symbols.each do |symbol|
|
60
|
+
field = symbol.to_s.tr('_', '-')
|
61
|
+
|
62
|
+
class_eval(
|
63
|
+
# def date_end=(date)
|
64
|
+
# date = (date.is_a?(Date) ? date.dup : Date.parse(date))
|
65
|
+
#
|
66
|
+
# @fields['date-end'] = date
|
67
|
+
# end
|
68
|
+
<<-END_SETTER, __FILE__, __LINE__ + 1
|
69
|
+
def #{symbol}=(date)
|
70
|
+
date = (date.is_a?(Date) ? date.dup : Date.parse(date))
|
71
|
+
|
72
|
+
@fields['#{field}'] = date
|
73
|
+
end
|
74
|
+
END_SETTER
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def method_to_field(name)
|
82
|
+
name.tr('_', '-')
|
83
|
+
end
|
48
84
|
end
|
49
85
|
end
|
data/lib/cff/person.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
|
# A Person represents a person in a CITATION.cff file. A Person might have a
|
21
23
|
# number of roles, such as author, contact, editor, etc.
|
22
24
|
#
|
@@ -43,12 +45,7 @@ module CFF
|
|
43
45
|
# * `tel`
|
44
46
|
# * `website`
|
45
47
|
class Person < ModelPart
|
46
|
-
|
47
|
-
ALLOWED_FIELDS = [
|
48
|
-
'address', 'affiliation', 'alias', 'city', 'country', 'email',
|
49
|
-
'family-names', 'fax', 'given-names', 'name-particle', 'name-suffix',
|
50
|
-
'orcid', 'post-code', 'region', 'tel', 'website'
|
51
|
-
].freeze # :nodoc:
|
48
|
+
ALLOWED_FIELDS = SCHEMA_FILE['definitions']['person']['properties'].keys.freeze # :nodoc:
|
52
49
|
|
53
50
|
# :call-seq:
|
54
51
|
# new -> Person
|
@@ -58,11 +55,12 @@ module CFF
|
|
58
55
|
#
|
59
56
|
# Create a new Person with the optionally supplied given and family names.
|
60
57
|
def initialize(param = nil, *more)
|
58
|
+
super()
|
59
|
+
|
61
60
|
if param.is_a?(Hash)
|
62
61
|
@fields = param
|
63
|
-
@fields.default = ''
|
64
62
|
else
|
65
|
-
@fields =
|
63
|
+
@fields = {}
|
66
64
|
|
67
65
|
unless param.nil?
|
68
66
|
@fields['family-names'] = more[0]
|