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
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # The simple sort script from root README
4
+
5
+ require 'cliqr'
6
+
7
+ cli = Cliqr.interface do
8
+ name 'numbers'
9
+ description 'A simplistic example for quickly getting started with cliqr.'
10
+ version '0.0.1' # optional; adds a version action to our simple command
11
+
12
+ # main command handler
13
+ handler do
14
+ puts "Hi #{name}" if name?
15
+ puts 'Nothing to do here. Please try the sort action.'
16
+ end
17
+
18
+ option :name do
19
+ description 'Your name.'
20
+ operator do
21
+ value.split(' ').first # only get the first name
22
+ end
23
+ end
24
+
25
+ action :sort do
26
+ description 'Sort a set of random numbers'
27
+ shell :disable
28
+
29
+ handler do
30
+ fail StandardError, 'count should be a non-zero positive number' unless count > 0
31
+ result = [].tap { |numbers| count.times { numbers << rand(9999) } }.sort
32
+ result = result.reverse if order? && order == :descending
33
+ puts result
34
+ end
35
+
36
+ option :count do
37
+ short 'c' # optional, but usually a good idea to have it
38
+ description 'Count of something.'
39
+ type :numeric # restricts values for this option to numbers
40
+ end
41
+
42
+ option :order do
43
+ short 'o'
44
+ description 'Order of sort.'
45
+
46
+ # This is how you can make sure that the input is valid.
47
+ operator do
48
+ fail StandardError, "Unknown order #{value}" unless [:ascending, :descending].include?(value.to_sym)
49
+ value.to_sym
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ cli.execute(ARGV)
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # A fake vagrant command example.
4
+ #
5
+ # In this example you can find:
6
+ # - A Command wih action and sub-actions
7
+ # - Command handler returning non-zero exit code
8
+ # - Command options and their types
9
+ # - Overriding default option argument values
10
+ # - Enable/Disable command arguments
11
+
12
+ require 'cliqr'
13
+
14
+ cli = Cliqr.interface do
15
+ name 'vagrant'
16
+ description <<-EOS
17
+ Vagrant is a tool for building and distributing development environments.
18
+
19
+ http://www.vagrantup.com
20
+ EOS
21
+ version '1.7.2'
22
+ shell :disable
23
+
24
+ action :box do
25
+ description 'manages boxes: installation, removal, etc.'
26
+ shell :disable
27
+
28
+ action :add do
29
+ shell :disable
30
+ handler do
31
+ puts 'This command was not invoked properly. The help for this command is available below.'
32
+ forward 'vagrant box add help'
33
+ 1 # non-zero exit code
34
+ end
35
+
36
+ option :clean do
37
+ short 'c'
38
+ description 'Clean any temporary download files'
39
+ type :boolean
40
+ end
41
+
42
+ option :force do
43
+ short 'f'
44
+ description 'Overwrite an existing box if it exists'
45
+ type :boolean
46
+ end
47
+
48
+ option :insecure do
49
+ description 'Do not validate SSL certificates'
50
+ end
51
+
52
+ option :cacert do
53
+ description 'CA certificate for SSL download'
54
+ end
55
+ end
56
+ end
57
+
58
+ action :up do
59
+ description 'starts and provisions the vagrant environment'
60
+ shell :disable
61
+ arguments :disable
62
+ handler do
63
+ puts 'A Vagrant environment or target machine is required to run this command. ' \
64
+ 'Then there is also that fact that this is a fake demo :)'
65
+ 1 # non-zero exit code
66
+ end
67
+
68
+ option :provision do
69
+ description 'Enable or disable provisioning'
70
+ type :boolean
71
+ end
72
+
73
+ option 'destroy-on-error' do
74
+ description 'Destroy machine if any fatal error happens'
75
+ type :boolean
76
+ default true
77
+ end
78
+
79
+ option :parallel do
80
+ description 'Enable or disable parallelism if provider supports it'
81
+ type :boolean
82
+ end
83
+
84
+ option :provider do
85
+ description 'Back the machine with a specific provider'
86
+ end
87
+ end
88
+ end
89
+
90
+ cli.execute(ARGV)
@@ -16,7 +16,7 @@ module Cliqr
16
16
  #
17
17
  # @example
18
18
  # Cliqr.interface do
19
- # basename 'my-command' # name of the command
19
+ # name 'my-command' # name of the command
20
20
  # description 'command description in a few words' # long description
21
21
  # handler MyCommandHandler # command's handler class
22
22
  #
@@ -28,6 +28,7 @@ module Cliqr
28
28
  # @api public
29
29
  def interface(&block)
30
30
  config = CLI::Config.build(&block)
31
+ config.setup_defaults
31
32
  CLI::Interface.build(config)
32
33
  end
33
34
 
@@ -40,9 +41,23 @@ module Cliqr
40
41
  # end
41
42
  # end
42
43
  #
