l43_opt_parser 0.0.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 63be9d1fa069240f7ef6fdd36805bb3af88c50519543465170bfb15dcd1af069
4
- data.tar.gz: f54d8f9209b79139f896e8453e78010de27dd3bdd00c0aa6aa6db3f0713c8e4a
3
+ metadata.gz: 960127bd5fe3d57920703c562c1eb31335d8e1c3abef182ff4e673ee1479e70d
4
+ data.tar.gz: ba5380ffe585fd5dd198cc11eddedfad056c2d17667e8f446fdaccd34a0fea4d
5
5
  SHA512:
6
- metadata.gz: fb4bf372fe886ef383b136b964499dbfc861fc6c67b7b58343eb63651b0ecf1b4235e66aacf865d0465f807386c4d330b844463af9c414351f598bf839a4072c
7
- data.tar.gz: c332e0cb1dda8032e5636d9c96b5fc99d43f9c588140e3561ac48f0e7ebe7d6d7128bea88b67b968f90dc1a585c48574ad0dc77a1eedf4388adaec5833cce99e
6
+ metadata.gz: 4da0f00b58bb29dbb69f511e8404a5cb03664ba38ef03933ce7e4ff3e4e4963178d0b9716e0efc79e22534fc081c00ca6d5fe7de127356c2c35f7d7cdd2ce28d
7
+ data.tar.gz: 20062237a066a5d7eb15da190a36aa5dd74e1a415dab6ffc96dd8a9287374afc11ab125727030f7628b74acd98ec53df6fac6dbbb6c35401b9ba4c565b5f20f0
@@ -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,32 +1,26 @@
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 on_multiple(&blk)
19
+ # @on_multiple = blk
20
+ # self
21
+ # end
22
+
23
+ def real_name = as || name
30
24
  end
31
25
  end
32
26
  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.1'
5
+ VERSION = '0.2.0'
6
6
  end
7
7
  end
8
8
  # SPDX-License-Identifier: AGPL-3.0-or-later
@@ -1,45 +1,144 @@
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
+ BadFormat = Class.new(RuntimeError)
25
+ DuplicateFlagDefinition = Class.new(RuntimeError)
26
+ DuplicateKwdDefinition = Class.new(RuntimeError)
27
+ FailedCheck = Class.new(RuntimeError)
28
+ MissingValue = Class.new(RuntimeError)
29
+ UndefinedFlag = Class.new(RuntimeError)
30
+ UndefinedKwd = Class.new(RuntimeError)
31
+
11
32
  FlagRgx = /\A:(.*)/
12
33
  KeywordRgx = /(.*):\z/
34
+ ShortFlagRgx = /\A\-(.*)/
35
+
36
+ attr_reader :arguments, :constrained, :description, :kwds_name
13
37
 
14
38
  # Constraints
15
39
  # -----------
