cff 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 30519ae0956f0d4221822133c8d638170919f92a3c969af8fc9acf75146f1652
4
- data.tar.gz: d50b46821a6b7711cadfa8f89628afad29c1c06c0549756c7cbbe2f50fd38507
3
+ metadata.gz: c4c306916ccaa887479febca758c7d8d5e9272229ee9eedb88663649e3d6c15c
4
+ data.tar.gz: 798151267eba0eab166fc104660a5a87f15ecafdd336c0e9819ab9922a58b516
5
5
  SHA512:
6
- metadata.gz: cf2c30b86d41a0450d0ece7312fb25be248cc434de332178413aea306b67ad0a7a6d5a553c7cffcd971683a224c00727ef5ad815d0e73df5c06c9c8cc3faf723
7
- data.tar.gz: 9700c2ecf3fcc319bb04037081c653361c8062e35eb772881932bf7025679f5dc3d1907c042d841f2ba40b42efb1416a95a9ecff8361c02b0b74ea0c2f05b3c5
6
+ metadata.gz: 2834d89f3fac5714b9708cd4c15999c5df41c50bfc9bab3fa6b208cca46fff333b6d619fb7e84cb6faaec5a42219ae3a37db9830c2f788179a926d66e6ac1c33
7
+ data.tar.gz: 82bc7dffa78909c49c0c035ab6b8823852a1ce5e8c9486ce81f09d7a88fb530c09e2a0c486274d282ae963a737bc6cef272dd1be00509c2d65053a6ff1f2c827
data/CHANGES.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changes log for the Ruby CFF Library
2
2
 
3
+ ## Version 0.9.0
4
+
5
+ * Update to final released version of schema 1.2.0.
6
+ * Add `description` field to `Identifier`.
7
+ * Add `Model::read` to parse a CFF file from memory.
8
+ * Add `Model::open`. Same as `read` but can take a block.
9
+ * Override `File#to_yaml`.
10
+ * Allow `File::write` to write File objects.
11
+ * Add `save_as` parameter to `File#write`.
12
+ * Add `Reference::from_cff`.
13
+ * Update CITATION.cff file to reference CFF repo.
14
+ * Fix `File` docs to be explicit about filenames.
15
+ * Validate the filename of a CFF file.
16
+ * Surface the `fail_fast` options on the `File` validatation methods.
17
+ * Reduce json_schema dependency to ~0.20.0.
18
+ * Fix APA-like formatter when a reference is missing a volume.
19
+ * APA: don't emit journal data if there's no journal.
20
+
3
21
  ## Version 0.8.0
4
22
 
5
23
  * Add a comment field to the File class.
data/CITATION.cff CHANGED
@@ -1,4 +1,4 @@
1
- # This CITATION.cff file was created by ruby-cff (v 0.8.0).
1
+ # This CITATION.cff file was created by ruby-cff (v 0.9.0).
2
2
  # Gem: https://rubygems.org/gems/cff
3
3
  # CFF: https://citation-file-format.github.io/
4
4
 
@@ -15,12 +15,69 @@ authors:
15
15
  keywords:
16
16
  - ruby
17
17
  - credit
18
- - citation
18
+ - software citation
19
+ - research software
20
+ - software sustainability
19
21
  - metadata
20
- - cff
21
- version: 0.8.0
22
+ - citation file format
23
+ - CFF
24
+ version: 0.9.0
22
25
  doi: 10.5281/zenodo.1184077
23
- date-released: 2021-08-08
26
+ date-released: 2021-08-18
24
27
  license: Apache-2.0
25
28
  repository-artifact: https://rubygems.org/gems/cff
26
29
  repository-code: https://github.com/citation-file-format/ruby-cff
