dry-cli 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|