43
- # @return [CLI::Command]
44
+ # @return [Cliqr::CLI::Command]
44
45
  def command
45
46
  CLI::Command
46
47
  end
48
+
49
+ # A argument operator must extend from [Cliqr::CLI::ArgumentOperator]
50
+ #
51
+ # @example
52
+ # class MyOperator < Cliqr.operator
53
+ # def operate(value)
54
+ # # return the operated value
55
+ # end
56
+ # end
57
+ #
58
+ # @return [Cliar::CLI::ArgumentOperator]
59
+ def operator
60
+ CLI::ArgumentOperator
61
+ end
47
62
  end
48
63
  end
@@ -17,9 +17,9 @@ module Cliqr
17
17
  # Check if a type of a argument matches a required type
18
18
  def type_of?(argument, required_type)
19
19
  case required_type
20
- when :numeric
20
+ when CLI::NUMERIC_ARGUMENT_TYPE
21
21
  Integer(argument)
22
- when :boolean
22
+ when CLI::BOOLEAN_ARGUMENT_TYPE
23
23
  fail ArgumentError unless argument.class == TrueClass || argument.class == FalseClass
24
24
  end
25
25
  true
@@ -27,7 +27,7 @@ module Cliqr
27
27
  errors = ValidationErrors.new
28
28
  config.options.each do |option|
29
29
  validate_argument(args.option(option.name), option, errors) \
30
- if args.options.key?(option.name)
30
+ if args.options.key?(option.name.to_s)
31
31
  end
32
32
  fail(Cliqr::Error::IllegalArgumentError, "illegal argument error - #{errors}") \
33
33
  unless errors.empty?
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+
3
+ module Cliqr
4
+ module CLI
5
+ # Operates on the value of a argument after it has been validated
6
+ #
7
+ # @api private
8
+ class ArgumentOperator
9
+ # Default pass through argument operator
10
+ DEFAULT_ARGUMENT_OPERATOR = ArgumentOperator.new
11
+
12
+ # Get a new ArgumentOperator for a argument type
13
+ #
14
+ # @return [Cliqr::CLI::ArgumentOperator]
15
+ def self.for_type(type)
16
+ case type
17
+ when CLI::NUMERIC_ARGUMENT_TYPE
18
+ NumericArgumentOperator.new
19
+ else
20
+ DEFAULT_ARGUMENT_OPERATOR
21
+ end
22
+ end
23
+
24
+ # Return the same value back without any change
25
+ #
26
+ # @return [String]
27
+ def operate(value)
28
+ value
29
+ end
30
+ end
31
+
32
+ # Handle numerical arguments
33
+ #
34
+ # @api private
35
+ class NumericArgumentOperator < ArgumentOperator
36
+ # Convert the argument to a integer value
37
+ #
38
+ # @return [Integer]
39
+ def operate(value)
40
+ value.to_i
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+
3
+ module Cliqr
4
+ module CLI
5
+ # Context in which a anonymous argument operator runs
6
+ #
7
+ # @api private
8
+ class ArgumentOperatorContext
9
+ # Value to operator on
10
+ #
11
+ # @return [Object]
12
+ attr_accessor :value
13
+
14
+ # Create a argument operator context for a value
15
+ def initialize(value)
16
+ @value = value
17
+ end
18
+ end
19
+ end
20
+ end
@@ -9,7 +9,7 @@ module Cliqr
9
9
  # Execute the command
10
10
  #
11
11
  # @return [Integer] Exit status of the command execution
12
- def execute
12
+ def execute(_context)
13
13
  0
14
14
  end
15
15
  end
@@ -1,5 +1,7 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require 'cliqr/cli/argument_operator_context'
4
+
3
5
  module Cliqr
4
6
  # Definition and builder for command context
5
7
  module CLI
@@ -17,21 +19,34 @@ module Cliqr
17
19
  # @return [Array<String>] List of arguments
18
20
  attr_accessor :arguments
19
21
 
22
+ # Name of the current action
23
+ #
24
+ # @return [String]
25
+ attr_accessor :action_name
26
+
20
27
  # Build a instance of command context based on the parsed set of arguments
21
28
  #
29
+ # @param [Cliqr::CLI::Config] config The configuration settings for command's action config
22
30
  # @param [Cliqr::Parser::ParsedInput] parsed_input Parsed input object
31
+ # @param [Hash] options Options for command execution
32
+ # @param [Proc] executor Executes forwarded commands
23
33
  #
24
34
  # @return [Cliqr::CLI::CommandContext]
25
- def self.build(parsed_input)
26
- CommandContextBuilder.new(parsed_input).build
35
+ def self.build(config, parsed_input, options, &executor)
36
+ CommandContextBuilder.new(config, parsed_input, options, executor).build
27
37
  end
28
38
 
29
39
  # Initialize the command context (called by the CommandContextBuilder)
30
40
  #
