cff 0.2.0 → 0.9.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,4 +1,6 @@
1
- # Copyright (c) 2018 Robert Haines.
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.
@@ -12,20 +14,21 @@
12
14
  # See the License for the specific language governing permissions and
13
15
  # limitations under the License.
14
16
 
15
- #
17
+ ##
16
18
  module CFF
17
19
 
18
- # :stopdoc:
20
+ # ModelPart is the superclass of anything that makes up part of the CFF Model.
21
+ # This includes Model, Person, Entity and Reference.
22
+ #
23
+ # ModelPart does not provide any methods or fields for the public API.
19
24
  class ModelPart
25
+
26
+ # :stopdoc:
20
27
  include Util
21
28
 
22
29
  attr_reader :fields
23
30
 
24
- def initialize(fields)
25
- @fields = fields
26
- end
27
-
28
- def method_missing(name, *args) # :nodoc:
31
+ def method_missing(name, *args)
29
32
  n = method_to_field(name.id2name)
30
33
  super unless self.class::ALLOWED_FIELDS.include?(n.chomp('='))
31
34
 
@@ -36,6 +39,11 @@ module CFF
36
39
  end
37
40
  end
38
41
 
42
+ def respond_to_missing?(name, *)
43
+ n = method_to_field(name.id2name)
44
+ self.class::ALLOWED_FIELDS.include?(n.chomp('=')) || super
45
+ end
46
+
47
+ # :startdoc:
39
48
  end
40
- # :startdoc:
41
49
  end
data/lib/cff/person.rb CHANGED
@@ -1,4 +1,6 @@
1
- # Copyright (c) 2018 Robert Haines.
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.
@@ -12,44 +14,63 @@
12
14
  # See the License for the specific language governing permissions and
13
15
  # limitations under the License.
14
16
 
15
- #
17
+ ##
16
18
  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',
24
- 'affiliation',
25
- 'city',
26
- 'country',
27
- 'email',
28
- 'family-names',
29
- 'fax',
30
- 'given-names',
31
- 'name-particle',
32
- 'name-suffix',
33
- 'orcid',
34
- 'post-code',
35
- 'region',
36
- 'tel',
37
- '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'
38
51
  ].freeze # :nodoc:
39
52
 
40
53
  # :call-seq:
54
+ # new -> Person
55
+ # new { |person| block } -> Person
41
56
  # new(given_name, family_name) -> Person
57
+ # new(given_name, family_name) { |person| block } -> Person
42
58
  #
43
- # Create a new Person with the supplied given and family names.
44
- def initialize(param, *more)
45
- if Hash === param
46
- super(param)
59
+ # Create a new Person with the optionally supplied given and family names.
60
+ def initialize(param = nil, *more)
61
+ if param.is_a?(Hash)
62
+ @fields = param
63
+ @fields.default = ''
47
64
  else
48
65
  @fields = Hash.new('')
49
- @fields['family-names'] = more[0]
50
- @fields['given-names'] = param
66
+
67
+ unless param.nil?
68
+ @fields['family-names'] = more[0]
69
+ @fields['given-names'] = param
70
+ end
51
71
  end
52
- end
53
72
 
73
+ yield self if block_given?
74
+ end
54
75
  end
55
76
  end
