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
@@ -55,7 +55,7 @@ module Cliqr
55
55
  class ValidationError < CliqrError; end
56
56
 
57
57
  # Encapsulates the error that gets thrown during a command execution
58
- class CommandRuntimeException < CliqrError; end
58
+ class CommandRuntimeError < CliqrError; end
59
59
 
60
60
  # Error to signify that a command's runner is not available
61
61
  class UnknownCommandRunnerException < CliqrError; end
@@ -69,16 +69,22 @@ module Cliqr
69
69
  # Indicates that a option has multiple values in the command line
70
70
  class MultipleOptionValues < CliqrError; end
71
71
 
72
- # Raised if two options are defined with same long or short name
72
+ # Raised if more than one option is defined with same long or short name
73
73
  class DuplicateOptions < CliqrError; end
74
74
 
75
- # Raised if an option is not defined properly
76
- class InvalidArgumentError < CliqrError; end
75
+ # Raised if multiple actions are defined with same name at the same nesting level
76
+ class DuplicateActions < CliqrError; end
77
77
 
78
78
  # Raised if an argument does not conform to the option's type
79
79
  class IllegalArgumentError < CliqrError; end
80
80
 
81
81
  # Indicates that a unknown validator type is being used in a class
82
82
  class UnknownValidatorType < CliqrError; end
83
+
84
+ # Indicates that a unknown action was invoked
85
+ class UnknownActionError < CliqrError; end
86
+
87
+ # Raised when a command is executed that is not supposed to run
88
+ class IllegalCommandError < CliqrError; end
83
89
  end
84
90
  end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+
3
+ module Cliqr
4
+ module Parser
5
+ # Token handler for parsing action commands
6
+ #
7
+ # @api private
8
+ class ActionToken < Token
9
+ # Create a new action token
10
+ def initialize(_name)
11
+ super(arg)
12
+ end
13
+
14
+ # This token is valid if argument is non-empty
15
+ #
16
+ # @return [Boolean] <tt>true</tt> if token's argument is non-empty
17
+ def valid?
18
+ return false if arg.nil?
19
+ !arg.empty?
20
+ end
21
+ end
22
+ end
23
+ end
@@ -12,7 +12,7 @@ module Cliqr
12
12
  # @param [Cliqr::CLI::Config] config Command line configuration
13
13
  # @param [Array<String>] args An array of arguments from command line
14
14
  #
15
- # @return [Hash] Parsed hash of command linet arguments
15
+ # @return [Hash] Parsed action config and hash of command line arguments
16
16
  def self.parse(config, args)
17
17
  tree_walker = ArgumentTreeWalker.new(config)
18
18
  tree_walker.walk(args)
@@ -16,10 +16,7 @@ module Cliqr
16
16
  # @return [Boolean] <tt>true</tt> if token's argument is non-empty
17
17
  def valid?
18
18
  return false if arg.nil?
19
-
20
- return !arg.empty? if @arg.respond_to?(:empty?)
21
-
22
- true
19
+ !arg.empty?
23
20
  end
24
21
  end
25
22
  end
@@ -20,19 +20,38 @@ module Cliqr
20
20
 
21
21
  # Parse the arguments and generate tokens by iterating over command line arguments
22
22
  #
23
- # @param [Array<String>] args List of arguments that needs to parsed
23
+ # @param [Array<String>] raw_args List of arguments that needs to parsed
24
24
  #
25
- # @return [Hash] Parsed hash of command line arguments
26
- def walk(args)
27
- input_builder = ParsedInputBuilder.new(@config)
28
- token_factory = TokenFactory.new(@config)
25
+ # @return [Array] Action config and parsed hash of command line arguments
26
+ def walk(raw_args)
27
+ action_config, args = parse_action(raw_args)
28
+ input_builder = ParsedInputBuilder.new(@config, action_config)
29
+ token_factory = TokenFactory.new(action_config)
29
30
  token = token_factory.get_token
30
31
  args.each do |arg|
31
32
  token = handle_argument(arg, token, token_factory, input_builder)
32
33
  end