31
41
  # @return [Cliqr::CLI::CommandContext]
32
- def initialize(command, options, arguments)
33
- @command = command
42
+ def initialize(config, options, arguments, environment, executor)
43
+ @config = config
44
+ @command = config.command
34
45
  @arguments = arguments
46
+ @action_name = config.name
47
+ @context = self
48
+ @environment = environment
49
+ @executor = executor
35
50
 
36
51
  # make option map from array
37
52
  @options = Hash[*options.collect { |option| [option.name, option] }.flatten]
@@ -50,7 +65,8 @@ module Cliqr
50
65
  #
51
66
  # @return [Cliqr::CLI::CommandOption] Instance of CommandOption for option
52
67
  def option(name)
53
- @options[name]
68
+ return @options[name] if option?(name)
69
+ default(name)
54
70
  end
55
71
 
56
72
  # Check if a option with a specified name has been passed
@@ -62,7 +78,46 @@ module Cliqr
62
78
  @options.key?(name)
63
79
  end
64
80
 
65
- private :initialize
81
+ # Check whether the current context is based off of a sub-action
82
+ #
83
+ # @return [Boolean] <tt>true</tt> if this context is based off a sub-action
84
+ def action?
85
+ @config.parent?
86
+ end
87
+
88
+ # Forward a command to the executor
89
+ #
90
+ # @return [Integer] Exit code
91
+ def forward(args, options = {})
92
+ @executor.call(args, options)
93
+ end
94
+
95
+ # Handle the case when a method is invoked to get an option value
96
+ #
97
+ # @return [Object] Option's value
98
+ def method_missing(name, *_args, &_block)
99
+ option_name = name.to_s.chomp('?')
100
+ existence_check = name.to_s.end_with?('?')
101
+ existence_check ? option?(option_name) : option(option_name).value \
102
+ if @config.option?(option_name)
103
+ end
104
+
105
+ # Get default value for a option
106
+ #
107
+ # @return [Object]
108
+ def default(name)
109
+ option_config = @config.option(name)
110
+ CommandOption.new([name, option_config.default], option_config)
111
+ end
112
+
113
+ # Check if running in a bash environment
114
+ #
115
+ # @return [Boolean]
116
+ def bash?
117
+ @environment == :bash
118
+ end
119
+
120
+ private :initialize, :default
66
121
  end
67
122
 
68
123
  private
@@ -73,20 +128,32 @@ module Cliqr
73
128
  class CommandContextBuilder
74
129
  # Initialize builder for CommandContext
75
130
  #
131
+ # @param [Cliqr::CLI::Config] config The configuration settings for command's action config
76
132
  # @param [Cliqr::Parser::ParsedInput] parsed_input Parsed and validated command line arguments
133
+ # @param [Hash] options Options for command execution
134
+ # @param [Proc] executor Executes forwarded commands
77
135
  #
78
136
  # @return [Cliqr::CLI::CommandContextBuilder]
79
- def initialize(parsed_input)
137
+ def initialize(config, parsed_input, options, executor)
138
+ @config = config
80
139
  @parsed_input = parsed_input
140
+ @options = options
141
+ @executor = executor
81
142
  end
82
143
 
83
144
  # Build a new instance of CommandContext
84
145
  #
85
146
  # @return [Cliqr::CLI::CommandContext] A newly created CommandContext instance
86
147
  def build
87
- CommandContext.new @parsed_input.command,
88
- @parsed_input.options.map { |option| CommandOption.new(option) },
89
- @parsed_input.arguments
148
+ option_contexts = @parsed_input.options.map do |option|
149
+ CommandOption.new(option, @config.option(option.first))
150
+ end
151
+
152
+ CommandContext.new @config,
153
+ option_contexts,
154
+ @parsed_input.arguments,
155
+ @options[:environment],
156
+ @executor
90
157
  end
91
158
  end
92
159
 
@@ -107,12 +174,26 @@ module Cliqr
107
174
  # Create a new command line option instance
108
175
  #
109
176
  # @param [Array] option Parsed arguments for creating a command line option
177
+ # @param [Cliqr::CLI::OptionConfig] option_config Option's config settings
110
178
  #
111
179
  # @return [Cliqr::CLI::CommandContext] A new CommandOption object
112
- def initialize(option)
113
- @value = option.pop
180
+ def initialize(option, option_config)
181
+ @value = run_value_operator(option.pop, option_config.operator)
114
182
  @name = option.pop
115
183
  end
184
+
185
+ private
186
+
187
+ # Run the operator for a named attribute for a value
188
+ #
189
+ # @return [Nothing]
190
+ def run_value_operator(value, operator)
191
+ if operator.is_a?(Proc)
192
+ ArgumentOperatorContext.new(value).instance_eval(&operator)
193
+ else
194
+ operator.operate(value)
195
+ end
196
+ end
116
197
  end
117
198
  end
118
199
  end