30
+ references:
31
+ - type: software
32
+ title: Citation File Format
33
+ authors:
34
+ - family-names: Druskat
35
+ given-names: Stephan
36
+ orcid: https://orcid.org/0000-0003-4925-7248
37
+ - family-names: Spaaks
38
+ given-names: Jurriaan H.
39
+ orcid: https://orcid.org/0000-0002-7064-4069
40
+ - family-names: Chue Hong
41
+ given-names: Neil
42
+ orcid: https://orcid.org/0000-0002-8876-7606
43
+ - family-names: Haines
44
+ given-names: Robert
45
+ orcid: https://orcid.org/0000-0002-9538-7919
46
+ - family-names: Baker
47
+ given-names: James
48
+ orcid: https://orcid.org/0000-0002-2682-6922
49
+ - family-names: Bliven
50
+ given-names: Spencer
51
+ orcid: https://orcid.org/0000-0002-1200-1698
52
+ email: spencer.bliven@gmail.com
53
+ - family-names: Willighagen
54
+ given-names: Egon
55
+ orcid: https://orcid.org/0000-0001-7542-0286
56
+ - family-names: Pérez-Suárez
57
+ given-names: David
58
+ orcid: https://orcid.org/0000-0003-0784-6909
59
+ website: https://dpshelio.github.io
60
+ - family-names: Konovalov
61
+ given-names: Alexander
62
+ orcid: https://orcid.org/0000-0001-5299-3292
63
+ identifiers:
64
+ - type: doi
65
+ value: 10.5281/zenodo.1003149
66
+ description: The concept DOI for the collection containing all versions of the Citation File Format.
67
+ - type: doi
68
+ value: 10.5281/zenodo.5171937
69
+ description: The versioned DOI for the version 1.2.0 of the Citation File Format.
70
+ keywords:
71
+ - citation file format
72
+ - CFF
73
+ - citation files
74
+ - software citation
75
+ - file format
76
+ - YAML
77
+ - software sustainability
78
+ - research software
79
+ - credit
80
+ abstract: CITATION.cff files are plain text files with human- and machine-readable citation information for software. Code developers can include them in their repositories to let others know how to correctly cite their software. This is the specification for the Citation File Format.
81
+ date-released: 2021-08-09
82
+ license: CC-BY-4.0
83
+ version: 1.2.0
data/README.md CHANGED
@@ -51,8 +51,8 @@ keywords:
51
51
  - ruby
52
52
  - credit
53
53
  - citation
54
- version: 0.8.0
55
- date-released: 2021-08-08
54
+ version: 0.9.0
55
+ date-released: 2021-08-18
56
56
  license: Apache-2.0
57
57
  repository-artifact: https://rubygems.org/gems/cff
58
58
  repository-code: https://github.com/citation-file-format/ruby-cff
@@ -72,6 +72,38 @@ CFF::File.open('CITATION.cff') do |cff|
72
72
  end
73
73
  ```
74
74
 
75
+ You can read a CFF file quickly with `CFF::File::read`:
76
+
77
+ ```ruby
78
+ cff = CFF::File.read('CITATION.cff')
79
+ ```
80
+
81
+ And you can read a CFF file from memory with `CFF::Model::read` or `CFF::Model::open` - as with `CFF::File` a block can be passed in to `open`:
82
+
83
+ ```ruby
84
+ cff_string = ::File.read('CITATION.cff')
85
+ cff = CFF::Model.read(cff_string)
86
+
87
+ CFF::Model.open(cff_string) do |cff|
88
+ # Edit cff here...
89
+ end
90
+ ```
91
+
92
+ To quickly reference other software from your own CFF file, you can use `CFF::Reference.from_cff`. This example uses the CFF file from the core CFF repository as a reference for the Ruby CFF repository:
93
+
94
+ ```ruby
95
+ require 'open-uri'
96
+
97
+ uri = 'https://raw.githubusercontent.com/citation-file-format/citation-file-format/main/CITATION.cff'
98
+ other_cff = URI.open(uri).read
99
+
100
+ ref = CFF::Reference.from_cff(CFF::Model.read(other_cff))
101
+
102
+ CFF::File.open('CITATION.cff') do |cff|
103
+ cff.references = [ref]
104
+ end
105
+ ```
106
+
75
107
  ### Validating CFF files
76
108
 
77
109
  To quickly validate a file and raise an error on failure, you can use `CFF::File` directly:
@@ -95,10 +127,12 @@ rescue CFF::ValidationError => e
95
127
  end
96
128
  ```
