dry-cli 0.6.0 → 0.7.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/CHANGELOG.md +66 -37
- data/LICENSE +1 -1
- data/README.md +1 -1
- data/dry-cli.gemspec +2 -3
- data/lib/dry/cli.rb +53 -34
- data/lib/dry/cli/banner.rb +31 -21
- data/lib/dry/cli/command.rb +51 -20
- data/lib/dry/cli/command_registry.rb +59 -34
- data/lib/dry/cli/inflector.rb +1 -1
- data/lib/dry/cli/inline.rb +4 -4
- data/lib/dry/cli/option.rb +2 -2
- data/lib/dry/cli/parser.rb +16 -18
- data/lib/dry/cli/program_name.rb +1 -1
- data/lib/dry/cli/registry.rb +11 -6
- data/lib/dry/cli/usage.rb +11 -8
- data/lib/dry/cli/version.rb +1 -1
- metadata +18 -19
- data/lib/dry/cli/utils/files.rb +0 -444
data/lib/dry/cli/command.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require 'dry/cli/option'
|
3
|
+
require "forwardable"
|
4
|
+
require "dry/cli/option"
|
6
5
|
|
7
6
|
module Dry
|
8
7
|
class CLI
|
@@ -14,25 +13,20 @@ module Dry
|
|
14
13
|
# @api private
|
15
14
|
def self.inherited(base)
|
16
15
|
super
|
16
|
+
base.class_eval do
|
17
|
+
@_mutex = Mutex.new
|
18
|
+
@description = nil
|
19
|
+
@examples = []
|
20
|
+
@subcommands = []
|
21
|
+
@arguments = base.superclass_arguments || []
|
22
|
+
@options = base.superclass_options || []
|
23
|
+
end
|
17
24
|
base.extend ClassMethods
|
18
25
|
end
|
19
26
|
|
20
27
|
# @since 0.1.0
|
21
28
|
# @api private
|
22
29
|
module ClassMethods
|
23
|
-
# @since 0.1.0
|
24
|
-
# @api private
|
25
|
-
def self.extended(base)
|
26
|
-
super
|
27
|
-
|
28
|
-
base.class_eval do
|
29
|
-
@description = nil
|
30
|
-
@examples = Concurrent::Array.new
|
31
|
-
@arguments = Concurrent::Array.new
|
32
|
-
@options = Concurrent::Array.new
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
30
|
# @since 0.1.0
|
37
31
|
# @api private
|
38
32
|
attr_reader :description
|
@@ -48,6 +42,14 @@ module Dry
|
|
48
42
|
# @since 0.1.0
|
49
43
|
# @api private
|
50
44
|
attr_reader :options
|
45
|
+
|
46
|
+
# @since 0.7.0
|
47
|
+
# @api private
|
48
|
+
attr_reader :subcommands
|
49
|
+
|
50
|
+
# @since 0.7.0
|
51
|
+
# @api private
|
52
|
+
attr_writer :subcommands
|
51
53
|
end
|
52
54
|
|
53
55
|
# Set the description of the command
|
@@ -103,7 +105,7 @@ module Dry
|
|
103
105
|
# # foo server --port=2306 # Bind to a port
|
104
106
|
# # foo server --no-code-reloading # Disable code reloading
|
105
107
|
def self.example(*examples)
|
106
|
-
@examples += examples.flatten
|
108
|
+
@examples += examples.flatten(1)
|
107
109
|
end
|
108
110
|
|
109
111
|
# Specify an argument
|
@@ -242,7 +244,7 @@ module Dry
|
|
242
244
|
# # starting console (engine: pry)
|
243
245
|
#
|
244
246
|
# # $ foo console --engine=foo
|
245
|
-
# #
|
247
|
+
# # ERROR: Invalid param provided
|
246
248
|
#
|
247
249
|
# @example Description
|
248
250
|
# require "dry/cli"
|
@@ -317,7 +319,9 @@ module Dry
|
|
317
319
|
# @since 0.1.0
|
318
320
|
# @api private
|
319
321
|
def self.params
|
320
|
-
|
322
|
+
@_mutex.synchronize do
|
323
|
+
(@arguments + @options).uniq
|
324
|
+
end
|
321
325
|
end
|
322
326
|
|
323
327
|
# @since 0.1.0
|
@@ -340,6 +344,32 @@ module Dry
|
|
340
344
|
arguments.reject(&:required?)
|
341
345
|
end
|
342
346
|
|
347
|
+
# @since 0.7.0
|
348
|
+
# @api private
|
349
|
+
def self.subcommands
|
350
|
+
subcommands
|
351
|
+
end
|
352
|
+
|
353
|
+
# @since 0.7.0
|
354
|
+
# @api private
|
355
|
+
def self.superclass_variable_dup(var)
|
356
|
+
if superclass.instance_variable_defined?(var)
|
357
|
+
superclass.instance_variable_get(var).dup
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
# @since 0.7.0
|
362
|
+
# @api private
|
363
|
+
def self.superclass_arguments
|
364
|
+
superclass_variable_dup(:@arguments)
|
365
|
+
end
|
366
|
+
|
367
|
+
# @since 0.7.0
|
368
|
+
# @api private
|
369
|
+
def self.superclass_options
|
370
|
+
superclass_variable_dup(:@options)
|
371
|
+
end
|
372
|
+
|
343
373
|
extend Forwardable
|
344
374
|
|
345
375
|
delegate %i[
|
@@ -351,7 +381,8 @@ module Dry
|
|
351
381
|
default_params
|
352
382
|
required_arguments
|
353
383
|
optional_arguments
|
354
|
-
|
384
|
+
subcommands
|
385
|
+
] => "self.class"
|
355
386
|
end
|
356
387
|
end
|
357
388
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require 'concurrent/hash'
|
3
|
+
require "set"
|
5
4
|
|
6
5
|
module Dry
|
7
6
|
class CLI
|
@@ -13,52 +12,65 @@ module Dry
|
|
13
12
|
# @since 0.1.0
|
14
13
|
# @api private
|
15
14
|
def initialize
|
15
|
+
@_mutex = Mutex.new
|
16
16
|
@root = Node.new
|
17
17
|
end
|
18
18
|
|
19
19
|
# @since 0.1.0
|
20
20
|
# @api private
|
21
21
|
def set(name, command, aliases)
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
@_mutex.synchronize do
|
23
|
+
node = @root
|
24
|
+
name.split(/[[:space:]]/).each do |token|
|
25
|
+
node = node.put(node, token)
|
26
|
+
end
|
26
27
|
|
27
|
-
|
28
|
-
|
28
|
+
node.aliases!(aliases)
|
29
|
+
if command
|
30
|
+
node.leaf!(command)
|
31
|
+
node.subcommands!(command)
|
32
|
+
end
|
29
33
|
|
30
|
-
|
34
|
+
nil
|
35
|
+
end
|
31
36
|
end
|
32
37
|
|
33
38
|
# @since 0.1.0
|
34
39
|
# @api private
|
35
40
|
#
|
36
41
|
def get(arguments)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
42
|
+
@_mutex.synchronize do
|
43
|
+
node = @root
|
44
|
+
args = []
|
45
|
+
names = []
|
46
|
+
valid_leaf = nil
|
47
|
+
result = LookupResult.new(node, args, names, node.leaf?)
|
48
|
+
|
49
|
+
arguments.each_with_index do |token, i|
|
50
|
+
tmp = node.lookup(token)
|
51
|
+
|
52
|
+
if tmp.nil? && valid_leaf
|
53
|
+
result = valid_leaf
|
54
|
+
break
|
55
|
+
elsif tmp.nil?
|
56
|
+
result = LookupResult.new(node, args, names, false)
|
57
|
+
break
|
58
|
+
elsif tmp.leaf?
|
59
|
+
args = arguments[i + 1..-1]
|
60
|
+
names = arguments[0..i]
|
61
|
+
node = tmp
|
62
|
+
result = LookupResult.new(node, args, names, true)
|
63
|
+
valid_leaf = result
|
64
|
+
break unless tmp.children?
|
65
|
+
else
|
66
|
+
names = arguments[0..i]
|
67
|
+
node = tmp
|
68
|
+
result = LookupResult.new(node, args, names, node.leaf?)
|
69
|
+
end
|
58
70
|
end
|
59
|
-
end
|
60
71
|
|
61
|
-
|
72
|
+
result
|
73
|
+
end
|
62
74
|
end
|
63
75
|
|
64
76
|
# Node of the registry
|
@@ -94,8 +106,8 @@ module Dry
|
|
94
106
|
# @api private
|
95
107
|
def initialize(parent = nil)
|
96
108
|
@parent = parent
|
97
|
-
@children =
|
98
|
-
@aliases =
|
109
|
+
@children = {}
|
110
|
+
@aliases = {}
|
99
111
|
@command = nil
|
100
112
|
|
101
113
|
@before_callbacks = Chain.new
|
@@ -120,6 +132,13 @@ module Dry
|
|
120
132
|
@command = command
|
121
133
|
end
|
122
134
|
|
135
|
+
# @since 0.7.0
|
136
|
+
# @api private
|
137
|
+
def subcommands!(command)
|
138
|
+
command_class = command.is_a?(Class) ? command : command.class
|
139
|
+
command_class.subcommands = children
|
140
|
+
end
|
141
|
+
|
123
142
|
# @since 0.1.0
|
124
143
|
# @api private
|
125
144
|
def alias!(key, child)
|
@@ -139,6 +158,12 @@ module Dry
|
|
139
158
|
def leaf?
|
140
159
|
!command.nil?
|
141
160
|
end
|
161
|
+
|
162
|
+
# @since 0.7.0
|
163
|
+
# @api private
|
164
|
+
def children?
|
165
|
+
children.any?
|
166
|
+
end
|
142
167
|
end
|
143
168
|
|
144
169
|
# Result of a registry lookup
|
data/lib/dry/cli/inflector.rb
CHANGED
data/lib/dry/cli/inline.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "backports/2.5.0/module/define_method" if RUBY_VERSION < "2.5"
|
4
4
|
|
5
5
|
module Dry
|
6
6
|
class CLI
|
7
|
-
require
|
7
|
+
require "dry/cli"
|
8
8
|
# Inline Syntax (aka DSL) to implement one-file applications
|
9
9
|
#
|
10
10
|
# `dry/cli/inline` is not required by default
|
@@ -62,8 +62,8 @@ module Dry
|
|
62
62
|
# @since 0.6.0
|
63
63
|
def run(arguments: ARGV, out: $stdout)
|
64
64
|
command = AnonymousCommand
|
65
|
-
command.define_method(:call) do
|
66
|
-
yield(
|
65
|
+
command.define_method(:call) do |**args|
|
66
|
+
yield(**args)
|
67
67
|
end
|
68
68
|
|
69
69
|
Dry.CLI(command).call(arguments: arguments, out: out)
|
data/lib/dry/cli/option.rb
CHANGED
@@ -32,7 +32,7 @@ module Dry
|
|
32
32
|
# @api private
|
33
33
|
def desc
|
34
34
|
desc = options[:desc]
|
35
|
-
values ? "#{desc}: (#{values.join(
|
35
|
+
values ? "#{desc}: (#{values.join("/")})" : desc
|
36
36
|
end
|
37
37
|
|
38
38
|
# @since 0.1.0
|
@@ -108,7 +108,7 @@ module Dry
|
|
108
108
|
# @api private
|
109
109
|
def alias_names
|
110
110
|
aliases
|
111
|
-
.map { |name| name.gsub(/^-{1,2}/,
|
111
|
+
.map { |name| name.gsub(/^-{1,2}/, "") }
|
112
112
|
.compact
|
113
113
|
.uniq
|
114
114
|
.map { |name| name.size == 1 ? "-#{name}" : "--#{name}" }
|
data/lib/dry/cli/parser.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "optparse"
|
4
|
+
require "dry/cli/program_name"
|
5
5
|
|
6
6
|
module Dry
|
7
7
|
class CLI
|
@@ -13,7 +13,7 @@ module Dry
|
|
13
13
|
# @since 0.1.0
|
14
14
|
# @api private
|
15
15
|
#
|
16
|
-
def self.call(command, arguments,
|
16
|
+
def self.call(command, arguments, prog_name)
|
17
17
|
original_arguments = arguments.dup
|
18
18
|
parsed_options = {}
|
19
19
|
|
@@ -24,28 +24,22 @@ module Dry
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
opts.on_tail(
|
27
|
+
opts.on_tail("-h", "--help") do
|
28
28
|
return Result.help
|
29
29
|
end
|
30
30
|
end.parse!(arguments)
|
31
31
|
|
32
32
|
parsed_options = command.default_params.merge(parsed_options)
|
33
|
-
parse_required_params(command, arguments,
|
33
|
+
parse_required_params(command, arguments, prog_name, parsed_options)
|
34
34
|
rescue ::OptionParser::ParseError
|
35
|
-
Result.failure("
|
36
|
-
end
|
37
|
-
|
38
|
-
# @since 0.1.0
|
39
|
-
# @api private
|
40
|
-
def self.full_command_name(names)
|
41
|
-
ProgramName.call(names)
|
35
|
+
Result.failure("ERROR: \"#{prog_name}\" was called with arguments \"#{original_arguments.join(" ")}\"") # rubocop:disable Metrics/LineLength
|
42
36
|
end
|
43
37
|
|
44
38
|
# @since 0.1.0
|
45
39
|
# @api private
|
46
40
|
#
|
47
41
|
# rubocop:disable Metrics/AbcSize
|
48
|
-
def self.parse_required_params(command, arguments,
|
42
|
+
def self.parse_required_params(command, arguments, prog_name, parsed_options)
|
49
43
|
parsed_params = match_arguments(command.arguments, arguments)
|
50
44
|
parsed_required_params = match_arguments(command.required_arguments, arguments)
|
51
45
|
all_required_params_satisfied = command.required_arguments.all? { |param| !parsed_required_params[param.name].nil? } # rubocop:disable Metrics/LineLength
|
@@ -55,12 +49,16 @@ module Dry
|
|
55
49
|
unless all_required_params_satisfied
|
56
50
|
parsed_required_params_values = parsed_required_params.values.compact
|
57
51
|
|
58
|
-
usage = "\nUsage: \"#{
|
52
|
+
usage = "\nUsage: \"#{prog_name} #{command.required_arguments.map(&:description_name).join(" ")}" # rubocop:disable Metrics/LineLength
|
53
|
+
|
54
|
+
usage += " | #{prog_name} SUBCOMMAND" if command.subcommands.any?
|
55
|
+
|
56
|
+
usage += '"'
|
59
57
|
|
60
|
-
if parsed_required_params_values.empty?
|
61
|
-
return Result.failure("ERROR: \"#{
|
58
|
+
if parsed_required_params_values.empty?
|
59
|
+
return Result.failure("ERROR: \"#{prog_name}\" was called with no arguments#{usage}")
|
62
60
|
else
|
63
|
-
return Result.failure("ERROR: \"#{
|
61
|
+
return Result.failure("ERROR: \"#{prog_name}\" was called with arguments #{parsed_required_params_values}#{usage}") # rubocop:disable Metrics/LineLength
|
64
62
|
end
|
65
63
|
end
|
66
64
|
|
@@ -103,7 +101,7 @@ module Dry
|
|
103
101
|
|
104
102
|
# @since 0.1.0
|
105
103
|
# @api private
|
106
|
-
def self.failure(error =
|
104
|
+
def self.failure(error = "Error: Invalid param provided")
|
107
105
|
new(error: error)
|
108
106
|
end
|
109
107
|
|
data/lib/dry/cli/program_name.rb
CHANGED
data/lib/dry/cli/registry.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "dry/cli/command_registry"
|
4
4
|
|
5
5
|
module Dry
|
6
6
|
class CLI
|
@@ -12,6 +12,7 @@ module Dry
|
|
12
12
|
# @api private
|
13
13
|
def self.extended(base)
|
14
14
|
base.class_eval do
|
15
|
+
@_mutex = Mutex.new
|
15
16
|
@commands = CommandRegistry.new
|
16
17
|
end
|
17
18
|
end
|
@@ -75,6 +76,8 @@ module Dry
|
|
75
76
|
# end
|
76
77
|
# end
|
77
78
|
def register(name, command = nil, aliases: [], &block)
|
79
|
+
@commands.set(name, command, aliases)
|
80
|
+
|
78
81
|
if block_given?
|
79
82
|
prefix = Prefix.new(@commands, name, aliases)
|
80
83
|
if block.arity.zero?
|
@@ -82,8 +85,6 @@ module Dry
|
|
82
85
|
else
|
83
86
|
yield(prefix)
|
84
87
|
end
|
85
|
-
else
|
86
|
-
@commands.set(name, command, aliases)
|
87
88
|
end
|
88
89
|
end
|
89
90
|
|
@@ -170,7 +171,9 @@ module Dry
|
|
170
171
|
# end
|
171
172
|
# end
|
172
173
|
def before(command_name, callback = nil, &blk)
|
173
|
-
|
174
|
+
@_mutex.synchronize do
|
175
|
+
command(command_name).before_callbacks.append(&_callback(callback, blk))
|
176
|
+
end
|
174
177
|
end
|
175
178
|
|
176
179
|
# Register an after callback.
|
@@ -256,7 +259,9 @@ module Dry
|
|
256
259
|
# end
|
257
260
|
# end
|
258
261
|
def after(command_name, callback = nil, &blk)
|
259
|
-
|
262
|
+
@_mutex.synchronize do
|
263
|
+
command(command_name).after_callbacks.append(&_callback(callback, blk))
|
264
|
+
end
|
260
265
|
end
|
261
266
|
|
262
267
|
# @since 0.1.0
|
@@ -267,7 +272,7 @@ module Dry
|
|
267
272
|
|
268
273
|
private
|
269
274
|
|
270
|
-
COMMAND_NAME_SEPARATOR =
|
275
|
+
COMMAND_NAME_SEPARATOR = " "
|
271
276
|
|
272
277
|
# @since 0.2.0
|
273
278
|
# @api private
|