16
- def flag(name, multiple: false)
17
- flags.merge!(symbolize(name) => Flag.new(symbolize(name), multiple:)) { |*|
40
+ def flag(name, short=nil, **kwds)
41
+ f = Flag.new(name:, short:, **kwds)
42
+ flags.merge!(name => f) { |*|
18
43
  raise DuplicateFlagDefinition, "flag :#{name} is already defined"
19
44
  }
45
+ if short
46
+ aliases.merge!(short => f) {|*|
47
+ raise DuplicateFlagDefinition, "flag :#{short} is already defined"
48
+ }
49
+ end
20
50
  @constrained = true
51
+ description.add_flag(f)
21
52
  self
22
53
  end
23
54
 
24
- def keyword(name, multiple: false, type: String, &initializer)
25
- keywords.merge!(name => Keyword.new(name, multiple:, type:, &initializer)) { |*|
55
+
56
+ def keyword(name, short=nil, as: nil, default: Core::None, **ks, &init)
57
+ k = Keyword.new(name:, short:, init:, default:, as:, **ks)
58
+ keywords.merge!(name => k) { |*|
26
59
  raise DuplicateKeywordDefinition, "keyword :#{name} is already defined"
27
60
  }
61
+ if short
62
+ aliases.merge!(short => k) {|*|
63
+ raise DuplicateKwdDefinition, "keyword :#{short} is already defined"
64
+ }
65
+ end
66
+ if default != Core::None
67
+ defaults.merge!((as||name) => default)
68
+ elsif k.multiple
69
+ defaults.merge!((as||name) => [])
70
+ end
28
71
  @constrained = true
72
+ description.add_kwd(k)
29
73
  self
30
74
  end
31
75
 
76
+ # Documentation
77
+ # -------------
78
+ def add_br
79
+ description.add_line
80
+ self
81
+ end
82
+
83
+ def add_desc(*chunks)
84
+ description.add_desc(*chunks)
85
+ self
86
+ end
87
+
88
+ def section(line, indent: 1, color: [:bold, :green])
89
+ description.add_section(line, indent:, color:)
90
+ self
91
+ end
92
+
93
+ # Services
94
+ # --------
95
+
96
+ def descriptions = description.lines
97
+ def help(device: $stderr) = cs_put(descriptions, device:)
98
+
99
+
32
100
  # Business Logic
33
101
  # --------------
34
102
  def parse(arguments)
35
- reset_data!
36
103
  @arguments = arguments
37
104
  parse_arguments
38
105
 
39
- OpenStruct.new(args:, errors:, kwds:, ok?: errors.empty?).freeze
106
+ return Result.error(errors) unless errors.empty?
107
+ if constrained
108
+ mo = make_open_object
109
+ kwds = mo.new(**defaults.merge(keywords_values))
110
+ post_checks!(kwds)
111
+ kwds = run_hooks(kwds)
112
+ result = OpenStruct.new(kwds_name => kwds, args:, errors:).freeze
113
+ Result.ok(result)
114
+ else
115
+ Result.ok(OpenStruct.new(args:, errors:, kwds: OpenStruct.new(**keywords_values)).freeze)
116
+ end
117
+ end
118
+
119
+ def post_hook(&blk)
120
+ hooks << blk
121
+ self
122
+ end
123
+
124
+ def post_check(name = nil, &blk)
125
+ checks << [name, blk]
126
+ self
127
+ end
128
+
129
+ def usage(name, options: nil, args: nil, nls: 0)
130
+ description.add_usage(name, options:, args:)
131
+ nls.times { add_br }
40
132
  end
41
133
 
42
134
  private
135
+ def initialize(with_help: false, kwds_name: :kwds)
136
+ @kwds_name = kwds_name
137
+ @description = Description.new
138
+ if with_help
139
+ flag(:help, :h, desc: "show this help and exit", stop: true)
140
+ end
141
+ end
43
142
  # Permissions
44
143
  # -----------
45
144
  def allowed_flag?(flag)
@@ -47,31 +146,26 @@ module L43
47
146
  flags.fetch(flag, false)
48
147
  end
49
148
 
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
149
  # Error Handling
61
150
  # --------------
62
- def add_errors(new_errors) = errors.push(*new_errors)
151
+ def add_errors(*new_errors)
152
+ errors.push(*new_errors.flatten)
153
+ :error
154
+ end
63
155
 
64
156
  # Business Logic
65
157
  # --------------
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)
158
+ def assign_keyword(kwd_sym, value)
159
+ case get_keyword(kwd_sym)
160
+ in :error
161
+ nil
162
+ in keyword
163
+ case keyword.assign_value(value)
164
+ in :error, error
165
+ nil
166
+ in :ok, keyword
167
+ keywords_values.merge!(keyword.real_name => keyword.value)
168
+ end
75
169
  end
76
170
  end
77
171
 
@@ -79,31 +173,50 @@ module L43
79
173
  return value unless constrained?
80
174
  end
81
175
 
82
- def get_flag(flag)
83
- flags.fetch(flag) do
84
- flags[flag] = Flag.new(flag, multiple: true)
176
+ def get_flag(flag_sym)
177
+ flags.fetch(flag_sym) do
178
+ aliases.fetch(flag_sym) do
179
+ if constrained?
180
+ add_errors("undefined flag #{flag_sym}")
181
+ else
182
+ flags[flag_sym] = Flag.new(name: flag_sym)
183
+ end
184
+ end
85
185
  end
86
186
  end
87
187
 
88
- def get_keyword(kwd)
89
- keywords.fetch(kwd) do
90
- keywords[kwd] = Keyword.new(kwd, multiple: true)
188
+ def get_keyword(name)
189
+ keywords.fetch(name) do
190
+ aliases.fetch(name) do
191
+ if constrained
192
+ add_errors("undefined keyword: '#{name}'")
193
+ else
194
+ keywords[name] = Keyword.new(name:)
195
+ end
196
+ end
91
197
  end
92
198
  end
93
199
 
200
+ def parse_all_flags(flags)
201
+ flags.grapheme_clusters.each { parse_flag(it.to_sym) }
202
+ 1
203
+ end
204
+
94
205
  def parse_argument
95
- case @arguments.first
206
+ case arguments.first
96
207
  when nil
97
208
  return
98
209
  when '::'
99
- args.push(*@arguments.drop(1))
210
+ args.push(*arguments.drop(1))
100
211
  return
212
+ when ShortFlagRgx
213
+ parse_all_flags($1)
101
214
  when FlagRgx
102
215
  parse_flag($1.to_sym)
103
216
  when KeywordRgx
104
217
  parse_kwd($1.to_sym)
105
218
  else
106
- args << @arguments.first
219
+ args << arguments.first
107
220
  1
108
221
  end
109
222
  end
@@ -111,51 +224,59 @@ module L43
111
224
  def parse_arguments
112
225
  loop do
113
226
  count = parse_argument
227
+ # p(args:, arguments:)
114
228
  return unless count
115
229
  @arguments = @arguments.drop(count)
116
230
  end
117
231
  end
118
232
 
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)
233
+ def parse_flag(flag_sym)
234
+ case get_flag(flag_sym)
235
+ in :error
236
+ nil
237
+ in flag
238
+ case flag.count_ok
239
+ in :ok
240
+ flag = flag
241
+ .update_attribute(:arg_count, &:succ)
242
+ .update(value: true)
243
+ keywords_values.merge!(flag.real_name => true)
244
+ in :error, error
245
+ add_errors(error)
246
+ end
128
247
  end