33
- fail Cliqr::Error::OptionValueMissing, "a value must be defined for argument \"#{token.arg}\"" \
34
- if token.active?
35
- input_builder.build
34
+ fail Cliqr::Error::OptionValueMissing, \
35
+ "a value must be defined for argument \"#{token.arg}\"" if token.active?
36
+ ensure_default_action(action_config, input_builder)
37
+ end
38
+
39
+ private
40
+
41
+ # Parse the action from the list of arguments
42
+ #
43
+ # @return [Cliqr::CLI::Config] Configuration of the command invoked and remaining arguments
44
+ def parse_action(raw_args)
45
+ args = []
46
+ action_config = @config
47
+ raw_args.each do |arg|
48
+ if action_config.action?(arg)
49
+ action_config = action_config.action(arg)
50
+ else
51
+ args.push(arg)
52
+ end
53
+ end
54
+ [action_config, args]
36
55
  end
37
56
 
38
57
  # Handle the next argument in the context of the current token
@@ -50,6 +69,19 @@ module Cliqr
50
69
 
51
70
  token
52
71
  end
72
+
73
+ # Make sure default options are processed by overriding action
74
+ #
75
+ # @return [Array] Action config and parsed hash of command line arguments
76
+ def ensure_default_action(action_config, input_builder)
77
+ parsed_input = input_builder.build
78
+ default_action_name = parsed_input.default_action(action_config)
79
+ unless default_action_name.nil?
80
+ action_config = action_config.action(default_action_name)
81
+ parsed_input.remove_option(default_action_name)
82
+ end
83
+ [action_config, parsed_input]
84
+ end
53
85
  end
54
86
  end
55
87
  end
@@ -46,7 +46,8 @@ module Cliqr
46
46
 
47
47
  # Collect this token's name and value into a input builder
48
48
  #
49
- # @param [Cliqr::Parser::ParsedInputBuilder] input_builder A builder to prepare parsed arguments
49
+ # @param [Cliqr::Parser::ParsedInputBuilder] input_builder A builder to prepare parsed
50
+ # arguments
50
51
  #
51
52
  # @return [Cliqr::Parser::ParsedInputBuilder] The updated input builder instance
52
53
  def collect(input_builder)
@@ -25,7 +25,7 @@ module Cliqr
25
25
  @command = parsed_arguments[:command]
26
26
 
27
27
  @options = Hash[parsed_arguments[:options].collect \
28
- { |option| [option[:name], option[:value]] }]\
28
+ { |option| [option[:name].to_s, option[:value]] }]\
29
29
  if parsed_arguments.key?(:options)
30
30
 
31
31
  @arguments = parsed_arguments[:arguments]
@@ -37,7 +37,14 @@ module Cliqr
37
37
  #
38
38
  # @return [String]
39
39
  def option(name)
40
- @options[name]
40
+ @options[name.to_s]
41
+ end
42
+
43
+ # Remove a option
44
+ #
45
+ # @return [Object] Option's original value
46
+ def remove_option(name)
47
+ @options.delete(name.to_s)
41
48
  end
42
49
 
43
50
  # Test equality with another object
@@ -55,6 +62,18 @@ module Cliqr
55
62
  def ==(other)
56
63
  eql?(other)
57
64
  end
65
+
66
+ # Get name of default action if present as option
67
+ #
68
+ # @return [Symbol] Name of the default action or <tt>nil</tt> if not present
69
+ def default_action(action_config)
70
+ if option('help') && action_config.help?
71
+ return :help
72
+ elsif option('version') && action_config.version?
73
+ return :version
74
+ end
75
+ nil
76
+ end
58
77
  end
59
78
  end
60
79
  end
@@ -14,8 +14,10 @@ module Cliqr
14
14
  # @param [Cliqr::CLI::Config] config Configuration settings for the command line interface
15
15
  #
16
16
  # @return [Cliqr::CLI::Parser::ParsedInputBuilder]
17
- def initialize(config)
17
+ def initialize(config, action_config)
18
18
  @config = config
19
+ @action_config = action_config
20
+ @actions = []
19
21
  @options = []
20
22
  @option_names = Set.new
21
23
  @arguments = []
@@ -23,7 +25,8 @@ module Cliqr
23
25
 
24
26
  # Add a new parsed option token from the list of options
25
27
  #
26
- # @param [Cliqr::CLI::Parser::OptionToken] token A parsed option token from command line arguments
28
+ # @param [Cliqr::CLI::Parser::OptionToken] token A parsed option token from command line
29
+ # arguments
27
30
  #
