cff 0.8.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.
- checksums.yaml +4 -4
- data/CHANGES.md +91 -0
- data/CITATION.cff +62 -5
- data/CONTRIBUTING.md +71 -0
- data/LICENCE +1 -1
- data/README.md +57 -17
- 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 +14 -9
- data/lib/cff/file.rb +101 -31
- 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 +14 -8
- data/lib/cff/{model.rb → index.rb} +73 -66
- data/lib/cff/licensable.rb +4 -5
- data/lib/cff/model_part.rb +46 -10
- data/lib/cff/person.rb +8 -10
- data/lib/cff/reference.rb +76 -112
- data/lib/cff/schema.rb +23 -0
- data/lib/{schema → cff/schemas}/1.2.0.json +1 -1
- data/lib/cff/util.rb +63 -6
- data/lib/cff/validatable.rb +13 -13
- 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/entity.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 Entity can represent different types of entities, e.g., a publishing
|
21
23
|
# company, or conference. Like a Person, an Entity might have a number of
|
22
24
|
# roles, such as author, contact, editor, etc.
|
@@ -28,11 +30,12 @@ module CFF
|
|
28
30
|
# parentheses):
|
29
31
|
#
|
30
32
|
# * `address`
|
33
|
+
# * `alias`
|
31
34
|
# * `city`
|
32
35
|
# * `country`
|
33
|
-
# * `email`
|
34
36
|
# * `date_end` - *Note:* returns a `Date` object
|
35
37
|
# * `date_start` - *Note:* returns a `Date` object
|
38
|
+
# * `email`
|
36
39
|
# * `fax`
|
37
40
|
# * `location`
|
38
41
|
# * `name`
|
@@ -42,11 +45,9 @@ module CFF
|
|
42
45
|
# * `tel`
|
43
46
|
# * `website`
|
44
47
|
class Entity < ModelPart
|
48
|
+
ALLOWED_FIELDS = SCHEMA_FILE['definitions']['entity']['properties'].keys.freeze # :nodoc:
|
45
49
|
|
46
|
-
|
47
|
-
'address', 'city', 'country', 'email', 'date-end', 'date-start', 'fax',
|
48
|
-
'location', 'name', 'orcid', 'post-code', 'region', 'tel', 'website'
|
49
|
-
].freeze # :nodoc:
|
50
|
+
attr_date :date_end, :date_start
|
50
51
|
|
51
52
|
# :call-seq:
|
52
53
|
# new(name) -> Entity
|
@@ -54,37 +55,16 @@ module CFF
|
|
54
55
|
#
|
55
56
|
# Create a new Entity with the supplied name.
|
56
57
|
def initialize(param)
|
58
|
+
super()
|
59
|
+
|
57
60
|
if param.is_a?(Hash)
|
58
61
|
@fields = param
|
59
|
-
@fields.default = ''
|
60
62
|
else
|
61
|
-
@fields =
|
63
|
+
@fields = {}
|
62
64
|
@fields['name'] = param
|
63
65
|
end
|
64
66
|
|
65
67
|
yield self if block_given?
|
66
68
|
end
|
67
|
-
|
68
|
-
# :call-seq:
|
69
|
-
# date_end = date
|
70
|
-
#
|
71
|
-
# Set the `date-end` field. If a non-Date object is passed in it will
|
72
|
-
# be parsed into a Date.
|
73
|
-
def date_end=(date)
|
74
|
-
date = Date.parse(date) unless date.is_a?(Date)
|
75
|
-
|
76
|
-
@fields['date-end'] = date
|
77
|
-
end
|
78
|
-
|
79
|
-
# :call-seq:
|
80
|
-
# date_start = date
|
81
|
-
#
|
82
|
-
# Set the `date-start` field. If a non-Date object is passed in it will
|
83
|
-
# be parsed into a Date.
|
84
|
-
def date_start=(date)
|
85
|
-
date = Date.parse(date) unless date.is_a?(Date)
|
86
|
-
|
87
|
-
@fields['date-start'] = date
|
88
|
-
end
|
89
69
|
end
|
90
70
|
end
|
data/lib/cff/errors.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.
|
@@ -16,30 +16,35 @@
|
|
16
16
|
|
17
17
|
##
|
18
18
|
module CFF
|
19
|
-
|
20
19
|
# Error is the base class for all errors raised by this library.
|
21
20
|
class Error < RuntimeError
|
22
|
-
|
23
21
|
def initialize(message = nil) # :nodoc:
|
24
22
|
super
|
25
23
|
end
|
26
24
|
end
|
27
25
|
|
28
|
-
# ValidationError is raised when a CFF file fails
|
29
|
-
#
|
30
|
-
#
|
26
|
+
# ValidationError is raised when a CFF file fails validation. It contains
|
27
|
+
# details of each failure that was detected by the underlying JsonSchema
|
28
|
+
# library, which is used to perform the validation.
|
29
|
+
#
|
30
|
+
# Additionally, the `invalid_filename` flag is used to indicate whether the
|
31
|
+
# CFF file is named correctly. This is only used when validating a File;
|
32
|
+
# validating a Index directly will not set this flag to `true`.
|
31
33
|
class ValidationError < Error
|
32
|
-
|
33
34
|
# The list of JsonSchema::ValidationErrors found by the validator.
|
34
35
|
attr_reader :errors
|
35
36
|
|
36
|
-
|
37
|
+
# If a File was validated, was its filename invalid?
|
38
|
+
attr_reader :invalid_filename
|
39
|
+
|
40
|
+
def initialize(errors, invalid_filename: false) # :nodoc:
|
37
41
|
super('Validation error')
|
38
42
|
@errors = errors
|
43
|
+
@invalid_filename = invalid_filename
|
39
44
|
end
|
40
45
|
|
41
46
|
def to_s # :nodoc:
|
42
|
-
"#{super}: #{@errors.join(' ')}"
|
47
|
+
"#{super}: (Invalid filename: #{@invalid_filename}) #{@errors.join(' ')}"
|
43
48
|
end
|
44
49
|
end
|
45
50
|
end
|
data/lib/cff/file.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,13 +14,23 @@
|
|
14
14
|
# See the License for the specific language governing permissions and
|
15
15
|
# limitations under the License.
|
16
16
|
|
17
|
+
require_relative 'errors'
|
18
|
+
require_relative 'index'
|
19
|
+
require_relative 'version'
|
20
|
+
|
21
|
+
require 'date'
|
22
|
+
require 'yaml'
|
23
|
+
|
17
24
|
##
|
18
25
|
module CFF
|
19
|
-
|
20
|
-
# File provides direct access to a CFF Model, with the addition of some
|
26
|
+
# File provides direct access to a CFF Index, with the addition of some
|
21
27
|
# filesystem utilities.
|
28
|
+
#
|
29
|
+
# To be a fully compliant and valid CFF file its filename should be
|
30
|
+
# 'CITATION.cff'. This class allows you to create files with any filename,
|
31
|
+
# and to validate the contents of those files independently of the preferred
|
32
|
+
# filename.
|
22
33
|
class File
|
23
|
-
|
24
34
|
# A comment to be inserted at the top of the resultant CFF file.
|
25
35
|
attr_reader :comment
|
26
36
|
|
@@ -33,27 +43,28 @@ module CFF
|
|
33
43
|
'Gem: https://rubygems.org/gems/cff',
|
34
44
|
'CFF: https://citation-file-format.github.io/'
|
35
45
|
].freeze # :nodoc:
|
46
|
+
CFF_VALID_FILENAME = 'CITATION.cff' # :nodoc:
|
36
47
|
|
37
48
|
# :call-seq:
|
38
49
|
# new(filename, title) -> File
|
39
|
-
# new(filename,
|
50
|
+
# new(filename, index) -> File
|
40
51
|
#
|
41
|
-
# Create a new File. Either a pre-existing
|
42
|
-
# with
|
52
|
+
# Create a new File. Either a pre-existing Index can be passed in or, as
|
53
|
+
# with Index itself, a title can be supplied to initalize a new File.
|
43
54
|
#
|
44
|
-
# All methods provided by
|
55
|
+
# All methods provided by Index are also available directly on File
|
45
56
|
# objects.
|
46
57
|
def initialize(filename, param, comment = CFF_COMMENT, create: false)
|
47
|
-
param =
|
58
|
+
param = Index.new(param) unless param.is_a?(Index)
|
48
59
|
|
49
60
|
@filename = filename
|
50
|
-
@
|
61
|
+
@index = param
|
51
62
|
@comment = comment
|
52
63
|
@dirty = create
|
53
64
|
end
|
54
65
|
|
55
66
|
# :call-seq:
|
56
|
-
# read(
|
67
|
+
# read(filename) -> File
|
57
68
|
#
|
58
69
|
# Read a file and parse it for subsequent manipulation.
|
59
70
|
def self.read(file)
|
@@ -66,8 +77,8 @@ module CFF
|
|
66
77
|
end
|
67
78
|
|
68
79
|
# :call-seq:
|
69
|
-
# open(
|
70
|
-
# open(
|
80
|
+
# open(filename) -> File
|
81
|
+
# open(filename) { |cff| block }
|
71
82
|
#
|
72
83
|
# With no associated block, File.open is a synonym for ::read. If the
|
73
84
|
# optional code block is given, it will be passed the opened file as an
|
@@ -97,31 +108,41 @@ module CFF
|
|
97
108
|
end
|
98
109
|
|
99
110
|
# :call-seq:
|
100
|
-
# validate(
|
111
|
+
# validate(filename, fail_on_filename: true) -> Array
|
101
112
|
#
|
102
113
|
# Read a file and return an array with the result. The result array is a
|
103
|
-
#
|
104
|
-
#
|
105
|
-
|
106
|
-
|
114
|
+
# three-element array, with `true`/`false` at index 0 to indicate
|
115
|
+
# pass/fail, an array of schema validation errors at index 1 (if any), and
|
116
|
+
# `true`/`false` at index 2 to indicate whether the filename passed/failed
|
117
|
+
# validation.
|
118
|
+
#
|
119
|
+
# You can choose whether filename validation failure should cause overall
|
120
|
+
# validation failure with the `fail_on_filename` parameter (default: true).
|
121
|
+
def self.validate(file, fail_on_filename: true)
|
122
|
+
File.read(file).validate(fail_on_filename: fail_on_filename)
|
107
123
|
end
|
108
124
|
|
109
125
|
# :call-seq:
|
110
|
-
# validate!(
|
126
|
+
# validate!(filename, fail_on_filename: true)
|
111
127
|
#
|
112
128
|
# Read a file and raise a ValidationError upon failure. If an error is
|
113
129
|
# raised it will contain the detected validation failures for further
|
114
130
|
# inspection.
|
115
|
-
|
116
|
-
|
131
|
+
#
|
132
|
+
# You can choose whether filename validation failure should cause overall
|
133
|
+
# validation failure with the `fail_on_filename` parameter (default: true).
|
134
|
+
def self.validate!(file, fail_on_filename: true)
|
135
|
+
File.read(file).validate!(fail_on_filename: fail_on_filename)
|
117
136
|
end
|
118
137
|
|
119
138
|
# :call-seq:
|
120
|
-
# write(
|
121
|
-
# write(
|
139
|
+
# write(filename, File)
|
140
|
+
# write(filename, Index)
|
141
|
+
# write(filename, yaml)
|
122
142
|
#
|
123
|
-
# Write the supplied
|
143
|
+
# Write the supplied File, Index or yaml string to `file`.
|
124
144
|
def self.write(file, cff, comment = '')
|
145
|
+
comment = cff.comment if cff.respond_to?(:comment)
|
125
146
|
cff = cff.to_yaml unless cff.is_a?(String)
|
126
147
|
content = File.format_comment(comment) + cff[YAML_HEADER.length...-1]
|
127
148
|
|
@@ -129,11 +150,56 @@ module CFF
|
|
129
150
|
end
|
130
151
|
|
131
152
|
# :call-seq:
|
132
|
-
#
|
153
|
+
# validate(fail_fast: false, fail_on_filename: true) -> Array
|
154
|
+
#
|
155
|
+
# Validate this file and return an array with the result. The result array
|
156
|
+
# is a three-element array, with `true`/`false` at index 0 to indicate
|
157
|
+
# pass/fail, an array of schema validation errors at index 1 (if any), and
|
158
|
+
# `true`/`false` at index 2 to indicate whether the filename passed/failed
|
159
|
+
# validation.
|
160
|
+
#
|
161
|
+
# You can choose whether filename validation failure should cause overall
|
162
|
+
# validation failure with the `fail_on_filename` parameter (default: true).
|
163
|
+
def validate(fail_fast: false, fail_on_filename: true)
|
164
|
+
valid_filename = (::File.basename(@filename) == CFF_VALID_FILENAME)
|
165
|
+
result = (@index.validate(fail_fast: fail_fast) << valid_filename)
|
166
|
+
result[0] &&= valid_filename if fail_on_filename
|
167
|
+
|
168
|
+
result
|
169
|
+
end
|
170
|
+
|
171
|
+
# :call-seq:
|
172
|
+
# validate!(fail_fast: false, fail_on_filename: true)
|
133
173
|
#
|
134
|
-
#
|
135
|
-
|
136
|
-
|
174
|
+
# Validate this file and raise a ValidationError upon failure. If an error
|
175
|
+
# is raised it will contain the detected validation failures for further
|
176
|
+
# inspection.
|
177
|
+
#
|
178
|
+
# You can choose whether filename validation failure should cause overall
|
179
|
+
# validation failure with the `fail_on_filename` parameter (default: true).
|
180
|
+
def validate!(fail_fast: false, fail_on_filename: true)
|
181
|
+
result = validate(
|
182
|
+
fail_fast: fail_fast, fail_on_filename: fail_on_filename
|
183
|
+
)
|
184
|
+
return if result[0]
|
185
|
+
|
186
|
+
raise ValidationError.new(result[1], invalid_filename: !result[2])
|
187
|
+
end
|
188
|
+
|
189
|
+
# :call-seq:
|
190
|
+
# write(save_as: filename)
|
191
|
+
#
|
192
|
+
# Write this CFF File. The `save_as` parameter can be used to save a new
|
193
|
+
# copy of this CFF File under a different filename, leaving the original
|
194
|
+
# file untouched. If `save_as` is used then the internal filename of the
|
195
|
+
# File will be updated to the supplied filename.
|
196
|
+
def write(save_as: nil)
|
197
|
+
unless save_as.nil?
|
198
|
+
@filename = save_as
|
199
|
+
@dirty = true
|
200
|
+
end
|
201
|
+
|
202
|
+
File.write(@filename, @index, @comment) if @dirty
|
137
203
|
@dirty = false
|
138
204
|
end
|
139
205
|
|
@@ -156,17 +222,21 @@ module CFF
|
|
156
222
|
@comment = comment
|
157
223
|
end
|
158
224
|
|
225
|
+
def to_yaml # :nodoc:
|
226
|
+
@index.to_yaml
|
227
|
+
end
|
228
|
+
|
159
229
|
def method_missing(name, *args) # :nodoc:
|
160
|
-
if @
|
230
|
+
if @index.respond_to?(name)
|
161
231
|
@dirty = true if name.to_s.end_with?('=') # Remove to_s when Ruby >2.6.
|
162
|
-
@
|
232
|
+
@index.send(name, *args)
|
163
233
|
else
|
164
234
|
super
|
165
235
|
end
|
166
236
|
end
|
167
237
|
|
168
238
|
def respond_to_missing?(name, *all) # :nodoc:
|
169
|
-
@
|
239
|
+
@index.respond_to?(name, *all)
|
170
240
|
end
|
171
241
|
|
172
242
|
def self.format_comment(comment) # :nodoc:
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2018-2022 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
|
+
require_relative 'apalike'
|
18
|
+
require_relative 'bibtex'
|
19
|
+
|
20
|
+
##
|
21
|
+
module CFF
|
22
|
+
module Formatters # :nodoc:
|
23
|
+
register_formatter(APALike)
|
24
|
+
register_formatter(BibTeX)
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright (c) 2018-2022 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
|
+
require_relative 'formatter'
|
18
|
+
|
19
|
+
##
|
20
|
+
module CFF
|
21
|
+
module Formatters # :nodoc:
|
22
|
+
# Generates an APALIKE citation string
|
23
|
+
class APALike < Formatter # :nodoc:
|
24
|
+
def self.format(model:, preferred_citation: true) # rubocop:disable Metrics/AbcSize
|
25
|
+
model = select_and_check_model(model, preferred_citation)
|
26
|
+
return if model.nil?
|
27
|
+
|
28
|
+
output = []
|
29
|
+
output << combine_authors(
|
30
|
+
model.authors.map { |author| format_author(author) }
|
31
|
+
)
|
32
|
+
|
33
|
+
date = month_and_year_from_model(model)
|
34
|
+
output << "(#{date})" unless date.empty?
|
35
|
+
|
36
|
+
version = " (Version #{model.version})" unless model.version.to_s.empty?
|
37
|
+
output << "#{model.title}#{version}#{type_label(model)}"
|
38
|
+
output << publication_data_from_model(model)
|
39
|
+
output << url(model)
|
40
|
+
|
41
|
+
output.reject(&:empty?).join('. ')
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.publication_data_from_model(model) # rubocop:disable Metrics
|
45
|
+
case model.type
|
46
|
+
when 'article'
|
47
|
+
[
|
48
|
+
model.journal,
|
49
|
+
volume_from_model(model),
|
50
|
+
pages_from_model(model, dash: '–'),
|
51
|
+
note_from_model(model) || ''
|
52
|
+
].reject(&:empty?).join(', ')
|
53
|
+
when 'book'
|
54
|
+
model.publisher.empty? ? '' : model.publisher.name
|
55
|
+
when 'conference-paper'
|
56
|
+
[
|
57
|
+
model.collection_title,
|
58
|
+
volume_from_model(model),
|
59
|
+
pages_from_model(model, dash: '–')
|
60
|
+
].reject(&:empty?).join(', ')
|
61
|
+
when 'report'
|
62
|
+
if model.institution.empty?
|
63
|
+
model.authors.first.affiliation
|
64
|
+
else
|
65
|
+
model.institution.name
|
66
|
+
end
|
67
|
+
when 'phdthesis'
|
68
|
+
type_and_school_from_model(model, 'Doctoral dissertation')
|
69
|
+
when 'mastersthesis'
|
70
|
+
type_and_school_from_model(model, "Master's thesis")
|
71
|
+
when 'unpublished'
|
72
|
+
note_from_model(model) || ''
|
73
|
+
else
|
74
|
+
''
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.type_and_school_from_model(model, type)
|
79
|
+
type = model.thesis_type == '' ? type : model.thesis_type
|
80
|
+
school = model.institution.empty? ? model.authors.first.affiliation : model.institution.name
|
81
|
+
"[#{type}, #{school}]"
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.volume_from_model(model)
|
85
|
+
issue = model.issue.to_s.empty? ? '' : "(#{model.issue})"
|
86
|
+
model.volume.to_s.empty? ? '' : "#{model.volume}#{issue}"
|
87
|
+
end
|
88
|
+
|
89
|
+
# If we're citing a conference paper, try and use the date of the
|
90
|
+
# conference. Otherwise use the specified month and year, or the date
|
91
|
+
# of release.
|
92
|
+
def self.month_and_year_from_model(model)
|
93
|
+
if model.type == 'conference-paper' && !model.conference.empty?
|
94
|
+
start = model.conference.date_start
|
95
|
+
unless start == ''
|
96
|
+
finish = model.conference.date_end
|
97
|
+
return month_and_year_from_date(start)[1] if finish == '' || start >= finish
|
98
|
+
|
99
|
+
return date_range(start, finish)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
super[1]
|
104
|
+
end
|
105
|
+
|
106
|
+
def self.date_range(start, finish)
|
107
|
+
start_str = '%Y, %B %-d'
|
108
|
+
finish_str = '%-d'
|
109
|
+
finish_str = "%B #{finish_str}" unless start.month == finish.month
|
110
|
+
finish_str = "%Y, #{finish_str}" unless start.year == finish.year
|
111
|
+
|
112
|
+
"#{start.strftime(start_str)}–#{finish.strftime(finish_str)}"
|
113
|
+
end
|
114
|
+
|
115
|
+
# Prefer a DOI over the other URI options.
|
116
|
+
def self.url(model)
|
117
|
+
model.doi.empty? ? super : "https://doi.org/#{model.doi}"
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.type_label(model)
|
121
|
+
return ' [Data set]' if model.type.include?('data')
|
122
|
+
return ' [Conference paper]' if model.type.include?('conference')
|
123
|
+
return '' if model.is_a?(Reference) && !model.type.include?('software')
|
124
|
+
|
125
|
+
' [Computer software]'
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.combine_authors(authors)
|
129
|
+
return authors[0].chomp('.') if authors.length == 1
|
130
|
+
|
131
|
+
"#{authors[0..-2].join(', ')}, & #{authors[-1]}".chomp('.')
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.format_author(author)
|
135
|
+
return author.name if author.is_a?(Entity)
|
136
|
+
|
137
|
+
particle =
|
138
|
+
author.name_particle.empty? ? '' : "#{author.name_particle} "
|
139
|
+
suffix = author.name_suffix.empty? ? '.' : "., #{author.name_suffix}"
|
140
|
+
|
141
|
+
"#{particle}#{author.family_names}, #{initials(author.given_names)}#{suffix}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|