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.
@@ -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