cff 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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]