ace-support-cli 0.6.2
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 +7 -0
- data/CHANGELOG.md +61 -0
- data/LICENSE +1 -0
- data/README.md +35 -0
- data/Rakefile +13 -0
- data/exe/ace-support-cli +6 -0
- data/lib/ace/support/cli/argv_coalescer.rb +66 -0
- data/lib/ace/support/cli/base.rb +76 -0
- data/lib/ace/support/cli/command.rb +68 -0
- data/lib/ace/support/cli/error.rb +71 -0
- data/lib/ace/support/cli/errors.rb +20 -0
- data/lib/ace/support/cli/help/banner.rb +238 -0
- data/lib/ace/support/cli/help/concise.rb +66 -0
- data/lib/ace/support/cli/help/help_command.rb +67 -0
- data/lib/ace/support/cli/help/two_tier_help.rb +24 -0
- data/lib/ace/support/cli/help/usage.rb +178 -0
- data/lib/ace/support/cli/help/version_command.rb +38 -0
- data/lib/ace/support/cli/models/argument.rb +31 -0
- data/lib/ace/support/cli/models/option.rb +52 -0
- data/lib/ace/support/cli/parser.rb +272 -0
- data/lib/ace/support/cli/registry.rb +86 -0
- data/lib/ace/support/cli/registry_dsl.rb +44 -0
- data/lib/ace/support/cli/runner.rb +69 -0
- data/lib/ace/support/cli/standard_options.rb +14 -0
- data/lib/ace/support/cli/version.rb +9 -0
- data/lib/ace/support/cli.rb +36 -0
- metadata +71 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
require_relative "errors"
|
|
5
|
+
|
|
6
|
+
module Ace
|
|
7
|
+
module Support
|
|
8
|
+
module Cli
|
|
9
|
+
class Parser
|
|
10
|
+
def initialize(command_class, command_name: nil)
|
|
11
|
+
@command_class = command_class
|
|
12
|
+
@command_name = command_name
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def parse(args)
|
|
16
|
+
options = build_defaults
|
|
17
|
+
remaining = args.dup
|
|
18
|
+
|
|
19
|
+
if help_requested?(remaining) && rich_help?
|
|
20
|
+
render_help(remaining)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
parser = OptionParser.new
|
|
24
|
+
parser.banner = "Usage: #{File.basename($0)} #{command_label} [options]"
|
|
25
|
+
|
|
26
|
+
configure_options(parser, options)
|
|
27
|
+
|
|
28
|
+
parser.parse!(remaining)
|
|
29
|
+
|
|
30
|
+
apply_positionals(options, remaining)
|
|
31
|
+
validate_required_options!(options)
|
|
32
|
+
|
|
33
|
+
options
|
|
34
|
+
rescue OptionParser::ParseError => e
|
|
35
|
+
raise ParseError, parse_error_message(e)
|
|
36
|
+
rescue ArgumentError => e
|
|
37
|
+
raise ParseError, e.message
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
attr_reader :command_class
|
|
43
|
+
|
|
44
|
+
def help_requested?(args)
|
|
45
|
+
tokens = tokens_before_separator(args)
|
|
46
|
+
tokens.include?("--help") || tokens.include?("-h")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def rich_help?
|
|
50
|
+
has_desc = command_class.respond_to?(:description) && !command_class.description.nil?
|
|
51
|
+
has_examples = command_class.respond_to?(:examples) && !command_class.examples.empty?
|
|
52
|
+
has_desc || has_examples
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def render_help(args)
|
|
56
|
+
name = resolved_command_name
|
|
57
|
+
output = Ace::Support::Cli::Help::TwoTierHelp.render(command_class, name, args: args)
|
|
58
|
+
raise HelpRendered.new(output)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def tokens_before_separator(args)
|
|
62
|
+
separator = args.index("--")
|
|
63
|
+
separator ? args.first(separator) : args
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def resolved_command_name
|
|
67
|
+
return @command_name if @command_name && !@command_name.empty?
|
|
68
|
+
|
|
69
|
+
program = File.basename($PROGRAM_NAME)
|
|
70
|
+
label = command_label
|
|
71
|
+
(label == "command") ? program : "#{program} #{label}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def build_defaults
|
|
75
|
+
command_class.options.each_with_object({}) do |option, hash|
|
|
76
|
+
hash[option.name] = duplicate_default(option.default)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def duplicate_default(value)
|
|
81
|
+
case value
|
|
82
|
+
when Array then value.dup
|
|
83
|
+
when Hash then value.dup
|
|
84
|
+
else value
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def configure_options(parser, options)
|
|
89
|
+
command_class.options.each do |option|
|
|
90
|
+
desc = option.desc.to_s
|
|
91
|
+
|
|
92
|
+
case option.type
|
|
93
|
+
when :boolean
|
|
94
|
+
parser.on(*option.aliases, "--[no-]#{option.name.to_s.tr("_", "-")}", desc) do |value|
|
|
95
|
+
options[option.name] = value
|
|
96
|
+
end
|
|
97
|
+
when :integer
|
|
98
|
+
switches = value_switches(option, "N")
|
|
99
|
+
parser.on(*switches, Integer, desc) { |value| assign_option_value(options, option, value) }
|
|
100
|
+
when :float
|
|
101
|
+
switches = value_switches(option, "N")
|
|
102
|
+
parser.on(*switches, Float, desc) { |value| assign_option_value(options, option, value) }
|
|
103
|
+
when :array
|
|
104
|
+
switches = value_switches(option, "A,B")
|
|
105
|
+
parser.on(*switches, Array, desc) do |value|
|
|
106
|
+
current = Array(options[option.name])
|
|
107
|
+
parsed_values = value.nil? ? [] : Array(value)
|
|
108
|
+
options[option.name] = current + parsed_values
|
|
109
|
+
end
|
|
110
|
+
when :hash
|
|
111
|
+
switches = value_switches(option, "KEY=VALUE")
|
|
112
|
+
parser.on(*switches, String, desc) do |value|
|
|
113
|
+
key, parsed_value = parse_hash_pair(value, option)
|
|
114
|
+
current = options[option.name] || {}
|
|
115
|
+
options[option.name] = current.merge(key => parsed_value)
|
|
116
|
+
end
|
|
117
|
+
else
|
|
118
|
+
switches = value_switches(option, "VALUE")
|
|
119
|
+
parser.on(*switches, String, desc) { |value| assign_option_value(options, option, value) }
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def assign_option_value(options, option, value)
|
|
125
|
+
if option.repeat
|
|
126
|
+
current = Array(options[option.name])
|
|
127
|
+
options[option.name] = current + [value]
|
|
128
|
+
else
|
|
129
|
+
options[option.name] = value
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def value_switches(option, value_label)
|
|
134
|
+
(option.aliases + [option.long_switch]).map do |switch|
|
|
135
|
+
"#{switch} #{value_label}"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def parse_hash_pair(value, option)
|
|
140
|
+
parts = value.split(/[=:]/, 2)
|
|
141
|
+
raise ArgumentError, "Invalid value for #{option.long_switch}: expected key=value" if parts.length < 2
|
|
142
|
+
|
|
143
|
+
parts
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def apply_positionals(options, remaining)
|
|
147
|
+
cursor = 0
|
|
148
|
+
command_class.arguments.each do |argument|
|
|
149
|
+
if argument.type == :array
|
|
150
|
+
values = remaining.drop(cursor)
|
|
151
|
+
if values.empty? && argument.required
|
|
152
|
+
raise ArgumentError, "Missing required argument: #{argument.name}"
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
options[argument.name] = values
|
|
156
|
+
cursor = remaining.length
|
|
157
|
+
break
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
raw_value = remaining[cursor]
|
|
161
|
+
if raw_value.nil?
|
|
162
|
+
raise ArgumentError, "Missing required argument: #{argument.name}" if argument.required
|
|
163
|
+
next
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
options[argument.name] = coerce_argument(raw_value, argument)
|
|
167
|
+
cursor += 1
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
return unless remaining.length > cursor
|
|
171
|
+
if accepts_args_keyword?
|
|
172
|
+
options[:args] = remaining.drop(cursor)
|
|
173
|
+
return
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
extra = remaining.drop(cursor).join(" ")
|
|
177
|
+
raise ArgumentError, "Unexpected arguments: #{extra}"
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def accepts_args_keyword?
|
|
181
|
+
command_class.instance_method(:call).parameters.any? do |kind, name|
|
|
182
|
+
%i[key keyreq].include?(kind) && name == :args
|
|
183
|
+
end
|
|
184
|
+
rescue NameError
|
|
185
|
+
false
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def coerce_argument(value, argument)
|
|
189
|
+
case argument.type
|
|
190
|
+
when :integer
|
|
191
|
+
Integer(value)
|
|
192
|
+
when :float
|
|
193
|
+
Float(value)
|
|
194
|
+
when :boolean
|
|
195
|
+
coerce_boolean(value)
|
|
196
|
+
else
|
|
197
|
+
value
|
|
198
|
+
end
|
|
199
|
+
rescue ArgumentError
|
|
200
|
+
raise ArgumentError, "Invalid value for argument #{argument.name}: expected #{argument.type}"
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def coerce_boolean(value)
|
|
204
|
+
return true if value == true || value.to_s.casecmp("true").zero?
|
|
205
|
+
return false if value == false || value.to_s.casecmp("false").zero?
|
|
206
|
+
|
|
207
|
+
raise ArgumentError
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def validate_required_options!(options)
|
|
211
|
+
missing = command_class.options.select do |option|
|
|
212
|
+
option.required && (options[option.name].nil? || options[option.name] == "")
|
|
213
|
+
end
|
|
214
|
+
return if missing.empty?
|
|
215
|
+
|
|
216
|
+
flags = missing.map(&:long_switch).join(", ")
|
|
217
|
+
raise ArgumentError, "Missing required options: #{flags}"
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def command_label
|
|
221
|
+
raw = command_class.name.to_s
|
|
222
|
+
token = raw.empty? ? "command" : (raw.split("::").last || "command")
|
|
223
|
+
token.gsub(/([a-z])([A-Z])/, '\1-\2').downcase
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def parse_error_message(error)
|
|
227
|
+
return "#{error.message}. Did you mean: #{suggest_for(error)}" if error.is_a?(OptionParser::InvalidOption)
|
|
228
|
+
|
|
229
|
+
error.message
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def suggest_for(error)
|
|
233
|
+
token = error.args.first.to_s
|
|
234
|
+
return "(no suggestion)" if token.empty?
|
|
235
|
+
|
|
236
|
+
candidates = command_class.options.flat_map do |option|
|
|
237
|
+
option.aliases + [option.long_switch]
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
ranked = candidates.sort_by do |candidate|
|
|
241
|
+
levenshtein(token, candidate)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
ranked.first || "(no suggestion)"
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def levenshtein(source, target)
|
|
248
|
+
m = source.length
|
|
249
|
+
n = target.length
|
|
250
|
+
return n if m.zero?
|
|
251
|
+
return m if n.zero?
|
|
252
|
+
|
|
253
|
+
matrix = Array.new(m + 1) { |i| [i] + [0] * n }
|
|
254
|
+
(0..n).each { |j| matrix[0][j] = j }
|
|
255
|
+
|
|
256
|
+
(1..m).each do |i|
|
|
257
|
+
(1..n).each do |j|
|
|
258
|
+
cost = (source[i - 1] == target[j - 1]) ? 0 : 1
|
|
259
|
+
matrix[i][j] = [
|
|
260
|
+
matrix[i - 1][j] + 1,
|
|
261
|
+
matrix[i][j - 1] + 1,
|
|
262
|
+
matrix[i - 1][j - 1] + cost
|
|
263
|
+
].min
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
matrix[m][n]
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
|
|
5
|
+
module Ace
|
|
6
|
+
module Support
|
|
7
|
+
module Cli
|
|
8
|
+
class Registry
|
|
9
|
+
Node = Struct.new(:command, :children)
|
|
10
|
+
|
|
11
|
+
attr_reader :version
|
|
12
|
+
|
|
13
|
+
def initialize(version: nil)
|
|
14
|
+
@version = version
|
|
15
|
+
@root = Node.new(nil, {})
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def register(name, command_class = nil)
|
|
19
|
+
node = ensure_path(name)
|
|
20
|
+
node.command = command_class if command_class
|
|
21
|
+
yield NestedRegistry.new(node) if block_given?
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def resolve(args)
|
|
26
|
+
raise CommandNotFoundError, "No commands registered" if @root.children.empty?
|
|
27
|
+
raise CommandNotFoundError, "No command provided" if args.empty?
|
|
28
|
+
|
|
29
|
+
tokens = args.dup
|
|
30
|
+
node = @root
|
|
31
|
+
consumed = 0
|
|
32
|
+
|
|
33
|
+
tokens.each do |token|
|
|
34
|
+
child = node.children[token]
|
|
35
|
+
break unless child
|
|
36
|
+
|
|
37
|
+
node = child
|
|
38
|
+
consumed += 1
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
unless node.command
|
|
42
|
+
attempted = tokens.first(consumed + 1).join(" ")
|
|
43
|
+
raise CommandNotFoundError, "Command not found: #{attempted.strip}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
[node.command, tokens.drop(consumed)]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def commands
|
|
50
|
+
@root.children.each_with_object({}) do |(name, node), hash|
|
|
51
|
+
hash[name] = node.command || NestedRegistry.new(node)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def ensure_path(name)
|
|
58
|
+
parts = name.to_s.split(" ")
|
|
59
|
+
raise ArgumentError, "Command name cannot be empty" if parts.empty?
|
|
60
|
+
|
|
61
|
+
parts.reduce(@root) do |node, part|
|
|
62
|
+
node.children[part] ||= Node.new(nil, {})
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class NestedRegistry
|
|
67
|
+
def initialize(node)
|
|
68
|
+
@node = node
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def register(name, command_class = nil)
|
|
72
|
+
parts = name.to_s.split(" ")
|
|
73
|
+
raise ArgumentError, "Command name cannot be empty" if parts.empty?
|
|
74
|
+
|
|
75
|
+
node = parts.reduce(@node) do |current, part|
|
|
76
|
+
current.children[part] ||= Node.new(nil, {})
|
|
77
|
+
end
|
|
78
|
+
node.command = command_class if command_class
|
|
79
|
+
yield NestedRegistry.new(node) if block_given?
|
|
80
|
+
self
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "registry"
|
|
4
|
+
require_relative "error"
|
|
5
|
+
|
|
6
|
+
module Ace
|
|
7
|
+
module Support
|
|
8
|
+
module Cli
|
|
9
|
+
# DSL adapter allowing CLI modules to keep `register` semantics
|
|
10
|
+
# while using Ace::Support::Cli::Registry under the hood.
|
|
11
|
+
module RegistryDsl
|
|
12
|
+
def self.extended(base)
|
|
13
|
+
base.instance_variable_set(:@registry, Ace::Support::Cli::Registry.new)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def register(name, command_class = nil, *_args, aliases: nil, **_kwargs)
|
|
17
|
+
registry.register(name, normalize_command(command_class))
|
|
18
|
+
Array(aliases).each do |aliaz|
|
|
19
|
+
registry.register(aliaz, normalize_command(command_class))
|
|
20
|
+
end
|
|
21
|
+
self
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def resolve(args)
|
|
25
|
+
return registry.resolve(args) unless args.empty?
|
|
26
|
+
|
|
27
|
+
registry.resolve(["--help"])
|
|
28
|
+
rescue Ace::Support::Cli::CommandNotFoundError
|
|
29
|
+
raise Ace::Support::Cli::Error.new("unknown command", exit_code: 1)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def registry
|
|
35
|
+
@registry ||= Ace::Support::Cli::Registry.new
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def normalize_command(command_class)
|
|
39
|
+
command_class
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "parser"
|
|
4
|
+
|
|
5
|
+
module Ace
|
|
6
|
+
module Support
|
|
7
|
+
module Cli
|
|
8
|
+
class Runner
|
|
9
|
+
def initialize(registry, parser_class: Parser)
|
|
10
|
+
@registry = registry
|
|
11
|
+
@parser_class = parser_class
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def call(args: ARGV)
|
|
15
|
+
if root_help_request?(args)
|
|
16
|
+
puts Ace::Support::Cli::Help::Usage.new(@registry, program_name: resolve_program_name).render
|
|
17
|
+
return 0
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
command_target, remaining, command_name = resolve_target(args)
|
|
21
|
+
command_class = command_target.is_a?(Class) ? command_target : command_target.class
|
|
22
|
+
parsed = @parser_class.new(command_class, command_name: command_name).parse(remaining)
|
|
23
|
+
result = if command_target.is_a?(Class)
|
|
24
|
+
command_target.new.call(**parsed)
|
|
25
|
+
else
|
|
26
|
+
command_target.call(**parsed)
|
|
27
|
+
end
|
|
28
|
+
result.nil? ? 0 : result
|
|
29
|
+
rescue Ace::Support::Cli::HelpRendered => e
|
|
30
|
+
puts e.output
|
|
31
|
+
e.status
|
|
32
|
+
rescue Ace::Support::Cli::ParseError => e
|
|
33
|
+
raise Ace::Support::Cli::Error.new(e.message)
|
|
34
|
+
rescue Ace::Support::Cli::CommandNotFoundError => e
|
|
35
|
+
raise Ace::Support::Cli::Error.new(e.message)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def root_help_request?(args)
|
|
41
|
+
@registry.respond_to?(:resolve) && (%w[--help -h].include?(args.first) || args.empty?)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def resolve_target(args)
|
|
45
|
+
program = resolve_program_name
|
|
46
|
+
if @registry.respond_to?(:resolve)
|
|
47
|
+
command, remaining = @registry.resolve(args)
|
|
48
|
+
consumed = args.length - remaining.length
|
|
49
|
+
name = ([program] + args.first(consumed)).join(" ")
|
|
50
|
+
[command, remaining, name]
|
|
51
|
+
else
|
|
52
|
+
normalized = args.dup
|
|
53
|
+
token = @registry.name.to_s.split("::").last.gsub(/([a-z])([A-Z])/, '\1-\2').downcase
|
|
54
|
+
normalized.shift if !token.empty? && normalized.first == token
|
|
55
|
+
[@registry, normalized, program]
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def resolve_program_name
|
|
60
|
+
if @registry.respond_to?(:const_defined?) && @registry.const_defined?(:PROGRAM_NAME)
|
|
61
|
+
@registry.const_get(:PROGRAM_NAME)
|
|
62
|
+
else
|
|
63
|
+
File.basename($PROGRAM_NAME)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ace
|
|
4
|
+
module Support
|
|
5
|
+
module Cli
|
|
6
|
+
module StandardOptions
|
|
7
|
+
QUIET_DESC = "Suppress non-essential output"
|
|
8
|
+
VERBOSE_DESC = "Show verbose output"
|
|
9
|
+
DEBUG_DESC = "Show debug output"
|
|
10
|
+
HELP_DESC = "Show this help"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "cli/version"
|
|
4
|
+
require_relative "cli/errors"
|
|
5
|
+
require_relative "cli/error"
|
|
6
|
+
require_relative "cli/standard_options"
|
|
7
|
+
require_relative "cli/base"
|
|
8
|
+
require_relative "cli/registry_dsl"
|
|
9
|
+
require_relative "cli/models/option"
|
|
10
|
+
require_relative "cli/models/argument"
|
|
11
|
+
require_relative "cli/command"
|
|
12
|
+
require_relative "cli/parser"
|
|
13
|
+
require_relative "cli/argv_coalescer"
|
|
14
|
+
require_relative "cli/registry"
|
|
15
|
+
require_relative "cli/runner"
|
|
16
|
+
require_relative "cli/help/banner"
|
|
17
|
+
require_relative "cli/help/usage"
|
|
18
|
+
require_relative "cli/help/concise"
|
|
19
|
+
require_relative "cli/help/help_command"
|
|
20
|
+
require_relative "cli/help/version_command"
|
|
21
|
+
require_relative "cli/help/two_tier_help"
|
|
22
|
+
|
|
23
|
+
module Ace
|
|
24
|
+
module Support
|
|
25
|
+
module Cli
|
|
26
|
+
Banner = Help::Banner
|
|
27
|
+
HelpConcise = Help::Concise
|
|
28
|
+
HelpCommand = Help::HelpCommand
|
|
29
|
+
VersionCommand = Help::VersionCommand
|
|
30
|
+
TwoTierHelp = Help::TwoTierHelp
|
|
31
|
+
|
|
32
|
+
class Usage < Help::Usage
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ace-support-cli
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.6.2
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Michal Czyz
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Provides command DSL, option parsing, registry routing, and runner primitives
|
|
13
|
+
for ACE CLI tools.
|
|
14
|
+
email:
|
|
15
|
+
- mc@cs3b.com
|
|
16
|
+
executables:
|
|
17
|
+
- ace-support-cli
|
|
18
|
+
extensions: []
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- CHANGELOG.md
|
|
22
|
+
- LICENSE
|
|
23
|
+
- README.md
|
|
24
|
+
- Rakefile
|
|
25
|
+
- exe/ace-support-cli
|
|
26
|
+
- lib/ace/support/cli.rb
|
|
27
|
+
- lib/ace/support/cli/argv_coalescer.rb
|
|
28
|
+
- lib/ace/support/cli/base.rb
|
|
29
|
+
- lib/ace/support/cli/command.rb
|
|
30
|
+
- lib/ace/support/cli/error.rb
|
|
31
|
+
- lib/ace/support/cli/errors.rb
|
|
32
|
+
- lib/ace/support/cli/help/banner.rb
|
|
33
|
+
- lib/ace/support/cli/help/concise.rb
|
|
34
|
+
- lib/ace/support/cli/help/help_command.rb
|
|
35
|
+
- lib/ace/support/cli/help/two_tier_help.rb
|
|
36
|
+
- lib/ace/support/cli/help/usage.rb
|
|
37
|
+
- lib/ace/support/cli/help/version_command.rb
|
|
38
|
+
- lib/ace/support/cli/models/argument.rb
|
|
39
|
+
- lib/ace/support/cli/models/option.rb
|
|
40
|
+
- lib/ace/support/cli/parser.rb
|
|
41
|
+
- lib/ace/support/cli/registry.rb
|
|
42
|
+
- lib/ace/support/cli/registry_dsl.rb
|
|
43
|
+
- lib/ace/support/cli/runner.rb
|
|
44
|
+
- lib/ace/support/cli/standard_options.rb
|
|
45
|
+
- lib/ace/support/cli/version.rb
|
|
46
|
+
homepage: https://github.com/cs3b/ace
|
|
47
|
+
licenses:
|
|
48
|
+
- MIT
|
|
49
|
+
metadata:
|
|
50
|
+
allowed_push_host: https://rubygems.org
|
|
51
|
+
homepage_uri: https://github.com/cs3b/ace
|
|
52
|
+
source_code_uri: https://github.com/cs3b/ace/tree/main/ace-support-cli/
|
|
53
|
+
changelog_uri: https://github.com/cs3b/ace/blob/main/ace-support-cli/CHANGELOG.md
|
|
54
|
+
rdoc_options: []
|
|
55
|
+
require_paths:
|
|
56
|
+
- lib
|
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: 3.2.0
|
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
67
|
+
requirements: []
|
|
68
|
+
rubygems_version: 3.6.9
|
|
69
|
+
specification_version: 4
|
|
70
|
+
summary: CLI command framework for ACE gems
|
|
71
|
+
test_files: []
|