97
129
 
98
- Non-bang methods (`validate`) return a two-element array, with `true`/`false` at index 0 to indicate pass/fail, and an array of errors at index 1 (if any).
130
+ Non-bang methods (`validate`) return an array, with `true`/`false` at index 0 to indicate pass/fail, and an array of errors at index 1 (if any).
99
131
 
100
132
  Passing `fail_fast: true` (default: `false`) will cause the validator to abort on the first error it encounters and report just that. Only the instance methods on `CFF::File` and `CFF::Model` provide the `fail_fast` option.
101
133
 
134
+ The validation methods (both class and instance) on `File` also validate the filename of a CFF file; in normal circumstances a CFF file should be named 'CITATION.cff'. You can switch this behaviour off by passing `fail_on_filename: false`. The non-bang methods (`validate`) on `File` return an extra value in the result array: `true`/`false` at index 2 to indicate whether the filename passed/failed validation.
135
+
102
136
  ### Outputting citation text
103
137
 
104
138
  This library can use CFF data to output text suitable for use when citing software. Currently the output formats supported are:
@@ -133,7 +167,7 @@ year = {2021}
133
167
  #### APA-like format
134
168
 
135
169
  ```
136
- Haines, R. (2021). Ruby CFF Library (Version 0.8.0) [Computer software]. https://github.com/citation-file-format/ruby-cff
170
+ Haines, R. (2021). Ruby CFF Library (Version 0.9.0) [Computer software]. https://github.com/citation-file-format/ruby-cff
137
171
  ```
138
172
 
139
173
  #### Citing a paper rather than software
data/cff.gemspec CHANGED
@@ -48,7 +48,7 @@ Gem::Specification.new do |spec|
48
48
 
49
49
  spec.required_ruby_version = '>= 2.6'
50
50
 
51
- spec.add_runtime_dependency 'json_schema', '~> 0.21.0'
51
+ spec.add_runtime_dependency 'json_schema', '~> 0.20.0'
52
52
  spec.add_runtime_dependency 'language_list', '~> 1.2'
53
53
 
54
54
  spec.add_development_dependency 'minitest', '~> 5.14'
data/lib/cff/errors.rb CHANGED
@@ -25,21 +25,29 @@ module CFF
25
25
  end
26
26
  end
27
27
 
28
- # ValidationError is raised when a CFF file fails validatedation. It
29
- # contains details of each failure that was detected by the underlying
30
- # JsonSchema library, which is used to perform the validation.
28
+ # ValidationError is raised when a CFF file fails validation. It contains
29
+ # details of each failure that was detected by the underlying JsonSchema
30
+ # library, which is used to perform the validation.
31
+ #
32
+ # Additionally, the `invalid_filename` flag is used to indicate whether the
33
+ # CFF file is named correctly. This is only used when validating a File;
34
+ # validating a Model directly will not set this flag to `true`.
31
35
  class ValidationError < Error
32
36
 
33
37
  # The list of JsonSchema::ValidationErrors found by the validator.
34
38
  attr_reader :errors
35
39
 
36
- def initialize(errors) # :nodoc:
40
+ # If a File was validated, was its filename invalid?
41
+ attr_reader :invalid_filename
42
+
43
+ def initialize(errors, invalid_filename: false) # :nodoc:
37
44
  super('Validation error')
38
45
  @errors = errors
46
+ @invalid_filename = invalid_filename
39
47
  end
40
48
 
