cff 0.9.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.
@@ -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
@@ -29,11 +31,13 @@ module CFF
29
31
  # * `type`
30
32
  # * `value`
31
33
  class Identifier < ModelPart
32
-
33
- ALLOWED_FIELDS = ['description', 'type', 'value'].freeze # :nodoc:
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 = ['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
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 = Hash.new('')
56
+ @fields = {}
52
57
 
53
58
  unless param.nil?
54
59
  self.type = param
55
- @fields['value'] = more[0] unless @fields['type'].empty?
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-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,63 +55,63 @@ 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
- # read(String) -> Model
99
+ # read(String) -> Index
88
100
  #
89
- # Read a CFF Model from a String and parse it for subsequent manipulation.
90
- def self.read(model)
91
- new(YAML.safe_load(model, permitted_classes: [Date, Time]))
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) -> Model
96
- # open(String) { |cff| block } -> Model
107
+ # open(String) -> Index
108
+ # open(String) { |cff| block } -> Index
97
109
  #
98
- # With no associated block, Model.open is a synonym for ::read. If the
99
- # optional code block is given, it will be passed the parsed model as an
100
- # argument and the Model will be returned when the block terminates.
101
- def self.open(model)
102
- cff = Model.read(model)
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
- # date_released = date
122
+ # type = type
111
123
  #
112
- # Set the `date-released` field. If a non-Date object is passed in it will
113
- # be parsed into a Date.
114
- def date_released=(date)
115
- date = Date.parse(date) unless date.is_a?(Date)
116
-
117
- @fields['date-released'] = date
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 build_model(fields) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
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
- # model.authors << author
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
- # model.contact << contact
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
- # model.identifiers << identifier
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
- # model.keywords << keyword
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
- # model.references << reference
280
+ # index.references << reference
297
281
  # ```
298
282
 
299
283
  ##
@@ -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,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:
@@ -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,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
- # ModelPart is the superclass of anything that makes up part of the CFF Model.
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 does not provide any methods or fields for the public API.
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-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
  # 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 = Hash.new('')
63
+ @fields = {}
66
64
 
67
65
  unless param.nil?
68
66
  @fields['family-names'] = more[0]