@@ -0,0 +1,564 @@
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
+ # Reference provides a reference pertaining to the software version or the
21
+ # software itself, e.g., a software paper describing the abstract concepts of
22
+ # the software, a paper describing an algorithm that has been implemented in
23
+ # the software version, etc.
24
+ #
25
+ # Reference implements all of the fields listed in the
26
+ # [CFF standard](https://citation-file-format.github.io/). Complex
27
+ # fields - `authors`, `contact`, `editors`, `editors_series`, `identifiers`,
28
+ # `keywords`, `languages`, `patent_states`, `recipients`, `senders` and
29
+ # `translators` - are documented below. All other fields are simple strings
30
+ # and can be set as such. A field which has not been set will return the
31
+ # empty string. The simple fields are (with defaults in parentheses):
32
+ #
33
+ # * `abbreviation`
34
+ # * `abstract`
35
+ # * `collection_doi`
36
+ # * `collection_title`
37
+ # * `collection_type`
38
+ # * `commit`
39
+ # * `conference`
40
+ # * `copyright`
41
+ # * `data-type`
42
+ # * `database`
43
+ # * `database_provider`
44
+ # * `date_accessed` - *Note:* returns a `Date` object
45
+ # * `date_downloaded` - *Note:* returns a `Date` object
46
+ # * `date_published` - *Note:* returns a `Date` object
47
+ # * `date_released` - *Note:* returns a `Date` object
48
+ # * `department`
49
+ # * `doi`
50
+ # * `edition`
51
+ # * `end`
52
+ # * `entry`
53
+ # * `filename`
54
+ # * `format`
55
+ # * `institution`
56
+ # * `isbn`
57
+ # * `issn`
58
+ # * `issue`
59
+ # * `issue_date` - *Note:* returns a `Date` object
60
+ # * `issue_title`
61
+ # * `journal`
62
+ # * `license` - *Note:* see documentation for `license =` below
63
+ # * `license_url`
64
+ # * `loc_end`
65
+ # * `loc_start`
66
+ # * `location`
67
+ # * `medium`
68
+ # * `month`
69
+ # * `nihmsid`
70
+ # * `notes`
71
+ # * `number`
72
+ # * `number_volumes`
73
+ # * `pages`
74
+ # * `pmcid`
75
+ # * `publisher`
76
+ # * `repository`
77
+ # * `repository_code`
78
+ # * `repository_artifact`
79
+ # * `scope`
80
+ # * `section`
81
+ # * `start`
82
+ # * `status` - *Note:* see documentation for `status =` below
83
+ # * `thesis_type`
84
+ # * `title`
85
+ # * `type` - *Note:* see documentation for `type =` below
86
+ # * `url`
87
+ # * `version`
88
+ # * `volume`
89
+ # * `volume_title`
90
+ # * `year`
91
+ # * `year_original`
92
+ class Reference < ModelPart
93
+
94
+ include Licensable
95
+
96
+ ALLOWED_FIELDS = [
97
+ 'abbreviation', 'abstract', 'authors', 'collection-doi',
98
+ 'collection-title', 'collection-type', 'commit', 'conference', 'contact',
99
+ 'copyright', 'data-type', 'database', 'database-provider',
100
+ 'date-accessed', 'date-downloaded', 'date-published', 'date-released',
101
+ 'department', 'doi', 'edition', 'editors', 'editors-series', 'end',
102
+ 'entry', 'filename', 'identifiers', 'institution', 'isbn', 'issn',
103
+ 'issue', 'issue-date', 'issue-title', 'journal', 'keywords', 'license',
104
+ 'license-url', 'loc-end', 'loc-start', 'location', 'medium', 'month',
105
+ 'nihmsid', 'notes', 'number', 'number-volumes', 'pages', 'patent-states',
106
+ 'pmcid', 'publisher', 'recipients', 'repository', 'repository-code',
107
+ 'repository-artifact', 'scope', 'section', 'senders', 'start', 'status',
108
+ 'thesis-type', 'title', 'translators', 'type', 'url', 'version',
109
+ 'volume', 'volume-title', 'year', 'year-original'
110
+ ].freeze # :nodoc:
111
+
112
+ # The [defined set of reference types](https://github.com/citation-file-format/citation-file-format#reference-types).
113
+ REFERENCE_TYPES = [
114
+ 'art', 'article', 'audiovisual', 'bill', 'blog', 'book', 'catalogue',
115
+ 'conference', 'conference-paper', 'data', 'database', 'dictionary',
116
+ 'edited-work', 'encyclopedia', 'film-broadcast', 'generic',
117
+ 'government-document', 'grant', 'hearing', 'historical-work',
118
+ 'legal-case', 'legal-rule', 'magazine-article', 'manual', 'map',
119
+ 'multimedia', 'music', 'newspaper-article', 'pamphlet', 'patent',
120
+ 'personal-communication', 'proceedings', 'report', 'serial', 'slides',
121
+ 'software', 'software-code', 'software-container', 'software-executable',
122
+ 'software-virtual-machine', 'sound-recording', 'standard', 'statute',
123
+ 'thesis', 'unpublished', 'video', 'website'
124
+ ].freeze
125
+
126
+ # The [defined set of reference status types](https://github.com/citation-file-format/citation-file-format#status-strings).
127
+ REFERENCE_STATUS_TYPES = [
128
+ 'abstract', 'advance-online', 'in-preparation', 'in-press',
129
+ 'pre-print', 'submitted'
130
+ ].freeze
131
+
132
+ # :call-seq:
133
+ # new(title) -> Reference
134
+ # new(title) { |ref| block } -> Reference
135
+ # new(title, type) -> Reference
136
+ # new(title, type) { |ref| block } -> Reference
137
+ #
138
+ # Create a new Reference with the supplied title and, optionally, type.
139
+ # If type is not given, or is not one of the
140
+ # [defined set of reference types](https://github.com/citation-file-format/citation-file-format#reference-types),
141
+ # 'generic' will be used by default.
142
+ def initialize(param, *more) # rubocop:disable Metrics/AbcSize
143
+ if param.is_a?(Hash)
144
+ @fields = build_model(param)
145
+ @fields.default = ''
146
+ else
147
+ @fields = Hash.new('')
148
+ type = more[0] &&= more[0].downcase
149
+ @fields['type'] = REFERENCE_TYPES.include?(type) ? type : 'generic'
150
+ @fields['title'] = param
151
+ end
152
+
153
+ [
154
+ 'authors', 'contact', 'editors', 'editors-series', 'identifiers',
155
+ 'keywords', 'patent-states', 'recipients', 'senders', 'translators'
156
+ ].each do |field|
157
+ @fields[field] = [] if @fields[field].empty?
158
+ end
159
+
160
+ yield self if block_given?
161
+ end
162
+
163
+ # :call-seq:
164
+ # from_cff(File, type: 'software') -> Reference
165
+ # from_cff(Model, type: 'software') -> Reference
166
+ #
167
+ # Create a Reference from another CFF File or Model. This is useful for
168
+ # easily adding a reference to something with its own CITATION.cff file
169
+ # already.
170
+ #
171
+ # This method assumes that the type of the Reference should be `software`,
172
+ # but this can be overridden with the `type` parameter.
173
+ def self.from_cff(model, type: 'software')
174
+ new(model.title, type) do |ref|
175
+ %w[
176
+ abstract authors contact commit date_released doi
177
+ identifiers keywords license license_url repository
178
+ repository_artifact repository_code url version
179
+ ].each do |field|
180
+ value = model.send(field)
181
+ ref.send("#{field}=", value.dup) unless value == ''
182
+ end
183
+ end
184
+ end
185
+
186
+ # :call-seq:
187
+ # add_language language
188
+ #
189
+ # Add a language to this Reference. Input is converted to the ISO 639-3
190
+ # three letter language code, so `GER` becomes `deu`, `french` becomes
191
+ # `fra` and `en` becomes `eng`.
192
+ def add_language(lang)
193
+ @fields['languages'] = [] if @fields['languages'].empty?
194
+ lang = LanguageList::LanguageInfo.find(lang)
195
+ return if lang.nil?
196
+
197
+ lang = lang.iso_639_3
198
+ @fields['languages'] << lang unless @fields['languages'].include? lang
199
+ end
200
+
201
+ # :call-seq:
202
+ # reset_languages
203
+ #
204
+ # Reset the list of languages for this Reference to be empty.
205
+ def reset_languages
206
+ @fields.delete('languages')
207
+ end
208
+
209
+ # :call-seq:
210
+ # languages -> Array
211
+ #
212
+ # Return the list of languages associated with this Reference.
213
+ def languages
214
+ @fields['languages'].empty? ? [] : @fields['languages'].dup
215
+ end
216
+
217
+ # :call-seq:
218
+ # date_accessed = date
219
+ #
220
+ # Set the `date-accessed` field. If a non-Date object is passed in it will
221
+ # be parsed into a Date.
222
+ def date_accessed=(date)
223
+ date = Date.parse(date) unless date.is_a?(Date)
224
+
225
+ @fields['date-accessed'] = date
226
+ end
227
+
228
+ # :call-seq:
229
+ # date_downloaded = date
230
+ #
231
+ # Set the `date-downloaded` field. If a non-Date object is passed in it will
232
+ # be parsed into a Date.
233
+ def date_downloaded=(date)
234
+ date = Date.parse(date) unless date.is_a?(Date)
235
+
236
+ @fields['date-downloaded'] = date
237
+ end
238
+
239
+ # :call-seq:
240
+ # date_published = date
241
+ #
242
+ # Set the `date-published` field. If a non-Date object is passed in it will
243
+ # be parsed into a Date.
244
+ def date_published=(date)
245
+ date = Date.parse(date) unless date.is_a?(Date)
246
+
247
+ @fields['date-published'] = date
248
+ end
249
+
250
+ # :call-seq:
251
+ # date_released = date
252
+ #
253
+ # Set the `date-released` field. If a non-Date object is passed in it will
254
+ # be parsed into a Date.
255
+ def date_released=(date)
256
+ date = Date.parse(date) unless date.is_a?(Date)
257
+
258
+ @fields['date-released'] = date
259
+ end
260
+
261
+ # Returns the format of this Reference.
262
+ #
263
+ # This method is explicitly defined to override the private format method
264
+ # that all objects seem to have.
265
+ def format # :nodoc:
266
+ @fields['format']
267
+ end
268
+
269
+ # Sets the format of this Reference.
270
+ #
271
+ # This method is explicitly defined to override the private format method
272
+ # that all objects seem to have.
273
+ def format=(fmt) # :nodoc:
274
+ @fields['format'] = fmt
275
+ end
276
+
277
+ # :call-seq:
278
+ # status = status
279
+ #
280
+ # Sets the status of this Reference. The status is restricted to a
281
+ # [defined set of status types](https://github.com/citation-file-format/citation-file-format#status-strings).
282
+ def status=(status)
283
+ status = status.downcase
284
+ @fields['status'] = status if REFERENCE_STATUS_TYPES.include?(status)
285
+ end
286
+
287
+ # :call-seq:
288
+ # type = type
289
+ #
290
+ # Sets the type of this Reference. The type is restricted to a
291
+ # [defined set of reference types](https://github.com/citation-file-format/citation-file-format#reference-types).
292
+ def type=(type)
293
+ type = type.downcase
294
+ @fields['type'] = type if REFERENCE_TYPES.include?(type)
295
+ end
296
+
297
+ # Override superclass #fields as References contain model parts too.
298
+ def fields # :nodoc:
299
+ [
300
+ 'authors', 'contact', 'editors', 'editors-series', 'identifiers',
301
+ 'recipients', 'senders', 'translators'
302
+ ].each do |field|
303
+ normalize_modelpart_array!(@fields[field])
304
+ end
305
+
306
+ fields_to_hash(@fields)
307
+ end
308
+
309
+ private
310
+
311
+ def build_model(fields) # :nodoc:
312
+ [
313
+ 'authors', 'contact', 'editors', 'editors-series', 'recipients',
314
+ 'senders', 'translators'
315
+ ].each do |field|
316
+ build_actor_collection!(fields[field]) if fields.include?(field)
317
+ end
318
+
319
+ [
320
+ 'conference', 'database-provider', 'institution', 'location',
321
+ 'publisher'
322
+ ].each do |field|
323
+ fields[field] &&= Entity.new(fields[field])
324
+ end
325
+
326
+ (fields['identifiers'] || []).map! do |i|
327
+ Identifier.new(i)
328
+ end
329
+
330
+ fields
331
+ end
332
+
333
+ public
334
+
335
+ # Some documentation of "hidden" methods is provided here, out of the
336
+ # way of the main class code.
337
+
338
+ ##
339
+ # :method: authors
340
+ # :call-seq:
341
+ # authors -> Array
342
+ #
343
+ # Return the list of authors for this Reference. To add an author to the
344
+ # list, use:
345
+ #
346
+ # ```
347
+ # reference.authors << author
348
+ # ```
349
+ #
350
+ # Authors can be a Person or Entity.
351
+
352
+ ##
353
+ # :method: authors=
354
+ # :call-seq:
355
+ # authors = array_of_authors -> Array
356
+ #
357
+ # Replace the list of authors for this reference.
358
+ #
359
+ # Authors can be a Person or Entity.
360
+
361
+ ##
362
+ # :method: contact
363
+ # :call-seq:
364
+ # contact -> Array
365
+ #
366
+ # Return the list of contacts for this Reference. To add a contact to the
367
+ # list, use:
368
+ #
369
+ # ```
370
+ # reference.contact << contact
371
+ # ```
372
+ #
373
+ # Contacts can be a Person or Entity.
374
+
375
+ ##
376
+ # :method: contact=
377
+ # :call-seq:
378
+ # contact = array_of_contacts -> Array
379
+ #
380
+ # Replace the list of contacts for this reference.
381
+ #
382
+ # Contacts can be a Person or Entity.
383
+
384
+ ##
385
+ # :method: editors
386
+ # :call-seq:
387
+ # editors -> Array
388
+ #
389
+ # Return the list of editors for this Reference. To add an editor to the
390
+ # list, use:
391
+ #
392
+ # ```
393
+ # reference.editors << editor
394
+ # ```
395
+ #
396
+ # An editor can be a Person or Entity.
397
+
398
+ ##
399
+ # :method: editors=
400
+ # :call-seq:
401
+ # editors = array_of_editors -> Array
402
+ #
403
+ # Replace the list of editors for this reference.
404
+ #
405
+ # Editors can be a Person or Entity.
406
+
407
+ ##
408
+ # :method: editors_series
409
+ # :call-seq:
410
+ # editors_series -> Array
411
+ #
412
+ # Return the list of series editors for this Reference. To add a series
413
+ # editor to the list, use:
414
+ #
415
+ # ```
416
+ # reference.editors_series << editor
417
+ # ```
418
+ #
419
+ # An editor can be a Person or Entity.
420
+
421
+ ##
422
+ # :method: editors_series=
423
+ # :call-seq:
424
+ # editors_series = array_of_series_editors -> Array
425
+ #
426
+ # Replace the list of series editors for this reference.
427
+ #
428
+ # Series editors can be a Person or Entity.
429
+
430
+ ##
431
+ # :method: identifiers
432
+ # :call-seq:
433
+ # identifiers -> Array
434
+ #
435
+ # Return the list of identifiers for this citation. To add a identifier to
436
+ # the list, use:
437
+ #
438
+ # ```
439
+ # model.identifiers << identifier
440
+ # ```
441
+
442
+ ##
443
+ # :method: identifiers=
444
+ # :call-seq:
445
+ # identifiers = array_of_identifiers -> Array
446
+ #
447
+ # Replace the list of identifiers for this citation.
448
+
449
+ ##
450
+ # :method: keywords
451
+ # :call-seq:
452
+ # keywords -> Array
453
+ #
454
+ # Return the list of keywords for this reference. To add a keyword to the
455
+ # list, use:
456
+ #
457
+ # ```
458
+ # model.keywords << keyword
459
+ # ```
460
+ #
461
+ # Keywords will be converted to Strings on output.
462
+
463
+ ##
464
+ # :method: keywords=
465
+ # :call-seq:
466
+ # keywords = array_of_keywords -> Array
467
+ #
468
+ # Replace the list of keywords for this reference.
469
+ #
470
+ # Keywords will be converted to Strings on output.
471
+
472
+ ##
473
+ # :method: patent_states
474
+ # :call-seq:
475
+ # patent_states -> Array
476
+ #
477
+ # Return the list of patent states for this reference. To add a patent
478
+ # state to the list, use:
479
+ #
480
+ # ```
481
+ # model.patent_states << patent_state
482
+ # ```
483
+ #
484
+ # Patent states will be converted to Strings on output.
485
+
486
+ ##
487
+ # :method: patent_states=
488
+ # :call-seq:
489
+ # patent_states = array_of_states -> Array
490
+ #
491
+ # Replace the list of patent states for this reference.
492
+ #
493
+ # Patent states will be converted to Strings on output.
494
+
495
+ ##
496
+ # :method: recipients
497
+ # :call-seq:
498
+ # recipients -> Array
499
+ #
500
+ # Return the list of recipients for this Reference. To add a recipient
501
+ # to the list, use:
502
+ #
503
+ # ```
504
+ # reference.recipients << recipient
505
+ # ```
506
+ #
507
+ # Recipients can be a Person or Entity.
508
+
509
+ ##
510
+ # :method: recipients=
511
+ # :call-seq:
512
+ # recipients = array_of_recipients -> Array
513
+ #
514
+ # Replace the list of recipients for this reference.
515
+ #
516
+ # Recipients can be a Person or Entity.
517
+
518
+ ##
519
+ # :method: senders
520
+ # :call-seq:
521
+ # senders -> Array
522
+ #
523
+ # Return the list of senders for this Reference. To add a sender to the
524
+ # list, use:
525
+ #
526
+ # ```
527
+ # reference.senders << sender
528
+ # ```
529
+ #
530
+ # Senders can be a Person or Entity.
531
+
532
+ ##
533
+ # :method: senders=
534
+ # :call-seq:
535
+ # senders = array_of_senders -> Array
536
+ #
537
+ # Replace the list of senders for this reference.
538
+ #
539
+ # Senders can be a Person or Entity.
540
+
541
+ ##
542
+ # :method: translators
543
+ # :call-seq:
544
+ # translators -> Array
545
+ #
546
+ # Return the list of translators for this Reference. To add a translator
547
+ # to the list, use:
548
+ #
549
+ # ```
550
+ # reference.translators << translator
551
+ # ```
552
+ #
553
+ # Translators can be a Person or Entity.
554
+
555
+ ##
556
+ # :method: translators=
557
+ # :call-seq:
558
+ # translators = array_of_translators -> Array
559
+ #
560
+ # Replace the list of translators for this reference.
561
+ #
562
+ # Translators can be a Person or Entity.
563
+ end
564
+ end