41
49
  def to_s # :nodoc:
42
- "#{super}: #{@errors.join(' ')}"
50
+ "#{super}: (Invalid filename: #{@invalid_filename}) #{@errors.join(' ')}"
43
51
  end
44
52
  end
45
53
  end
data/lib/cff/file.rb CHANGED
@@ -19,6 +19,11 @@ module CFF
19
19
 
20
20
  # File provides direct access to a CFF Model, with the addition of some
21
21
  # filesystem utilities.
22
+ #
23
+ # To be a fully compliant and valid CFF file its filename should be
24
+ # 'CITATION.cff'. This class allows you to create files with any filename,
25
+ # and to validate the contents of those files independently of the preferred
26
+ # filename.
22
27
  class File
23
28
 
24
29
  # A comment to be inserted at the top of the resultant CFF file.
@@ -33,6 +38,7 @@ module CFF
33
38
  'Gem: https://rubygems.org/gems/cff',
34
39
  'CFF: https://citation-file-format.github.io/'
35
40
  ].freeze # :nodoc:
41
+ CFF_VALID_FILENAME = 'CITATION.cff' # :nodoc:
36
42
 
37
43
  # :call-seq:
38
44
  # new(filename, title) -> File
@@ -53,7 +59,7 @@ module CFF
53
59
  end
54
60
 
55
61
  # :call-seq:
56
- # read(file) -> File
62
+ # read(filename) -> File
57
63
  #
58
64
  # Read a file and parse it for subsequent manipulation.
59
65
  def self.read(file)
@@ -66,8 +72,8 @@ module CFF
66
72
  end
67
73
 
68
74
  # :call-seq:
69
- # open(file) -> File
70
- # open(file) {|cff| block }
75
+ # open(filename) -> File
76
+ # open(filename) { |cff| block }
71
77
  #
72
78
  # With no associated block, File.open is a synonym for ::read. If the
73
79
  # optional code block is given, it will be passed the opened file as an
@@ -97,31 +103,41 @@ module CFF
97
103
  end
98
104
 
99
105
  # :call-seq:
100
- # validate(file) -> Array
106
+ # validate(filename, fail_on_filename: true) -> Array
101
107
  #
102
108
  # Read a file and return an array with the result. The result array is a
103
- # two-element array, with `true`/`false` at index 0 to indicate pass/fail,
104
- # and an array of errors at index 1 (if any).
105
- def self.validate(file)
106
- File.read(file).validate
109
+ # three-element array, with `true`/`false` at index 0 to indicate
110
+ # pass/fail, an array of schema validation errors at index 1 (if any), and
111
+ # `true`/`false` at index 2 to indicate whether the filename passed/failed
112
+ # validation.
113
+ #
114
+ # You can choose whether filename validation failure should cause overall
115
+ # validation failure with the `fail_on_filename` parameter (default: true).
116
+ def self.validate(file, fail_on_filename: true)
117
+ File.read(file).validate(fail_on_filename: fail_on_filename)
107
118
  end
108
119
 
109
120
  # :call-seq:
110
- # validate!(file)
121
+ # validate!(filename, fail_on_filename: true)
111
122
  #
112
123
  # Read a file and raise a ValidationError upon failure. If an error is
113
124
  # raised it will contain the detected validation failures for further
114
125
  # inspection.
115
- def self.validate!(file)
116
- File.read(file).validate!
126
+ #
127
+ # You can choose whether filename validation failure should cause overall
128
+ # validation failure with the `fail_on_filename` parameter (default: true).
129
+ def self.validate!(file, fail_on_filename: true)
130
+ File.read(file).validate!(fail_on_filename: fail_on_filename)
117
131
  end
118
132
 
119
133
  # :call-seq:
120
- # write(file, model)
121
- # write(file, yaml)
134
+ # write(filename, File)
135
+ # write(filename, Model)
136
+ # write(filename, yaml)
122
137
  #
