cliqr 1.1.0 → 1.2.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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +128 -1
  3. data/README.md +97 -71
  4. data/examples/README.md +12 -0
  5. data/examples/hbase +58 -0
  6. data/examples/my-command +63 -0
  7. data/examples/numbers +55 -0
  8. data/examples/vagrant +90 -0
  9. data/lib/cliqr.rb +17 -2
  10. data/lib/cliqr/argument_validation/argument_type_validator.rb +2 -2
  11. data/lib/cliqr/argument_validation/validator.rb +1 -1
  12. data/lib/cliqr/cli/argument_operator.rb +44 -0
  13. data/lib/cliqr/cli/argument_operator_context.rb +20 -0
  14. data/lib/cliqr/cli/command.rb +1 -1
  15. data/lib/cliqr/cli/command_context.rb +93 -12
  16. data/lib/cliqr/cli/command_runner_factory.rb +2 -2
  17. data/lib/cliqr/cli/config.rb +301 -33
  18. data/lib/cliqr/cli/executor.rb +14 -9
  19. data/lib/cliqr/cli/interface.rb +22 -7
  20. data/lib/cliqr/cli/router.rb +6 -2
  21. data/lib/cliqr/cli/shell_command.rb +69 -0
  22. data/lib/cliqr/cli/usage_builder.rb +185 -0
  23. data/lib/cliqr/config_validation/validator_factory.rb +59 -5
  24. data/lib/cliqr/error.rb +10 -4
  25. data/lib/cliqr/parser/action_token.rb +23 -0
  26. data/lib/cliqr/parser/argument_parser.rb +1 -1
  27. data/lib/cliqr/parser/argument_token.rb +1 -4
  28. data/lib/cliqr/parser/argument_tree_walker.rb +40 -8
  29. data/lib/cliqr/parser/option_token.rb +2 -1
  30. data/lib/cliqr/parser/parsed_input.rb +21 -2
  31. data/lib/cliqr/parser/parsed_input_builder.rb +11 -7
  32. data/lib/cliqr/parser/token.rb +3 -9
  33. data/lib/cliqr/parser/token_factory.rb +1 -1
  34. data/lib/cliqr/util.rb +135 -0
  35. data/lib/cliqr/version.rb +1 -1
  36. data/spec/argument_parser_spec_helper.rb +15 -0
  37. data/spec/config/action_config_validator_spec.rb +146 -0
  38. data/spec/config/config_finalize_spec.rb +1 -1
  39. data/spec/config/config_validator_spec.rb +29 -19
  40. data/spec/config/option_config_validator_spec.rb +13 -13
  41. data/spec/dsl/interface_spec.rb +1 -168
  42. data/spec/dsl/usage_spec.rb +705 -0
  43. data/spec/executor/action_executor_spec.rb +205 -0
  44. data/spec/executor/executor_spec.rb +405 -17
  45. data/spec/executor/help_executor_spec.rb +424 -0
  46. data/spec/executor/shell_executor_spec.rb +233 -0
  47. data/spec/fixtures/action_reader_command.rb +12 -0
  48. data/spec/fixtures/csv_argument_operator.rb +8 -0
  49. data/spec/fixtures/test_option_type_checker_command.rb +8 -0
  50. data/spec/parser/action_argument_parser_spec.rb +113 -0
  51. data/spec/parser/argument_parser_spec.rb +37 -44
  52. data/spec/spec_helper.rb +1 -0
  53. data/spec/validation/action_argument_validator_spec.rb +50 -0
  54. data/spec/validation/{argument_validation_spec.rb → command_argument_validation_spec.rb} +36 -18
  55. data/spec/validation/error_spec.rb +1 -1
  56. data/tasks/rdoc.rake +16 -0
  57. data/tasks/rubucop.rake +14 -0
  58. data/tasks/yard.rake +21 -0
  59. data/templates/usage.erb +39 -0
  60. metadata +48 -11
@@ -49,8 +49,8 @@ module Cliqr
49
49
  def run
50
50
  old_stdout = $stdout
51
51
  old_stderr = $stderr
52
- $stdout = StringIO.new('', 'w')
53
- $stderr = StringIO.new('', 'w')
52
+ $stdout = old_stdout.is_a?(StringIO) ? old_stdout : StringIO.new('', 'w')
53
+ $stderr = old_stderr.is_a?(StringIO) ? old_stderr : StringIO.new('', 'w')
54
54
  yield