129
248
  1
130
249
  end
131
250
 
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
251
+ def parse_kwd(kwd_sym)
252
+ case @arguments
253
+ in [_]
254
+ add_errors("keyword #{kwd_sym}: requires value but none was given, make sure there is an argument after '#{kwd_sym}:'")
255
+ in [_, value, *]
256
+ assign_keyword(kwd_sym, value)
257
+ end
140
258
  2
141
259
  end
142
260
 
261
+ def post_checks!(keywords)
262
+ checks.each do |name, check|
263
+ next if check.(keywords)
264
+
265
+ raise FailedCheck, "in postcheck #{name||check.source_location.join(":")}"
266
+ end
267
+ end
268
+
269
+ def run_hooks(keywords)
270
+ hooks.inject keywords do |k, hook|
271
+ hook.(k)
272
+ end
273
+ end
274
+
143
275
  def symbolize(name) = name.to_s.gsub("-", "_").to_sym
144
276
 
145
277
  # Data
146
278
  # ----
147
- def args = @__args__ ||= []
148
279
  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
280
 
160
281
  end
161
282
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: l43_opt_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Dober
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-02-10 00:00:00.000000000 Z
10
+ date: 2026-04-26 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ostruct
@@ -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,14 +93,14 @@ 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
  - - ">="
55
100
  - !ruby/object:Gem::Version
56
101
  version: '0'
57
102
  requirements: []
58
- rubygems_version: 4.0.4
103
+ rubygems_version: 4.0.10
59
104
  specification_version: 4
60
105
  summary: Extract Specs from Markdown
61
106
  test_files: []