l43_opt_parser 0.0.2 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e33711d68cf5ec66cd202dd3eb07c907ff2916f98f7b793c9b3af91cb7040461
4
- data.tar.gz: 82760d91e9ca728955e3e00b1820c9bc3d728dd804ee52a8e1a834589c3a7ecf
3
+ metadata.gz: 7480fcd5f83ef14a37ccbe58bf3beea44d2ce219a87c686be600051b10879870
4
+ data.tar.gz: faab1f7444bcd6d8a1e479206a6d1a18a40d9b9da901e32cb084663b7ef0ea7a
5
5
  SHA512:
6
- metadata.gz: 8b9c8523da3ced3fb2ff6b8a008df478d14d693a82af49163abab290d3dac9acbebcf2cfee98e08a1fbdabd98ce035aaaa00e7c2857346ccbcbba9674571443e
7
- data.tar.gz: a2bf0f60f4dfc7c5b1d0c923a53f24c2b2adc4f03a159c22c48b5778c41a9cd8ce55542fa25efdabd818c0e961adea7347b247576310e48cfdde9c82df40aae8
6
+ metadata.gz: a2f99db96ded13ab47be70b22dc1a89c8fd0e553cfb4d44ec58b79e5c8d596347ea8199e0c639d336a8691156cd9e0e4444e860662d4354f50c8916c596622e8
7
+ data.tar.gz: abfd0ec70ee9b4ebfff30abe8bea87e6c0d2a4f650b63997e12271e18f5d06d174d27d1d50fd7f92f34f55ba555c49db904c652a2994e59c2ef56645f46d9f48
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'l43/core/none'
4
+ module L43
5
+ class OptParser
6
+ class Description
7
+
8
+ def add_desc(*chunks, indent: 5)
9
+ self << [indent, *chunks]
10
+ end
11
+
12
+ def add_flag(flag, indent: 3, color: :cyan)
13
+ return unless flag.desc
14
+ short = flag.short ? ":#{flag.short}|" : ""
15
+ self << [indent, color, short, ":", flag.name.to_s, :reset, 1, flag.desc ]
16
+ end
17
+
18
+ def add_kwd(kwd, indent: 3, color: :cyan, vcolor: [:bold,:blue])
19
+ return unless kwd.desc
20
+ short = kwd.short ? "#{kwd.short}:|" : ""
21
+ default = kwd.default == Core::None ? [] : [1, :dim, "defaults to: ", :bold, kwd.default.inspect]
22
+ self << [indent, color, short, kwd.name.to_s, ": ", vcolor, "<value>", :reset, 1, kwd.desc, *default]
23
+ end
24
+
25
+ def add_line(*chunks)
26
+ self << chunks
27
+ end
28
+
29
+ def add_section(text, indent: 1, color: [:bold, :green])
30
+ self << [nil, indent, color, text]
31
+ end
32
+
33
+ def add_usage(name, options:, args:)
34
+ options = "options" if options == true
35
+ args = "args" if args == true
36
+ @__lines__ = [[:bold, name, " ", *_args(options, [:bold, :cyan]), ' ', *_args(args, [:bold, :blue], "...")], *_lines]
37
+ end
38
+
39
+ def lines = _lines.dup.freeze
40
+
41
+ private
42
+ def _args(args, cols, pfx='')
43
+ case args
44
+ when nil
45
+ []
46
+ when String
47
+ [cols, pfx, "[", args, "]", :reset]
48
+ else
49
+ args
50
+ end
51
+ end
52
+
53
+ def _lines = @__lines__ ||= []
54
+
55
+
56
+ def <<(*line)
57
+ _lines << [nil, *line.flatten, :reset]
58
+ end
59
+ end
60
+ end
61
+ end
62
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -1,7 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- class OptParser
4
- DuplicateFlagDefinition = Class.new(RuntimeError)
5
- DuplicateKeywordDefinition = Class.new(RuntimeError)
3
+ module L43
4
+ class OptParser
5
+ BadFormat = Class.new(RuntimeError)
6
+ DuplicateFlagDefinition = Class.new(RuntimeError)
7
+ DuplicateFlagDefinition = Class.new(RuntimeError)
8
+ DuplicateKeywordDefinition = Class.new(RuntimeError)
9
+ FailedCheck = Class.new(RuntimeError)
10
+ MissingValue = Class.new(RuntimeError)
11
+ UndefinedFlag = Class.new(RuntimeError)
12
+ UndefinedKwd = Class.new(RuntimeError)
13
+ end
6
14
  end
7
15
  # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -1,32 +1,21 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require 'l43/open_object'
2
4
  module L43