55
55
  {
56
56
  :stdout => $stdout.string,
@@ -1,8 +1,10 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require 'cliqr/dsl'
4
+ require 'cliqr/util'
4
5
  require 'cliqr/config_validation/verifiable'
5
6
  require 'cliqr/cli/command'
7
+ require 'cliqr/cli/argument_operator'
6
8
 
7
9
  module Cliqr
8
10
  # A extension for CLI module to group all config classes
@@ -10,6 +12,28 @@ module Cliqr
10
12
  # A value to initialize configuration attributes with
11
13
  UNSET = Object.new
12
14
 
15
+ # Configuration option to enable arguments for a command (default)
16
+ ENABLE_CONFIG = :enable
17
+
18
+ # Configuration option to disable arguments for a command
19
+ DISABLE_CONFIG = :disable
20
+
21
+ # Option type for regular options
22
+ ANY_ARGUMENT_TYPE = :any
23
+
24
+ # Option type for numeric arguments
25
+ NUMERIC_ARGUMENT_TYPE = :numeric
26
+
27
+ # Option type for boolean arguments
28
+ BOOLEAN_ARGUMENT_TYPE = :boolean
29
+
30
+ # Default values based on argument type
31
+ ARGUMENT_DEFAULTS = {
32
+ NUMERIC_ARGUMENT_TYPE => 0,
33
+ BOOLEAN_ARGUMENT_TYPE => false,
34
+ ANY_ARGUMENT_TYPE => nil
35
+ }
36
+
13
37
  # The configuration setting to build a cli application with its own dsl
14
38
  #
15
39
  # @api private
@@ -17,11 +41,11 @@ module Cliqr
17
41
  extend Cliqr::DSL
18
42
  include Cliqr::ConfigValidation
19
43
 
20
- # Base name of the command
44
+ # Name of the command
21
45
  #
22
46
  # @return [String]
23
- attr_accessor :basename
24
- validates :basename,
47
+ attr_accessor :name
48
+ validates :name,
25
49
  non_empty_format: /^[a-zA-Z0-9_\-]+$/
26
50
 
27
51
  # Description for the base command
@@ -34,14 +58,17 @@ module Cliqr
34
58
  # @return [Class<Cliqr::CLI::Command>]
35
59
  attr_accessor :handler
36
60
  validates :handler,
37
- extend: Cliqr::CLI::Command
61
+ one_of: {
62
+ extend: Cliqr::CLI::Command,
63
+ type_of: Proc
64
+ }
38
65
 
39
- # Optional attribute that dictates whether this command can take arbitrary arguments
66
+ # Dictates whether this command can take arbitrary arguments (optional)
40
67
  #
41
- # @return [Symbol] Either <tt>:enabled</tt> or <tt>:disabled</tt>
68
+ # @return [Symbol] Either <tt>#ENABLE_CONFIG</tt> or <tt>#DISABLE_CONFIG</tt>
42
69
  attr_accessor :arguments
43
70
  validates :arguments,
44
- inclusion: [:enable, :disable]
71
+ inclusion: [ENABLE_CONFIG, DISABLE_CONFIG]
45
72
 
46
73
  # Array of options applied to the base command
47
74
  #
@@ -50,44 +77,103 @@ module Cliqr
50
77
  validates :options,
51
78
  collection: true
52
79
 
80
+ # Array of children action configs for this action
81
+ #
82
+ # @return [Array<Cliqr::CLI::Config>]
83
+ attr_accessor :actions
84
+ validates :actions,
85
+ collection: true
86
+
87
+ # Enable or disable help command and option
88
+ #
89
+ # @return [Symbol] Either <tt>#ENABLE_CONFIG</tt> or <tt>#DISABLE_CONFIG</tt>
90
+ attr_accessor :help
91
+ validates :help,
92
+ inclusion: [ENABLE_CONFIG, DISABLE_CONFIG]
93
+
94
+ # Enable or disable the shell action for base config
95
+ #
96
+ # @return [Symbol] Either <tt>#ENABLE_CONFIG</tt> or <tt>#DISABLE_CONFIG</tt>
97
+ attr_accessor :shell
98
+ validates :shell,
99
+ inclusion: [ENABLE_CONFIG, DISABLE_CONFIG]
100
+
101
+ # Version tag for this configuration
102
+ #
103
+ # @return [Stirng]
104
+ attr_accessor :version
105
+
106
+ # Parent configuration
107
+ #
108
+ # @return [Cliqr::CLI::Config]
109
+ attr_accessor :parent
110
+
111
+ # Root config
112
+ #
113
+ # @return [Cliqr::CLI::Config]
114
+ attr_accessor :root
115
+
53
116
  # New config instance with all attributes set as UNSET
54
117
  def initialize
55
- @basename = UNSET
118
+ @name = UNSET
56
119
  @description = UNSET
57
120
  @handler = UNSET
58
121
  @arguments = UNSET
122
+ @help = UNSET
123
+ @shell = UNSET
124
+ @version = UNSET
59
125
 
60
126
  @options = []
61
127
  @option_index = {}
128
+
129
+ @actions = []
130
+ @action_index = {}
62
131
  end
63
132
 
64
133
  # Finalize config by adding default values for unset values
65
134
  #
66
135
  # @return [Cliqr::CLI::Config]
67
136
  def finalize
68
- @basename = '' if @basename == UNSET
69
- @description = '' if @description == UNSET
70
- @handler = nil if @handler == UNSET
71
- @arguments = :enable if @arguments == UNSET
137
+ @name = Config.get_if_unset(@name, '')
138
+ @description = Config.get_if_unset(@description, '')
139
+ @handler = Util.ensure_instance(Config.get_if_unset(@handler, nil))
140
+ @arguments = Config.get_if_unset(@arguments, ENABLE_CONFIG)
141
+ @help = Config.get_if_unset(@help, ENABLE_CONFIG)
142
+ @root = self
143
+ @shell = Config.get_if_unset(@shell, shell_default)
144
+ @version = Config.get_if_unset(@version, nil)
72
145
 
73
146
  self
74
147
  end
75
148
 
149
+ # Set up default attributes for this configuration
150
+ #
151
+ # @return [Cliqr::CLI::Config] Update config
152
+ def setup_defaults
153
+ add_shell
154
+ add_version
155
+ add_help
156
+ @handler = Cliqr::Util.forward_to_help_handler if @handler.nil? && help? && actions?
157
+ @actions.each(&:setup_defaults)
158
+ end
159
+
76
160
  # Set value for a config option
77
161
  #
78
162
  # @param [Symbol] name Name of the config parameter
79
- #
80
163
  # @param [Object] value Value for the config parameter
81
- #
82
- # @param [Function] block Function which populates configuration for a sub-attribute
164
+ # @param [Proc] block Function which populates configuration for a sub-attribute
83
165
  #
84
166
  # @return [Object] if setting a attribute's value
85
167
  # @return [Cliqr::CLI::OptionConfig] if adding a new option
168
+ # @return [Cliqr::CLI::Config] if adding a new action
86
169
  def set_config(name, value, &block)
87
170
  case name
88
171
  when :option
89
172
  handle_option value, &block # value is the long name for the option
173
+ when :action
174
+ handle_action value, &block # value is action's name
90
175
  else
176
+ value = block if block_given?
91
177
  handle_config name, value
92
178
  end
93
179
  end
@@ -103,9 +189,9 @@ module Cliqr
103
189
  #
104
190
  # @param [String] name Name of the option to check
105
191
  #
106
- # @return [Boolean] <tt>true</tt> if the a CLI config's option is set
192
+ # @return [Boolean] <tt>true</tt> if the CLI config's option is set
107
193
  def option?(name)
108
- @option_index.key?(name)
194
+ @option_index.key?(name.to_s)
109
195
  end
110
196
 
111
197
  # Get value of a option
@@ -114,14 +200,83 @@ module Cliqr
114
200
  #
115
201
  # @return [String] value for the option
116
202
  def option(name)
117
- @option_index[name]
203
+ @option_index[name.to_s]
204
+ end
205
+
206
+ # Check if particular action exists
207
+ #
208
+ # @param [String] name Name of the action to check
209
+ #
210
+ # @return [Boolean] <tt>true</tt> if the action exists in the configuration
211
+ def action?(name)
212
+ return false if name.nil?
213
+ @action_index.key?(name.to_sym)
214
+ end
215
+
216
+ # Check if this config has sub actions
217
+ #
218
+ # @return [Array<Cliqr::CLI::Config]>
219
+ def actions?
220
+ !@actions.empty?
221
+ end
222
+
223
+ # Get action config by name
224
+ #
225
+ # @param [String] name Name of the action
226
+ #
227
+ # @return [Cliqr::CLI::Config] Configuration of the action
228
+ def action(name)
229
+ @action_index[name.to_sym]
118
230
  end
119
231
 
120
232
  # Check if arguments are enabled for this configuration
121
233
  #
122
234
  # @return [Boolean] <tt>true</tt> if arguments are enabled
123
235
  def arguments?
124
- @arguments == :enable
236
+ @arguments == ENABLE_CONFIG
237
+ end
238
+
239
+ # Get name of the command along with the action upto this config
240
+ #
241
+ # @return [String] Serialized command name
242
+ def command
243
+ return name unless parent?
244
+ "#{@parent.command} #{name}"
245
+ end
246
+
247
+ # Check if this config has a parent config
248
+ #
249
+ # @return [Boolean] <tt>true</tt> if there exists a parent action for this action
250
+ def parent?
251
+ !@parent.nil?
252
+ end
253
+
254
+ # Check if help is enabled for this command
255
+ #
256
+ # @return [Boolean] <tt>true</tt> if help is enabled
257
+ def help?
258
+ @help == ENABLE_CONFIG
259
+ end
260
+
261
+ # Check if version is enabled for this command
262
+ #
263
+ # @return [Boolean] <tt>true</tt> if help is enabled
264
+ def version?
265
+ !@version.nil?
266
+ end
267
+
268
+ # Check if this is the root config
269
+ #
270
+ # @return [Boolean]
271
+ def root?
272
+ self == @root
273
+ end
274
+
275
+ # Check if this configuration has shell action enabled
276
+ #
277
+ # @return [Boolean]
278
+ def shell?
279
+ @shell == ENABLE_CONFIG
125
280
  end
126
281
 
127
282
  private
@@ -137,27 +292,59 @@ module Cliqr
137
292
  value
138
293
  end
139
294
 
140
- # Add a new option for the command
295
+ # Handle configuration for a new option
141
296
  #
142
297
  # @param [Symbol] name Long name of the option
143
- #
144
- # @param [Function] block Populate the option's config in this funciton block
298
+ # @param [Proc] block Populate the option's config in this funciton block
145
299
  #
146
300
  # @return [Cliqr::CLI::OptionConfig] Newly created option's config
147
301
  def handle_option(name, &block)
148
302
  option_config = OptionConfig.build(&block)
149
303
  option_config.name = name
304
+ add_option(option_config)
305
+ end
150
306
 
307
+ # Add a new option for the command
308
+ #
309
+ # @return [Cliqr::CLI::OptionConfig] Newly added option's config
310
+ def add_option(option_config)
151
311
  validate_option_name(option_config)
152
312
 
153
- @options.push option_config
154
-
155
- @option_index[option_config.name] = option_config
156
- @option_index[option_config.short] = option_config if option_config.short?
313
+ @options.push(option_config)
314
+ @option_index[option_config.name.to_s] = option_config
315
+ @option_index[option_config.short.to_s] = option_config if option_config.short?
157
316
 
158
317
  option_config
159
318
  end
160
319
 
320
+ # Handle configuration for a new action
321
+ #
322
+ # @param [String] name Name of the action
323
+ # @param [Function] block The block which configures this action
324
+ #
325
+ # @return [Cliqr::CLI::Config] The newly configured action
326
+ def handle_action(name, &block)
327
+ action_config = Config.build(&block)
328
+ action_config.name = name
329
+ add_action(action_config)
330
+ end
331
+
332
+ # Add a new action
333
+ #
334
+ # @return [Cliqr::CLI::Config] The newly added action
335
+ def add_action(action_config)
336
+ action_config.parent = self
337
+ action_config.root = root
338
+
339
+ validate_action_name(action_config)
340
+
341
+ @actions.push(action_config)
342
+ @action_index[action_config.name.to_sym] = action_config \
343
+ unless action_config.name.nil?
344
+
345
+ action_config
346
+ end
347
+
161
348
  # Make sure that the option's name is unique
162
349
  #
163
350
  # @param [Cliqr::CLI::OptionConfig] option_config Config for this particular option
@@ -174,6 +361,59 @@ module Cliqr
174
361
 
175
362
  option_config
176
363
  end
364
+
365
+ # Make sure that the action's name is unique
366
+ #
367
+ # @param [Cliqr::CLI::Config] action_config Config for this particular action
368
+ #
369
+ # @return [Cliqr::CLI::Config] Validated action's Config instance
370
+ def validate_action_name(action_config)
371
+ fail Cliqr::Error::DuplicateActions,
372
+ "multiple actions named \"#{action_config.name}\"" \
373
+ if action?(action_config.name)
374
+
375
+ action_config
376
+ end
377
+
378
+ # Add help command and option to this config
379
+ #
380
+ # @return [Cliqr::CLI::Config] Updated config
381
+ def add_help
382
+ return self unless help?
383
+ add_action(Cliqr::Util.build_help_action(self)) unless action?('help')
384
+ add_option(Cliqr::Util.build_help_option(self)) unless option?('help')
385
+ end
386
+
387
+ # Add version command and option to this config
388
+ #
389
+ # @return [Cliqr::CLI::Config] Updated config
390
+ def add_version
391
+ return self unless version?
392
+ add_action(Cliqr::Util.build_version_action(self)) unless action?('version')
393
+ add_option(Cliqr::Util.build_version_option(self)) unless option?('version')
394
+ end
395
+
396
+ # Add shell command
397
+ #
398
+ # @return [Cliqr::CLI::Config] Updated config
399
+ def add_shell
400
+ return self unless shell?
401
+ add_action(Cliqr::Util.build_shell_action(self)) unless action?('shell')
402
+ end
403
+
404
+ # Get default setting for shell attribute
405
+ #
406
+ # @return [Symbol]
407
+ def shell_default
408
+ root? && actions? ? ENABLE_CONFIG : DISABLE_CONFIG
409
+ end
410
+
411
+ # Get the passed param value if current attribute is unset
412
+ #
413
+ # @return [Object]
414
+ def self.get_if_unset(attribute_value, default_value)
415
+ attribute_value == UNSET ? default_value : attribute_value
416
+ end
177
417
  end
178
418
 
179
419
  # Config attributes for a command's option
@@ -208,16 +448,32 @@ module Cliqr
208
448
  # @return [Symbol] Type of the option
209
449
  attr_accessor :type
210
450
  validates :type,
211
- inclusion: [:any, :numeric, :boolean]
451
+ inclusion: [:any, NUMERIC_ARGUMENT_TYPE, BOOLEAN_ARGUMENT_TYPE]
452
+
453
+ # Operation to be applied to the option value after validation
454
+ #
455
+ # @return [Class<Cliqr::CLI::ArgumentOperator>]
456
+ attr_accessor :operator
457
+ validates :operator,
458
+ one_of: {
459
+ extend: Cliqr::CLI::ArgumentOperator,
460
+ type_of: Proc
461
+ }
462
+
463
+ # Default value for this option
464
+ #
465
+ # @return [Object]
466
+ attr_accessor :default
212
467
 
213
468
  # Set value for command option's attribute
214
469
  #
215
470
  # @param [Symbol] name Name of the attribute
216
- #
217
471
  # @param [Object] value Value for the attribute
472
+ # @param [Proc] block A anonymous block to initialize the config value
218
473
  #
219
474
  # @return [Object] Value that was set for the attribute
220
- def set_config(name, value)
475
+ def set_config(name, value, &block)
476
+ value = block if block_given?
221
477
  handle_option_config name, value
222
478
  end
223
479
 
@@ -227,16 +483,21 @@ module Cliqr
227
483
  @short = UNSET
228
484
  @description = UNSET
229
485
  @type = UNSET
486
+ @operator = UNSET
487
+ @default = UNSET
230
488
  end
231
489
 
232
490
  # Finalize option's config by adding default values for unset values
233
491
  #
234
492
  # @return [Cliqr::CLI::OptionConfig]
235
493
  def finalize
236
- @name = nil if @name == UNSET
237
- @short = nil if @short == UNSET
238
- @description = nil if @description == UNSET
239
- @type = :any if @type == UNSET
494
+ @name = Config.get_if_unset(@name, nil)
495
+ @short = Config.get_if_unset(@short, nil)
496
+ @description = Config.get_if_unset(@description, nil)
497
+ @type = Config.get_if_unset(@type, ANY_ARGUMENT_TYPE)
498
+ @operator = Util.ensure_instance(Config.get_if_unset(@operator,
499
+ ArgumentOperator.for_type(@type)))
500
+ @default = Config.get_if_unset(@default, ARGUMENT_DEFAULTS[@type])
240
501
 
241
502
  self
242
503
  end
@@ -269,6 +530,13 @@ module Cliqr
269
530
  @type == :boolean
270
531
  end
271
532
 
533
+ # Check if a default value setting is defined
534
+ #
535
+ # @return [Boolean]
536
+ def default?
537
+ !@default.nil?
538
+ end
539
+
272
540
  private
273
541
 
274
542
  # Set value for config option without evaluating a block