28
31
  # @return [Cliqr::Parser::ParsedInputBuilder] Updated input builder
29
32
  def add_option_token(token)
@@ -37,11 +40,11 @@ module Cliqr
37
40
 
38
41
  # Add a argument to the list of parsed arguments
39
42
  #
40
- # @param [String] arg Argument value
43
+ # @param [Cliqr::CLI::Parser::ArgumentToken] token Argument token
41
44
  #
42
45
  # @return [Cliqr::Parser::ParsedInputBuilder] Updated input builder
43
- def add_argument(arg)
44
- @arguments.push(arg)
46
+ def add_argument_token(token)
47
+ @arguments.push(token.arg) if token.valid?
45
48
  self
46
49
  end
47
50
 
@@ -49,7 +52,8 @@ module Cliqr
49
52
  #
50
53
  # @return [Cliqr::Parser::ParsedInput] Parsed arguments wrapper
51
54
  def build
52
- ParsedInput.new(:command => @config.basename,
55
+ ParsedInput.new(:command => @config.name,
56
+ :actions => @actions,
53
57
  :options => @options,
54
58
  :arguments => @arguments)
55
59
  end
@@ -62,7 +66,7 @@ module Cliqr
62
66
  #
63
67
  # @return [Set<String>] Current list of option names
64
68
  def add_option_name(token)
65
- option_config = @config.option(token.name)
69
+ option_config = @action_config.option(token.name)
66
70
  old_config = @option_names.add?(option_config.name)
67
71
  fail Cliqr::Error::MultipleOptionValues,
68
72
  "multiple values for option \"#{token.arg}\"" if old_config.nil?
@@ -27,20 +27,14 @@ module Cliqr
27
27
  @active
28
28
  end
29
29
 
30
- # This token is never valid
31
- #
32
- # @return [Boolean] Always <tt>false</tt>
33
- def valid?
34
- false
35
- end
36
-
37
30
  # Collect this token's argument into a input builder
38
31
  #
39
- # @param [Cliqr::Parser::ParsedInputBuilder] input_builder A builder to prepare parsed arguments
32
+ # @param [Cliqr::Parser::ParsedInputBuilder] input_builder A builder to prepare parsed
33
+ # arguments
40
34
  #
41
35
  # @return [Cliqr::Parser::ParsedInputBuilder] The updated input builder instance
42
36
  def collect(input_builder)
43
- input_builder.add_argument(@arg)
37
+ input_builder.add_argument_token(self)
44
38
  end
45
39
 
46
40
  protected
@@ -35,7 +35,7 @@ module Cliqr
35
35
  option_config = get_option_config(Regexp.last_match(2), arg)
36
36
  build_token(option_config, arg)
37
37
  else
38
- fail Cliqr::Error::InvalidArgumentError, "invalid command argument \"#{arg}\"" \
38
+ fail Cliqr::Error::IllegalArgumentError, "invalid command argument \"#{arg}\"" \
39
39
  unless @config.arguments?
40
40
  ArgumentToken.new(arg)
41
41
  end
@@ -0,0 +1,135 @@
1
+ # encoding: utf-8
2
+
3
+ require 'cliqr/cli/shell_command'
4
+
5
+ module Cliqr
6
+ # Utility methods
7
+ #
8
+ # @api private
9
+ class Util
10
+ # Ensure that a variable is a instance object not a class type
11
+ #
12
+ # @return [Object]
13
+ def self.ensure_instance(obj)
14
+ return obj.new if obj.class == Class
15
+ obj
16
+ end
17
+
18
+ # Build a help action for a parent config
19
+ #
20
+ # @return [Cliqr::CLI::Config] New action config
21
+ def self.build_help_action(config)
22
+ cli = Cliqr.interface do
23
+ name 'help'
24
+ description "The help action for command \"#{config.command}\" which provides details " \
25
+ 'and usage information on how to use the command.'
26
+ handler Util.help_action_handler(config)
27
+ help :disable if config.help?
28
+ shell :disable
29
+ end
30
+ cli.config
31
+ end
32
+
33
+ # Build a help option for a parent config
34
+ #
35
+ # @return [Cliqr::CLI::OptionConfig] New option config
36
+ def self.build_help_option(config)
37
+ Cliqr::CLI::OptionConfig.new.tap do |option_config|
38
+ option_config.name = 'help'
39
+ option_config.short = 'h'
40
+ option_config.description = "Get helpful information for action \"#{config.command}\" " \
41
+ 'along with its usage information.'
42
+ option_config.type = Cliqr::CLI::BOOLEAN_ARGUMENT_TYPE
43
+ option_config.operator = Cliqr::CLI::ArgumentOperator::DEFAULT_ARGUMENT_OPERATOR
44
+ option_config.finalize
45
+ end
46
+ end
47
+
48
+ # Build a version action for a parent config
49
+ #
50
+ # @return [Cliqr::CLI::Config] New action config
51
+ def self.build_version_action(config)
52
+ cli = Cliqr.interface do
53
+ name 'version'
54
+ description "Get version information for command \"#{config.command}\"."
55
+ handler do
56
+ puts config.version
57
+ end
58
+ shell :disable
59
+ end
60
+ cli.config
61
+ end
62
+
63
+ # Build a version option for a parent config
64
+ #
65
+ # @return [Cliqr::CLI::OptionConfig] New option config
66
+ def self.build_version_option(config)
67
+ Cliqr::CLI::OptionConfig.new.tap do |option_config|
68
+ option_config.name = 'version'
69
+ option_config.short = 'v'
70
+ option_config.description = "Get version information for command \"#{config.command}\"."
71
+ option_config.type = Cliqr::CLI::BOOLEAN_ARGUMENT_TYPE
72
+ option_config.operator = Cliqr::CLI::ArgumentOperator::DEFAULT_ARGUMENT_OPERATOR
73
+ option_config.finalize
74
+ end
75
+ end
76
+
77
+ # Action handler for help action
78
+ #
79
+ # @return [Proc]
80
+ def self.help_action_handler(config)
81
+ proc do
82
+ fail Cliqr::Error::IllegalArgumentError,
83
+ "too many arguments for \"#{command}\" command" if arguments.length > 1
84
+ action_config = arguments.length == 0 ? config : config.action(arguments.first)
85
+ puts Cliqr::CLI::UsageBuilder.build(action_config)
86
+ end
87
+ end
88
+
89
+ # Build a shell action for a parent config
90
+ #
91
+ # @return [Cliqr::CLI::Config] New action config
92
+ def self.build_shell_action(config)
93
+ cli = Cliqr.interface do
94
+ name 'shell'
95
+ description "Execute a shell in the context of \"#{config.command}\" command."
96
+ handler Cliqr::CLI::ShellCommand
97
+ shell :disable
98
+ end
99
+ cli.config
100
+ end
101
+
102
+ # Sanitize raw command line arguments
103
+ #
104
+ # @return [Array<String>]
105
+ def self.sanitize_args(args, config = nil)
106
+ sanitized = []
107
+ if args.is_a?(Array)
108
+ args.each { |arg| sanitized.concat(sanitize_args(arg)) }
109
+ elsif args.is_a?(String)
110
+ sanitized = args.split(' ')
111
+ end
112
+ remove_base_command(sanitized, config)
113
+ end
114
+
115
+ # Remove base command form sanitized list of arguments
116
+ #
117
+ # @return [Array<String>]
118
+ def self.remove_base_command(sanitized, config)
119
+ if !config.nil? && sanitized[0] == config.root.name.to_s
120
+ sanitized.drop(1)
121
+ else
122
+ sanitized
123
+ end
124
+ end
125
+
126
+ # Get handler that forwards command to the help action
127
+ #
128
+ # @return [Proc]
129
+ def self.forward_to_help_handler
130
+ proc do
131
+ forward "#{command} help"
132
+ end
133
+ end
134
+ end
135
+ end
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Versioned gem
4
4
  module Cliqr
5
- VERSION = '1.1.0'
5
+ VERSION = '1.2.0'
6
6
  end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ require 'cliqr/parser/argument_parser'
6
+ require 'cliqr/parser/parsed_input'
7
+
8
+ require 'fixtures/test_command'
9
+
10
+ def assert_results(config, args, expected_result, expected_config = nil)
11
+ expected_config ||= config
12
+ action_config, actual_parsed_input = Cliqr::Parser.parse(config, args)
13
+ expect(actual_parsed_input).to eq(expected_result)
14
+ expect(action_config).to eq(expected_config)
15
+ end