3
5
  class OptParser
4
6
  class Flag
5
- attr_reader :multiple, :name
6
-
7
- def on_multiple(&blk)
8
- @on_multiple = blk
9
- self
10
- end
7
+ extend L43::OpenObject
8
+ attributes :name, arg_count: 0, as: nil, desc: nil, value: false, short: nil, stop: false
11
9
 
12
- def update(value, old_value)
13
- if old_value && !multiple
14
- [:error, "flag :#{name} has already been specified but is not a multiple flag"]
15
- elsif multiple
16
- [:ok, @on_multiple.(old_value, value)]
10
+ def count_ok
11
+ if arg_count.zero?
12
+ :ok
17
13
  else
18
- [:ok, true]
14
+ [:error, "flag #{name} cannot be set multiple times"]
19
15
  end
20
16
  end
21
17
 
22
- private
23
- def initialize(name, multiple: false)
24
- @name = name
25
- @multiple = multiple
26
- on_multiple do |old, value|
27
- old ? old.succ : value
28
- end
29
- end
18
+ def real_name = as || name
30
19
  end
31
20
  end
32
21
  end
@@ -1,44 +1,64 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'l43/core/enum/hash'
4
+ require 'l43/core/enum/set'
5
+ require 'l43/core/result'
6
+ require 'l43/open_object'
7
+
8
+
3
9
  module L43
10
+ module Core::Result
11
+ def if_error(&blk)
12
+ return if ok?
13
+ blk.(self)
14
+ end
15
+ end
16
+
4
17
  class OptParser
5
18
  class Keyword
6
- attr_reader :initializer, :multiple, :name, :type
19
+ Result = Core::Result
7
20
 
8
- def on_multiple(&blk)
9
- @on_multiple = blk
10
- self
11
- end
21
+ extend L43::OpenObject
22
+ attributes :name,
23
+ arg_count: 0,
24
+ as: nil,
25
+ default: nil,
26
+ desc: nil,
27
+ init: nil,
28
+ multiple: false,
29
+ set: nil,
30
+ short: nil,
31
+ value: nil
12
32
 
13
- def update(value, old_value)
14
- if old_value && !multiple
15
- [:error, "keyword #{name} has already been specified but is not a multiple keyword"]
16
- elsif old_value
17
- value = make_value(value)
18
- [:ok, @on_multiple.(old_value, value)]
19
- elsif multiple
20
- [:ok, [make_value(value)]]
21
- else
22
- [:ok, make_value(value)]
33
+ def assign_value(value)
34
+ if arg_count > 0 && !multiple
35
+ return Resut.error("must not assign multiple values to keyword #{name}")
23
36
  end
37
+
38
+ assign_from_set(value).if_error { return it }
39
+
40
+ value = init.(value) if init
41
+ Result.ok(
42
+ update_attribute(:arg_count, &:succ)
43
+ .update_attribute(:value, simple_or_multiple(value))
44
+ )
24
45
  end
25
46
 
47
+ def real_name = as || name
48
+
26
49
  private
27
- def initialize(name, multiple: false, type: String, &initializer)
28
- @initializer = initializer
29
- @name = name
30
- @multiple = multiple
31
- @type = type
32
- on_multiple do |old, value|
33
- old ? [*old, value] : value
34
- end
50
+ def assign_from_set(value)
51
+ return Result.ok(value) unless set
52
+ set
53
+ .fetch_any(value.to_sym, value.to_s) do
54
+ "bad value for keyword #{name}: #{value} (not in set: #{set.keys.inspect})"
55
+ end
35
56
  end
36
57
 
37
- def make_value(value)
38
- value = type.new(value)
39
- value = initializer.(value) if initializer
40
- multiple
41
- value
58
+ def simple_or_multiple(value)
59
+ return value unless multiple
60
+
61
+ [*Array(self.value), value]
42
62
  end
43
63
  end
44
64
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module L43
4
+ class OptParser
5
+ module Memos
6
+ # Compile Time
7
+ # ------------
8
+ def aliases = @__aliases__ ||= {}
9
+ def checks = @__checks__ ||= []
10
+ def defaults = @__defaults__ ||= {}
11
+ def errors = @__errors__ ||= []
12
+ def flags = @__flags__ ||= {}
13
+ def hooks = @__hooks__ ||= []
14
+ def keywords = @__keywords__ ||= {}
15
+
16
+ # Result
17
+ # ------
18
+ def args = @__args__ ||= []
19
+ def keywords_values = @__keywords_values__ ||= {}
20
+
21
+ end
22
+ end
23
+ end
24
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'l43/open_object'
4
+ require 'l43/core/none'
5
+ module L43
6
+ class OptParser
7
+ module Meta
8
+ def make_open_object
9
+ OpenObject.def_class(*undefaulted_kwd_names, **defaulted_kwds_and_flags)
10
+ end
11
+
12
+ private
13
+ def undefaulted_kwd_names
14
+ keywords.values.filter_map { it.default == Core::None && it.name }
15
+ end
16
+
17
+ def defaulted_kwds_and_flags
18
+ flag_defaults = flags.keys.map { [it, false] }.to_h
19
+ flag_defaults.merge(defaults)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -2,7 +2,7 @@
2
2
 
