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.
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
4
- require 'concurrent/array'
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
- # # Error: Invalid param provided
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
- (@arguments + @options).uniq
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
- ] => 'self.class'
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 'set'
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
- node = @root
23
- name.split(/[[:space:]]/).each do |token|
24
- node = node.put(node, token)
25
- end
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
- node.aliases!(aliases)
28
- node.leaf!(command) if command
28
+ node.aliases!(aliases)
29
+ if command
30
+ node.leaf!(command)
31
+ node.subcommands!(command)
32
+ end
29
33
 
30
- nil
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
- node = @root
38
- args = []
39
- names = []
40
- result = LookupResult.new(node, args, names, node.leaf?)
41
-
42
- arguments.each_with_index do |token, i|
43
- tmp = node.lookup(token)
44
-
45
- if tmp.nil?
46
- result = LookupResult.new(node, args, names, false)
47
- break
48
- elsif tmp.leaf?
49
- args = arguments[i + 1..-1]
50
- names = arguments[0..i]
51
- node = tmp
52
- result = LookupResult.new(node, args, names, true)
53
- break
54
- else
55
- names = arguments[0..i]
56
- node = tmp
57
- result = LookupResult.new(node, args, names, node.leaf?)
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
- result
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 = Concurrent::Hash.new
98
- @aliases = Concurrent::Hash.new
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
@@ -10,7 +10,7 @@ module Dry
10
10
  def self.dasherize(input)
11
11
  return nil unless input
12
12
 
13
- input.to_s.downcase.gsub(/[[[:space:]]_]/, '-')
13
+ input.to_s.downcase.gsub(/[[[:space:]]_]/, "-")
14
14
  end
15
15
  end
16
16
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'backports/2.5.0/module/define_method' if RUBY_VERSION < '2.5'
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 'dry/cli'
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 |*args|
66
- yield(*args)
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)
@@ -32,7 +32,7 @@ module Dry
32
32
  # @api private
33
33
  def desc
34
34
  desc = options[:desc]
35
- values ? "#{desc}: (#{values.join('/')})" : desc
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}" }
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'optparse'
4
- require 'dry/cli/program_name'
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, names)
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('-h', '--help') do
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, names, parsed_options)
33
+ parse_required_params(command, arguments, prog_name, parsed_options)
34
34
  rescue ::OptionParser::ParseError
35
- Result.failure("Error: \"#{names.last}\" was called with arguments \"#{original_arguments.join(' ')}\"") # rubocop:disable Metrics/LineLength
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, names, parsed_options)
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: \"#{full_command_name(names)} #{command.required_arguments.map(&:description_name).join(' ')}\"" # rubocop:disable Metrics/LineLength
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? # rubocop:disable Style/GuardClause
61
- return Result.failure("ERROR: \"#{full_command_name(names)}\" was called with no arguments#{usage}") # rubocop:disable Metrics/LineLength
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: \"#{full_command_name(names)}\" was called with arguments #{parsed_required_params_values}#{usage}") # rubocop:disable Metrics/LineLength
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 = 'Error: Invalid param provided')
104
+ def self.failure(error = "Error: Invalid param provided")
107
105
  new(error: error)
108
106
  end
109
107
 
@@ -9,7 +9,7 @@ module Dry
9
9
  module ProgramName
10
10
  # @since 0.1.0
11
11
  # @api private
12
- SEPARATOR = ' '
12
+ SEPARATOR = " "
13
13
 
14
14
  # @since 0.1.0
15
15
  # @api private
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'dry/cli/command_registry'
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
- command(command_name).before_callbacks.append(&_callback(callback, blk))
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
- command(command_name).after_callbacks.append(&_callback(callback, blk))
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