123
- # Write the supplied model or yaml string to `file`.
138
+ # Write the supplied File, Model or yaml string to `file`.
124
139
  def self.write(file, cff, comment = '')
140
+ comment = cff.comment if cff.respond_to?(:comment)
125
141
  cff = cff.to_yaml unless cff.is_a?(String)
126
142
  content = File.format_comment(comment) + cff[YAML_HEADER.length...-1]
127
143
 
@@ -129,10 +145,55 @@ module CFF
129
145
  end
130
146
 
131
147
  # :call-seq:
132
- # write
148
+ # validate(fail_fast: false, fail_on_filename: true) -> Array
149
+ #
150
+ # Validate this file and return an array with the result. The result array
151
+ # is a three-element array, with `true`/`false` at index 0 to indicate
152
+ # pass/fail, an array of schema validation errors at index 1 (if any), and
153
+ # `true`/`false` at index 2 to indicate whether the filename passed/failed
154
+ # validation.
155
+ #
156
+ # You can choose whether filename validation failure should cause overall
157
+ # validation failure with the `fail_on_filename` parameter (default: true).
158
+ def validate(fail_fast: false, fail_on_filename: true)
159
+ valid_filename = (::File.basename(@filename) == CFF_VALID_FILENAME)
160
+ result = (@model.validate(fail_fast: fail_fast) << valid_filename)
161
+ result[0] &&= valid_filename if fail_on_filename
162
+
163
+ result
164
+ end
165
+
166
+ # :call-seq:
167
+ # validate!(fail_fast: false, fail_on_filename: true)
168
+ #
169
+ # Validate this file and raise a ValidationError upon failure. If an error
170
+ # is raised it will contain the detected validation failures for further
171
+ # inspection.
172
+ #
173
+ # You can choose whether filename validation failure should cause overall
174
+ # validation failure with the `fail_on_filename` parameter (default: true).
175
+ def validate!(fail_fast: false, fail_on_filename: true)
176
+ result = validate(
177
+ fail_fast: fail_fast, fail_on_filename: fail_on_filename
178
+ )
179
+ return if result[0]
180
+
181
+ raise ValidationError.new(result[1], invalid_filename: !result[2])
182
+ end
183
+
184
+ # :call-seq:
185
+ # write(save_as: filename)
133
186
  #
134
- # Write this CFF File.
135
- def write
187
+ # Write this CFF File. The `save_as` parameter can be used to save a new
188
+ # copy of this CFF File under a different filename, leaving the original
189
+ # file untouched. If `save_as` is used then the internal filename of the
190
+ # File will be updated to the supplied filename.
191
+ def write(save_as: nil)
192
+ unless save_as.nil?
193
+ @filename = save_as
194
+ @dirty = true
195
+ end
196
+
136
197
  File.write(@filename, @model, @comment) if @dirty
137
198
  @dirty = false
138
199
  end
@@ -156,6 +217,10 @@ module CFF
156
217
  @comment = comment
157
218
  end
158
219
 
220
+ def to_yaml # :nodoc:
221
+ @model.to_yaml
222
+ end
223
+
159
224
  def method_missing(name, *args) # :nodoc:
160
225
  if @model.respond_to?(name)
161
226
  @dirty = true if name.to_s.end_with?('=') # Remove to_s when Ruby >2.6.
@@ -40,9 +40,9 @@ module CFF
40
40
  end
41
41
 
42
42
  def self.publication_data_from_model(model)
43
- return '' unless model.respond_to?(:journal)
43
+ return '' unless model.respond_to?(:journal) && !model.journal.empty?
44
44
 
45
- vol = "#{model.volume}(#{model.issue})" unless model.volume.to_s.empty?
45
+ vol = model.volume.to_s.empty? ? '' : "#{model.volume}(#{model.issue})"
46
46
 
47
47
  [model.journal, vol, model.start.to_s].reject(&:empty?).join(', ')
