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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +128 -1
- data/README.md +97 -71
- data/examples/README.md +12 -0
- data/examples/hbase +58 -0
- data/examples/my-command +63 -0
- data/examples/numbers +55 -0
- data/examples/vagrant +90 -0
- data/lib/cliqr.rb +17 -2
- data/lib/cliqr/argument_validation/argument_type_validator.rb +2 -2
- data/lib/cliqr/argument_validation/validator.rb +1 -1
- data/lib/cliqr/cli/argument_operator.rb +44 -0
- data/lib/cliqr/cli/argument_operator_context.rb +20 -0
- data/lib/cliqr/cli/command.rb +1 -1
- data/lib/cliqr/cli/command_context.rb +93 -12
- data/lib/cliqr/cli/command_runner_factory.rb +2 -2
- data/lib/cliqr/cli/config.rb +301 -33
- data/lib/cliqr/cli/executor.rb +14 -9
- data/lib/cliqr/cli/interface.rb +22 -7
- data/lib/cliqr/cli/router.rb +6 -2
- data/lib/cliqr/cli/shell_command.rb +69 -0
- data/lib/cliqr/cli/usage_builder.rb +185 -0
- data/lib/cliqr/config_validation/validator_factory.rb +59 -5
- data/lib/cliqr/error.rb +10 -4
- data/lib/cliqr/parser/action_token.rb +23 -0
- data/lib/cliqr/parser/argument_parser.rb +1 -1
- data/lib/cliqr/parser/argument_token.rb +1 -4
- data/lib/cliqr/parser/argument_tree_walker.rb +40 -8
- data/lib/cliqr/parser/option_token.rb +2 -1
- data/lib/cliqr/parser/parsed_input.rb +21 -2
- data/lib/cliqr/parser/parsed_input_builder.rb +11 -7
- data/lib/cliqr/parser/token.rb +3 -9
- data/lib/cliqr/parser/token_factory.rb +1 -1
- data/lib/cliqr/util.rb +135 -0
- data/lib/cliqr/version.rb +1 -1
- data/spec/argument_parser_spec_helper.rb +15 -0
- data/spec/config/action_config_validator_spec.rb +146 -0
- data/spec/config/config_finalize_spec.rb +1 -1
- data/spec/config/config_validator_spec.rb +29 -19
- data/spec/config/option_config_validator_spec.rb +13 -13
- data/spec/dsl/interface_spec.rb +1 -168
- data/spec/dsl/usage_spec.rb +705 -0
- data/spec/executor/action_executor_spec.rb +205 -0
- data/spec/executor/executor_spec.rb +405 -17
- data/spec/executor/help_executor_spec.rb +424 -0
- data/spec/executor/shell_executor_spec.rb +233 -0
- data/spec/fixtures/action_reader_command.rb +12 -0
- data/spec/fixtures/csv_argument_operator.rb +8 -0
- data/spec/fixtures/test_option_type_checker_command.rb +8 -0
- data/spec/parser/action_argument_parser_spec.rb +113 -0
- data/spec/parser/argument_parser_spec.rb +37 -44
- data/spec/spec_helper.rb +1 -0
- data/spec/validation/action_argument_validator_spec.rb +50 -0
- data/spec/validation/{argument_validation_spec.rb → command_argument_validation_spec.rb} +36 -18
- data/spec/validation/error_spec.rb +1 -1
- data/tasks/rdoc.rake +16 -0
- data/tasks/rubucop.rake +14 -0
- data/tasks/yard.rake +21 -0
- data/templates/usage.erb +39 -0
- metadata +48 -11
data/lib/cliqr/error.rb
CHANGED
@@ -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
|
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
|
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
|
76
|
-
class
|
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
|
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)
|
@@ -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>]
|
23
|
+
# @param [Array<String>] raw_args List of arguments that needs to parsed
|
24
24
|
#
|
25
|
-
# @return [
|
26
|
-
def walk(
|
27
|
-
|
28
|
-
|
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,
|
34
|
-
|
35
|
-
input_builder
|
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
|
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
|
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 [
|
43
|
+
# @param [Cliqr::CLI::Parser::ArgumentToken] token Argument token
|
41
44
|
#
|
42
45
|
# @return [Cliqr::Parser::ParsedInputBuilder] Updated input builder
|
43
|
-
def
|
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.
|
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 = @
|
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?
|
data/lib/cliqr/parser/token.rb
CHANGED
@@ -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
|
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.
|
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::
|
38
|
+
fail Cliqr::Error::IllegalArgumentError, "invalid command argument \"#{arg}\"" \
|
39
39
|
unless @config.arguments?
|
40
40
|
ArgumentToken.new(arg)
|
41
41
|
end
|
data/lib/cliqr/util.rb
ADDED
@@ -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
|
data/lib/cliqr/version.rb
CHANGED
@@ -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
|