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/examples/numbers
ADDED
@@ -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)
|
data/examples/vagrant
ADDED
@@ -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)
|
data/lib/cliqr.rb
CHANGED
@@ -16,7 +16,7 @@ module Cliqr
|
|
16
16
|
#
|
17
17
|
# @example
|
18
18
|
# Cliqr.interface do
|
19
|
-
#
|
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
|
20
|
+
when CLI::NUMERIC_ARGUMENT_TYPE
|
21
21
|
Integer(argument)
|
22
|
-
when
|
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
|
data/lib/cliqr/cli/command.rb
CHANGED
@@ -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(
|
33
|
-
@
|
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
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
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
|