48
48
  end
@@ -25,11 +25,12 @@ module CFF
25
25
  # will return the empty string. The simple fields are (with defaults in
26
26
  # parentheses):
27
27
  #
28
+ # * `description`
28
29
  # * `type`
29
30
  # * `value`
30
31
  class Identifier < ModelPart
31
32
 
32
- ALLOWED_FIELDS = ['type', 'value'].freeze # :nodoc:
33
+ ALLOWED_FIELDS = ['description', 'type', 'value'].freeze # :nodoc:
33
34
 
34
35
  # The [defined set of identifier types](https://github.com/citation-file-format/citation-file-format/blob/main/README.md#identifier-type-strings).
35
36
  IDENTIFIER_TYPES = ['doi', 'url', 'swh', 'other'].freeze
@@ -20,8 +20,7 @@ module CFF
20
20
  # Functionality to add licence(s) to parts of the CFF model.
21
21
  module Licensable
22
22
 
23
- # :nodoc:
24
- LICENSES = SCHEMA_FILE['definitions']['license-enum']['enum'].dup.freeze
23
+ LICENSES = SCHEMA_FILE['definitions']['license-enum']['enum'].dup.freeze # :nodoc:
25
24
 
26
25
  # :call-seq:
27
26
  # license = license
data/lib/cff/model.rb CHANGED
@@ -83,6 +83,29 @@ module CFF
83
83
  yield self if block_given?
84
84
  end
85
85
 
86
+ # :call-seq:
87
+ # read(String) -> Model
88
+ #
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]))
92
+ end
93
+
94
+ # :call-seq:
95
+ # open(String) -> Model
96
+ # open(String) { |cff| block } -> Model
97
+ #
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)
103
+
104
+ yield cff if block_given?
105
+
106
+ cff
107
+ end
108
+
86
109
  # :call-seq:
87
110
  # date_released = date
88
111
  #
data/lib/cff/reference.rb CHANGED
@@ -160,6 +160,29 @@ module CFF
160
160
  yield self if block_given?
161
161
  end
162
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
+
163
186
  # :call-seq:
164
187
  # add_language language
165
188
  #
@@ -22,8 +22,7 @@ module CFF
22
22
  # Methods to validate CFF files/models against a formal schema.
23
23
  module Validatable
24
24
 
25
- # :nodoc:
26
- SCHEMA = JsonSchema.parse!(SCHEMA_FILE)
25
+ SCHEMA = JsonSchema.parse!(SCHEMA_FILE) # :nodoc:
27
26
 
28
27
  # :call-seq:
29
28
  # validate!(fail_fast: false)
data/lib/cff/version.rb CHANGED
@@ -17,7 +17,7 @@
17
17
  ##
18
18
  module CFF
19
19
  # :nodoc:
20
- VERSION = '0.8.0'
20
+ VERSION = '0.9.0'
21
21
  DEFAULT_SPEC_VERSION = '1.2.0'
22
22
  MIN_VALIDATABLE_VERSION = '1.2.0'
23
23
  end
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://citation-file-format.github.io/1.2.0/schema",
2
+ "$id": "https://citation-file-format.github.io/1.2.0/schema.json",
3
3
  "$schema": "http://json-schema.org/draft-07/schema",
4
4
  "additionalProperties": false,
5
5
  "definitions": {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Haines
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2021-08-08 00:00:00.000000000 Z
12
+ date: 2021-08-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json_schema
@@ -17,14 +17,14 @@ dependencies:
17
17
  requirements:
18
18
  - - "~>"
19
19
  - !ruby/object:Gem::Version
20
- version: 0.21.0
20
+ version: 0.20.0
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - "~>"
26
26
  - !ruby/object:Gem::Version
27
- version: 0.21.0
27
+ version: 0.20.0
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: language_list
30
30
  requirement: !ruby/object:Gem::Requirement