3
3
  module L43
4
4
  class OptParser
5
- VERSION = '0.0.2'
5
+ VERSION = '0.2.1'
6
6
  end
7
7
  end
8
8
  # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -1,45 +1,137 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ostruct'
3
+ require 'l43/color/colorschemes'
4
+ require 'l43/color/output'
5
+ require 'l43/core/none'
6
+ require 'l43/open_struct'
7
+ require 'l43/open_object'
8
+
9
+ require_relative 'opt_parser/description'
4
10
  require_relative 'opt_parser/errors'
5
11
  require_relative 'opt_parser/flag'
6
12
  require_relative 'opt_parser/keyword'
13
+ require_relative 'opt_parser/memos'
14
+ require_relative 'opt_parser/meta'
7
15
 
8
16
  module L43
9
17
  class OptParser
10
18
 
19
+ include Color::Output
20
+ include Core
21
+ include Memos
22
+ include Meta
23
+
24
+
11
25
  FlagRgx = /\A:(.*)/
12
26
  KeywordRgx = /(.*):\z/
27
+ ShortFlagRgx = /\A\-(.*)/
28
+
29
+ attr_reader :arguments, :constrained, :description, :kwds_name
13
30
 
14
31
  # Constraints
15
32
  # -----------
16
- def flag(name, multiple: false)
17
- flags.merge!(symbolize(name) => Flag.new(symbolize(name), multiple:)) { |*|
33
+ def flag(name, short=nil, **kwds)
34
+ f = Flag.new(name:, short:, **kwds)
35
+ flags.merge!(name => f) { |*|
18
36
  raise DuplicateFlagDefinition, "flag :#{name} is already defined"
19
37
  }
38
+ if short
39
+ aliases.merge!(short => f) {|*|
40
+ raise DuplicateFlagDefinition, "flag :#{short} is already defined"
41
+ }
42
+ end
20
43
  @constrained = true
44
+ description.add_flag(f)
21
45
  self
22
46
  end
23
47
 
24
- def keyword(name, multiple: false, type: String, &initializer)
25
- keywords.merge!(name => Keyword.new(name, multiple:, type:, &initializer)) { |*|
48
+
49
+ def keyword(name, short=nil, as: nil, default: Core::None, **ks, &init)
50
+ k = Keyword.new(name:, short:, init:, default:, as:, **ks)
51
+ keywords.merge!(name => k) { |*|
26
52
  raise DuplicateKeywordDefinition, "keyword :#{name} is already defined"
27
53
  }
54
+ if short
55
+ aliases.merge!(short => k) {|*|
56
+ raise DuplicateKeywordDefinition, "keyword :#{short} is already defined"
57
+ }
58
+ end
59
+ if default != Core::None
60
+ defaults.merge!((as||name) => default)
61
+ elsif k.multiple
62
+ defaults.merge!((as||name) => [])
63
+ end
28
64
  @constrained = true
65
+ description.add_kwd(k)
29
66
  self
30
67
  end
31
68
 
69
+ # Documentation
70
+ # -------------
71
+ def add_br
72
+ description.add_line
73
+ self
74
+ end
75
+
76
+ def add_desc(*chunks)
77
+ description.add_desc(*chunks)
78
+ self
79
+ end
80
+
81
+ def section(line, indent: 1, color: [:bold, :green])
82
+ description.add_section(line, indent:, color:)
83
+ self
84
+ end
85
+
86
+ # Services
87
+ # --------
88
+
89
+ def descriptions = description.lines
90
+ def help(device: $stderr) = cs_put(descriptions, device:)
91
+
92
+
32
93
  # Business Logic
33
94
  # --------------
34
95
  def parse(arguments)
35
- reset_data!
36
96
  @arguments = arguments
37
97
  parse_arguments
38
98
 
39
- OpenStruct.new(args:, errors:, kwds:, ok?: errors.empty?).freeze
99
+ return Result.error(errors) unless errors.empty?
100
+ if constrained
101
+ mo = make_open_object
102
+ kwds = mo.new(**defaults.merge(keywords_values))
103
+ post_checks!(kwds)
104
+ kwds = run_hooks(kwds)
105
+ result = OpenStruct.new(kwds_name => kwds, args:, errors:).freeze
106
+ Result.ok(result)
107
+ else
108
+ Result.ok(OpenStruct.new(args:, errors:, kwds: OpenStruct.new(**keywords_values)).freeze)
109
+ end
110
+ end
111
+
112
+ def post_hook(&blk)
113
+ hooks << blk
114
+ self
115
+ end
116
+
117
+ def post_check(name = nil, &blk)
118
+ checks << [name, blk]
119
+ self
120
+ end
121
+
122
+ def usage(name, options: nil, args: nil, nls: 0)
123
+ description.add_usage(name, options:, args:)
124
+ nls.times { add_br }
40
125
  end
41
126
 
42
127
  private
128
+ def initialize(with_help: false, kwds_name: :kwds)
129
+ @kwds_name = kwds_name
130
+ @description = Description.new
131
+ if with_help
132
+ flag(:help, :h, desc: "show this help and exit", stop: true)
133
+ end
134
+ end
43
135
  # Permissions
44
136
  # -----------
45
137
  def allowed_flag?(flag)
@@ -47,31 +139,26 @@ module L43
47
139
  flags.fetch(flag, false)
48
140
  end
49
141
 
50
- def allowed_kwd?(kwd)
51
- return true unless constrained?
52
- keywords.fetch(kwd, false)
53
- end
54
-
55
- def check_kwd(kwd, value)
56
- return unless constrained?
57
- end
58
-
59
-
60
142
  # Error Handling
61
143
  # --------------
62
- def add_errors(new_errors) = errors.push(*new_errors)
144
+ def add_errors(*new_errors)
145
+ errors.push(*new_errors.flatten)
146
+ :error
147
+ end
63
148
 
64
149
  # Business Logic
65
150
  # --------------
66
- def assign_kwd(kwd, value)
67
- error = check_kwd(kwd, value)
68
- return add_errors(error) if error
69
- keyword = get_keyword(kwd)
70
- case keyword.update(value, kwds[kwd])
71
- in :ok, value
72
- kwds.merge!(kwd => value)
73
- in :error, message
74
- add_errors(message)
151
+ def assign_keyword(kwd_sym, value)
152
+ case get_keyword(kwd_sym)
153
+ in :error
154
+ nil
155
+ in keyword
156
+ case keyword.assign_value(value)
157
+ in :error, error
158
+ nil
159
+ in :ok, keyword
160
+ keywords_values.merge!(keyword.real_name => keyword.value)
161
+ end
75
162
  end
76
163
  end
77
164
 
@@ -79,31 +166,50 @@ module L43
79
166
  return value unless constrained?
80
167
  end
81
168
 
82
- def get_flag(flag)
83
- flags.fetch(flag) do
84
- flags[flag] = Flag.new(flag, multiple: true)
169
+ def get_flag(flag_sym)
170
+ flags.fetch(flag_sym) do
171
+ aliases.fetch(flag_sym) do
172
+ if constrained?
173
+ add_errors("undefined flag #{flag_sym}")
174
+ else
175
+ flags[flag_sym] = Flag.new(name: flag_sym)
176
+ end
177
+ end
85
178
  end
86
179
  end
87
180
 
88
- def get_keyword(kwd)
89
- keywords.fetch(kwd) do
90
- keywords[kwd] = Keyword.new(kwd, multiple: true)
181
+ def get_keyword(name)
182
+ keywords.fetch(name) do
183
+ aliases.fetch(name) do
184
+ if constrained
185
+ add_errors("undefined keyword: '#{name}'")
186
+ else
187
+ keywords[name] = Keyword.new(name:)
188
+ end
189
+ end
91
190
  end
92
191
  end
93
192
 
193
+ def parse_all_flags(flags)
194
+ flags.grapheme_clusters.each { parse_flag(it.to_sym) }
195
+ 1
196
+ end
197
+
94
198
  def parse_argument
95
- case @arguments.first
199
+ case arguments.first
96
200
  when nil
97
201
  return
98
202
  when '::'
99
- args.push(*@arguments.drop(1))
203
+ args.push(*arguments.drop(1))
100
204
  return
205
+ when ShortFlagRgx
206
+ parse_all_flags($1)
101
207
  when FlagRgx
102
208
  parse_flag($1.to_sym)
103
209
  when KeywordRgx
104
210
  parse_kwd($1.to_sym)
105
211
  else
106
- args << @arguments.first
212
+ args << arguments.first
107
213
  1
108
214
  end
109
215
  end
@@ -111,51 +217,59 @@ module L43
111
217
  def parse_arguments
112
218
  loop do
113
219
  count = parse_argument
220
+ # p(args:, arguments:)
114
221
  return unless count
115
222
  @arguments = @arguments.drop(count)
116
223
  end
117
224
  end
118
225
 
119
- def parse_flag(flag)
120
- add_errors("undefined flag: :#{flag}") unless allowed_flag?(flag)
121
-
122
- defined_flag = get_flag(flag)
123
- case defined_flag.update(1, kwds[flag])
124
- in :ok, value
125
- kwds.merge!(flag => value)
126
- in :error, message
127
- add_errors(message)
226
+ def parse_flag(flag_sym)
227
+ case get_flag(flag_sym)
228
+ in :error
229
+ nil
230
+ in flag
231
+ case flag.count_ok
232
+ in :ok
233
+ flag = flag
234
+ .update_attribute(:arg_count, &:succ)
235
+ .update(value: true)
236
+ keywords_values.merge!(flag.real_name => true)
237
+ in :error, error
238
+ add_errors(error)
239
+ end
128
240
  end
129
241
  1
130
242
  end
131
243
 
132
- def parse_kwd(kwd)
133
- add_errors("undefined keyword: #{kwd}:") unless allowed_kwd?(kwd)
134
- case @arguments
135
- in [_]
136
- add_errors("keyword #{kwd}: requires value but none was given, make sure there is an argument after '#{kwd}:'")
137
- in [_, value, *]
138
- assign_kwd(kwd, value)
139
- end
244
+ def parse_kwd(kwd_sym)
245
+ case @arguments
246
+ in [_]
247
+ add_errors("keyword #{kwd_sym}: requires value but none was given, make sure there is an argument after '#{kwd_sym}:'")
248
+ in [_, value, *]
249
+ assign_keyword(kwd_sym, value)
250
+ end
140
251
  2
141
252
  end
142
253
 
254
+ def post_checks!(keywords)
255
+ checks.each do |name, check|
256
+ next if check.(keywords)
257
+
258
+ raise FailedCheck, "in postcheck #{name||check.source_location.join(":")}"
259
+ end
260
+ end
261
+
262
+ def run_hooks(keywords)
263
+ hooks.inject keywords do |k, hook|
264
+ hook.(k)
265
+ end
266
+ end
267
+
143
268
  def symbolize(name) = name.to_s.gsub("-", "_").to_sym
144
269
 
145
270
  # Data
146
271
  # ----
147
- def args = @__args__ ||= []
148
272
  def constrained? = @constrained
149
- def errors = @__errors__ ||= []
150
- def kwds = @__kwds__ ||= {}
151
- def flags = @__flags__ ||= {}
152
- def keywords = @__keywords__ ||= {}
153
-
154
- def reset_data!
155
- @__args__ = []
156
- @__errors__ = []
157
- @__kwds__ = {}
158
- end
159
273
 
160
274
  end
161
275
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: l43_opt_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Dober
@@ -23,6 +23,48 @@ dependencies:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
25
  version: 0.6.3
26
+ - !ruby/object:Gem::Dependency
27
+ name: l43_color
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: 0.2.1
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: 0.2.1
40
+ - !ruby/object:Gem::Dependency
41
+ name: l43_core
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: 0.2.8
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: 0.2.8
54
+ - !ruby/object:Gem::Dependency
55
+ name: l43_open_object
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: 0.2.9
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 0.2.9
26
68
  description: |
27
69
  A highly customizable option parser, allowing for different
28
70
  option syntax and returning custom classes' instances depending on
@@ -33,9 +75,12 @@ extensions: []
33
75
  extra_rdoc_files: []
34
76
  files:
35
77
  - lib/l43/opt_parser.rb
78
+ - lib/l43/opt_parser/description.rb
36
79
  - lib/l43/opt_parser/errors.rb
37
80
  - lib/l43/opt_parser/flag.rb
38
81
  - lib/l43/opt_parser/keyword.rb
82
+ - lib/l43/opt_parser/memos.rb
83
+ - lib/l43/opt_parser/meta.rb
39
84
  - lib/l43/opt_parser/version.rb
40
85
  homepage: https://codeberg.org/lab419/l43_opt_parser
41
86
  licenses:
@@ -48,7 +93,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
48
93
  requirements:
49
94
  - - ">="
50
95
  - !ruby/object:Gem::Version
51
- version: 4.0.0
96
+ version: 4.0.1
52
97
  required_rubygems_version: !ruby/object:Gem::Requirement
53
98
  requirements:
54
99
  - - ">="