cliqr 0.0.4 → 0.1.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 +207 -49
- data/README.md +8 -8
- data/lib/cliqr.rb +3 -3
- data/lib/cliqr/cli/argument_validator.rb +19 -0
- data/lib/cliqr/cli/command_context.rb +101 -0
- data/lib/cliqr/cli/config.rb +68 -15
- data/lib/cliqr/cli/config_validator.rb +104 -0
- data/lib/cliqr/cli/executor.rb +48 -0
- data/lib/cliqr/cli/interface.rb +50 -13
- data/lib/cliqr/cli/parser/argument_parser.rb +23 -0
- data/lib/cliqr/cli/parser/argument_tree_walker.rb +56 -0
- data/lib/cliqr/cli/parser/option_token.rb +72 -0
- data/lib/cliqr/cli/parser/parsed_argument_builder.rb +66 -0
- data/lib/cliqr/cli/parser/token.rb +38 -0
- data/lib/cliqr/cli/parser/token_factory.rb +58 -0
- data/lib/cliqr/cli/router.rb +24 -0
- data/lib/cliqr/error.rb +49 -5
- data/lib/cliqr/version.rb +1 -1
- data/spec/config/config_validator_spec.rb +21 -6
- data/spec/dsl/interface_spec.rb +137 -1
- data/spec/executor/command_runner_spec.rb +2 -4
- data/spec/executor/executor_spec.rb +85 -0
- data/spec/{executor/fixtures → fixtures}/always_error_command.rb +1 -1
- data/spec/fixtures/option_reader_command.rb +11 -0
- data/spec/{executor/fixtures → fixtures}/test_command.rb +1 -1
- data/spec/fixtures/test_option_reader_command.rb +8 -0
- data/spec/parser/argument_parser_spec.rb +100 -0
- metadata +24 -10
- data/lib/cliqr/cli/builder.rb +0 -31
- data/lib/cliqr/cli/validator.rb +0 -31
- data/spec/executor/router_spec.rb +0 -25
data/lib/cliqr/cli/config.rb
CHANGED
@@ -39,7 +39,9 @@ module Cliqr
|
|
39
39
|
@basename = UNSET
|
40
40
|
@description = UNSET
|
41
41
|
@handler = UNSET
|
42
|
-
|
42
|
+
|
43
|
+
@options = []
|
44
|
+
@option_index = {}
|
43
45
|
end
|
44
46
|
|
45
47
|
# Finalize config by adding default values for unset values
|
@@ -49,7 +51,6 @@ module Cliqr
|
|
49
51
|
@basename = '' if @basename == UNSET
|
50
52
|
@description = '' if @description == UNSET
|
51
53
|
@handler = nil if @handler == UNSET
|
52
|
-
@options = [] if @options == UNSET
|
53
54
|
|
54
55
|
self
|
55
56
|
end
|
@@ -60,10 +61,10 @@ module Cliqr
|
|
60
61
|
#
|
61
62
|
# @param [Object] value Value for the config parameter
|
62
63
|
#
|
63
|
-
# @param [
|
64
|
+
# @param [Function] block Function which populates configuration for a sub-attribute
|
64
65
|
#
|
65
|
-
# @return [Object]
|
66
|
-
# @return [Cliqr::CLI::OptionConfig]
|
66
|
+
# @return [Object] if setting a attribute's value
|
67
|
+
# @return [Cliqr::CLI::OptionConfig] if adding a new option
|
67
68
|
def set_config(name, value, &block)
|
68
69
|
case name
|
69
70
|
when :option
|
@@ -73,6 +74,31 @@ module Cliqr
|
|
73
74
|
end
|
74
75
|
end
|
75
76
|
|
77
|
+
# Check if options are set
|
78
|
+
#
|
79
|
+
# @return [Boolean] <tt>true</tt> if the CLI config's options have been set
|
80
|
+
def options?
|
81
|
+
@options != UNSET
|
82
|
+
end
|
83
|
+
|
84
|
+
# Check if particular option is set
|
85
|
+
#
|
86
|
+
# @param [String] name Name of the option to check
|
87
|
+
#
|
88
|
+
# @return [Boolean] <tt>true</tt> if the a CLI config's option is set
|
89
|
+
def option?(name)
|
90
|
+
@option_index.key?(name)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Get value of a option
|
94
|
+
#
|
95
|
+
# @param [String] name Name of the option
|
96
|
+
#
|
97
|
+
# @return [String] value for the option
|
98
|
+
def option(name)
|
99
|
+
@option_index[name]
|
100
|
+
end
|
101
|
+
|
76
102
|
private
|
77
103
|
|
78
104
|
# Set value for config option without evaluating a block
|
@@ -96,8 +122,14 @@ module Cliqr
|
|
96
122
|
def handle_option(name, &block)
|
97
123
|
option_config = OptionConfig.build(&block)
|
98
124
|
option_config.name = name
|
99
|
-
|
125
|
+
|
126
|
+
OptionConfigValidator.validate(option_config, self)
|
127
|
+
|
100
128
|
@options.push option_config
|
129
|
+
|
130
|
+
@option_index[option_config.name] = option_config
|
131
|
+
@option_index[option_config.short] = option_config if option_config.short?
|
132
|
+
|
101
133
|
option_config
|
102
134
|
end
|
103
135
|
end
|
@@ -123,6 +155,17 @@ module Cliqr
|
|
123
155
|
# @return [String]
|
124
156
|
attr_accessor :description
|
125
157
|
|
158
|
+
# Set value for command option's attribute
|
159
|
+
#
|
160
|
+
# @param [Symbol] name Name of the attribute
|
161
|
+
#
|
162
|
+
# @param [Object] value Value for the attribute
|
163
|
+
#
|
164
|
+
# @return [Object] Value that was set for the attribute
|
165
|
+
def set_config(name, value)
|
166
|
+
handle_option_config name, value
|
167
|
+
end
|
168
|
+
|
126
169
|
# Initialize a new config instance for an option with UNSET attribute values
|
127
170
|
def initialize
|
128
171
|
@name = UNSET
|
@@ -134,22 +177,32 @@ module Cliqr
|
|
134
177
|
#
|
135
178
|
# @return [Cliqr::CLI::OptionConfig]
|
136
179
|
def finalize
|
137
|
-
@name =
|
138
|
-
@short =
|
139
|
-
@description =
|
180
|
+
@name = nil if @name == UNSET
|
181
|
+
@short = nil if @short == UNSET
|
182
|
+
@description = nil if @description == UNSET
|
140
183
|
|
141
184
|
self
|
142
185
|
end
|
143
186
|
|
144
|
-
#
|
187
|
+
# Check if a option's name is defined
|
145
188
|
#
|
146
|
-
# @
|
189
|
+
# @return [Boolean] <tt>true</tt> if options' name is not null neither empty
|
190
|
+
def name?
|
191
|
+
!(@name.nil? || @name.empty?)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Check if a option's short name is defined
|
147
195
|
#
|
148
|
-
# @
|
196
|
+
# @return [Boolean] <tt>true</tt> if options' short name is not null neither empty
|
197
|
+
def short?
|
198
|
+
!(@short.nil? || @short.empty?)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Check if a option's description is defined
|
149
202
|
#
|
150
|
-
# @return [
|
151
|
-
def
|
152
|
-
|
203
|
+
# @return [Boolean] <tt>true</tt> if options' description is not null neither empty
|
204
|
+
def description?
|
205
|
+
!(@description.nil? || @description.empty?)
|
153
206
|
end
|
154
207
|
|
155
208
|
private
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'cliqr/error'
|
4
|
+
|
5
|
+
module Cliqr
|
6
|
+
module CLI
|
7
|
+
# Validator for the command line interface configuration
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class ConfigValidator
|
11
|
+
# Validates the config to make sure all the options are correctly set
|
12
|
+
#
|
13
|
+
# @param [Cliqr::CLI::Config] config Settings for building command line interface
|
14
|
+
#
|
15
|
+
# @return [Cliqr::CLI::Config] Validated config object
|
16
|
+
def self.validate(config)
|
17
|
+
fail Cliqr::Error::ConfigNotFound, 'a valid config should be defined' if config.nil?
|
18
|
+
fail Cliqr::Error::BasenameNotDefined, 'basename not defined' if config.basename.empty?
|
19
|
+
|
20
|
+
fail Cliqr::Error::HandlerNotDefined,
|
21
|
+
"handler not defined for command \"#{config.basename}\"" if config.handler.nil?
|
22
|
+
|
23
|
+
fail Cliqr::Error::InvalidCommandHandler,
|
24
|
+
"handler for command \"#{config.basename}\" should extend from [Cliqr::CLI::Command]" \
|
25
|
+
unless config.handler < Command
|
26
|
+
|
27
|
+
check_options(config)
|
28
|
+
|
29
|
+
config
|
30
|
+
end
|
31
|
+
|
32
|
+
# Check if config's options list is not nil
|
33
|
+
#
|
34
|
+
# @param [Cliqr::CLI::Config] config Configuration settings for the command line interface
|
35
|
+
#
|
36
|
+
# @return [Cliqr::CLI::Config] Validated config
|
37
|
+
def self.check_options(config)
|
38
|
+
fail Cliqr::Error::OptionsNotDefinedException,
|
39
|
+
"option array is nil for command \"#{config.basename}\"" if config.options.nil?
|
40
|
+
|
41
|
+
config
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Validator for validating option configuration in a CLI interface config
|
46
|
+
#
|
47
|
+
# @api private
|
48
|
+
class OptionConfigValidator
|
49
|
+
# Validate a command line interface's config for an option
|
50
|
+
#
|
51
|
+
# @param [Cliqr::CLI::OptionConfig] config Config for this particular option
|
52
|
+
# @param [Cliqr::CLI::Config] parent_config Config of the parent config instance
|
53
|
+
#
|
54
|
+
# @return [Cliqr::CLI::OptionConfig] Validated OptionConfig instance
|
55
|
+
def self.validate(config, parent_config)
|
56
|
+
validate_option_name(config, parent_config)
|
57
|
+
|
58
|
+
fail Cliqr::Error::InvalidOptionDefinition,
|
59
|
+
"option \"#{config.name}\" has empty short name" \
|
60
|
+
if !config.short.nil? && config.short.empty?
|
61
|
+
|
62
|
+
validate_short_option(config, parent_config) if config.short?
|
63
|
+
|
64
|
+
config
|
65
|
+
end
|
66
|
+
|
67
|
+
# Validates name for an option
|
68
|
+
#
|
69
|
+
# @param [Cliqr::CLI::OptionConfig] config Config for this particular option
|
70
|
+
# @param [Cliqr::CLI::Config] parent_config Config of the parent config instance
|
71
|
+
#
|
72
|
+
# @return [Cliqr::CLI::OptionConfig] Validated OptionConfig instance
|
73
|
+
def self.validate_option_name(config, parent_config)
|
74
|
+
fail Cliqr::Error::DuplicateOptions,
|
75
|
+
"multiple options with long name \"#{config.name}\"" \
|
76
|
+
if parent_config.option?(config.name)
|
77
|
+
|
78
|
+
fail Cliqr::Error::InvalidOptionDefinition,
|
79
|
+
"option number #{parent_config.options.length + 1} does not have a name field" \
|
80
|
+
unless config.name?
|
81
|
+
|
82
|
+
config
|
83
|
+
end
|
84
|
+
|
85
|
+
# Validates short name for an option
|
86
|
+
#
|
87
|
+
# @param [Cliqr::CLI::OptionConfig] config Config for this particular option
|
88
|
+
# @param [Cliqr::CLI::Config] parent_config Config of the parent config instance
|
89
|
+
#
|
90
|
+
# @return [Cliqr::CLI::OptionConfig] Validated OptionConfig instance
|
91
|
+
def self.validate_short_option(config, parent_config)
|
92
|
+
fail Cliqr::Error::DuplicateOptions,
|
93
|
+
"multiple options with short name \"#{config.short}\"" \
|
94
|
+
if parent_config.option?(config.short)
|
95
|
+
|
96
|
+
fail Cliqr::Error::InvalidOptionDefinition,
|
97
|
+
"short option name can not have more than one characters in \"#{config.name}\"" \
|
98
|
+
if /^[a-z0-9A-Z]$/.match(config.short).nil?
|
99
|
+
|
100
|
+
config
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'cliqr/cli/router'
|
4
|
+
require 'cliqr/cli/command_context'
|
5
|
+
require 'cliqr/cli/argument_validator'
|
6
|
+
require 'cliqr/cli/parser/argument_parser'
|
7
|
+
|
8
|
+
module Cliqr
|
9
|
+
module CLI
|
10
|
+
# Handles command execution with error handling
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
class Executor
|
14
|
+
# Create a new command executor
|
15
|
+
def initialize(config)
|
16
|
+
@config = config
|
17
|
+
@router = Router.new(config)
|
18
|
+
@validator = ArgumentValidator.new
|
19
|
+
end
|
20
|
+
|
21
|
+
# Execute the command
|
22
|
+
#
|
23
|
+
# @param [Array<String>] args Arguments that will be used to execute the command
|
24
|
+
# @param [Hash] options Options for command execution
|
25
|
+
#
|
26
|
+
# @return [Integer] Exit status of the command execution
|
27
|
+
def execute(args, options)
|
28
|
+
command_context = CommandContext.build(parse(args))
|
29
|
+
@router.handle command_context, **options
|
30
|
+
rescue StandardError => e
|
31
|
+
raise Cliqr::Error::CommandRuntimeException.new("command '#{@config.basename}' failed", e)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# Invoke the command line argument parser
|
37
|
+
#
|
38
|
+
# @param [Array<String>] args List of arguments that needs to parsed
|
39
|
+
#
|
40
|
+
# @return [Hash] Parsed hash of command line arguments
|
41
|
+
def parse(args)
|
42
|
+
parsed_args = Parser.parse(@config, args)
|
43
|
+
@validator.validate(parsed_args)
|
44
|
+
parsed_args
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/cliqr/cli/interface.rb
CHANGED
@@ -2,20 +2,27 @@
|
|
2
2
|
|
3
3
|
require 'cliqr/error'
|
4
4
|
|
5
|
-
require 'cliqr/cli/
|
6
|
-
require 'cliqr/cli/
|
5
|
+
require 'cliqr/cli/executor'
|
6
|
+
require 'cliqr/cli/config_validator'
|
7
7
|
|
8
8
|
module Cliqr
|
9
|
+
# Definition and builder for command line interface
|
9
10
|
module CLI
|
10
11
|
# A CLI interface instance which is the entry point for all CLI commands.
|
11
12
|
#
|
12
13
|
# @api private
|
13
14
|
class Interface
|
15
|
+
# Command line interface configuration
|
16
|
+
#
|
17
|
+
# @return [Cliqr::CLI::Config]
|
18
|
+
attr_accessor :config
|
19
|
+
|
14
20
|
# Create a new interface instance with a config
|
15
21
|
#
|
16
22
|
# @param [Cliqr::CLI::Config] config Config used to create this interface
|
17
23
|
def initialize(config)
|
18
24
|
@config = config
|
25
|
+
@executor = Executor.new(config)
|
19
26
|
end
|
20
27
|
|
21
28
|
# Get usage information of this command line interface instance
|
@@ -32,19 +39,49 @@ module Cliqr
|
|
32
39
|
|
33
40
|
# Execute a command
|
34
41
|
#
|
35
|
-
# @param [
|
42
|
+
# @param [Array<String>] args Arguments that will be used to execute the command
|
43
|
+
# @param [Hash] options Options for command execution
|
36
44
|
#
|
37
45
|
# @return [Integer] Exit code of the command execution
|
38
|
-
def execute(
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
46
|
+
def execute(args = [], **options)
|
47
|
+
options = {
|
48
|
+
:output => :default
|
49
|
+
}.merge(options)
|
50
|
+
@executor.execute(args, options)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Invoke the builder method for [Cliqr::CLI::Interface]
|
54
|
+
#
|
55
|
+
# @param [Cliqr::CLI::Config] config Instance of the command line config
|
56
|
+
#
|
57
|
+
# @return [Cliqr::CLI::Interface]
|
58
|
+
def self.build(config)
|
59
|
+
InterfaceBuilder.new(config).build
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
# Builder for [Cliqr::CLI::Interface]
|
66
|
+
#
|
67
|
+
# @api private
|
68
|
+
class InterfaceBuilder
|
69
|
+
# Start building a command line interface
|
70
|
+
#
|
71
|
+
# @param [Cliqr::CLI::Config] config the configuration options for the
|
72
|
+
# interface (validated using CLI::Validator)
|
73
|
+
#
|
74
|
+
# @return [Cliqr::CLI::ConfigBuilder]
|
75
|
+
def initialize(config)
|
76
|
+
@config = config
|
77
|
+
end
|
78
|
+
|
79
|
+
# Validate and build a cli interface based on the configuration options
|
80
|
+
#
|
81
|
+
# @return [Cliqr::CLI::Interface]
|
82
|
+
def build
|
83
|
+
ConfigValidator.validate @config
|
84
|
+
Interface.new(@config)
|
48
85
|
end
|
49
86
|
end
|
50
87
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'cliqr/cli/parser/argument_tree_walker'
|
4
|
+
|
5
|
+
module Cliqr
|
6
|
+
module CLI
|
7
|
+
# A set of utility methods and classes used to parse the command line arguments
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
module Parser
|
11
|
+
# Parse command line arguments based on [Cliqr::CLI::Config]
|
12
|
+
#
|
13
|
+
# @param [Cliqr::CLI::Config] config Command line configuration
|
14
|
+
# @param [Array<String>] args An array of arguments from command line
|
15
|
+
#
|
16
|
+
# @return [Hash] Parsed hash of command linet arguments
|
17
|
+
def self.parse(config, args)
|
18
|
+
tree_walker = ArgumentTreeWalker.new(config)
|
19
|
+
tree_walker.walk(args)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'cliqr/cli/parser/parsed_argument_builder'
|
4
|
+
require 'cliqr/cli/parser/token_factory'
|
5
|
+
|
6
|
+
module Cliqr
|
7
|
+
module CLI
|
8
|
+
module Parser
|
9
|
+
# Walks the list of arguments and parses them one token at a time
|
10
|
+
#
|
11
|
+
# @api private
|
12
|
+
class ArgumentTreeWalker
|
13
|
+
# Create a new instance
|
14
|
+
#
|
15
|
+
# @param [Cliqr::CLI::Config] config Configuration settings for the command line interface
|
16
|
+
#
|
17
|
+
# @return [Cliqr::CLI::Parser::ArgumentTreeWalker]
|
18
|
+
def initialize(config)
|
19
|
+
@config = config
|
20
|
+
end
|
21
|
+
|
22
|
+
# Parse the arguments and generate tokens by iterating over command line arguments
|
23
|
+
#
|
24
|
+
# @param [Array<String>] args List of arguments that needs to parsed
|
25
|
+
#
|
26
|
+
# @return [Hash] Parsed hash of command line arguments
|
27
|
+
def walk(args)
|
28
|
+
argument_builder = ParsedArgumentBuilder.new(@config)
|
29
|
+
token_factory = TokenFactory.new(@config)
|
30
|
+
token = token_factory.get_token
|
31
|
+
args.each do |arg|
|
32
|
+
token = handle_argument(arg, token, argument_builder, token_factory)
|
33
|
+
end
|
34
|
+
token.finalize
|
35
|
+
argument_builder.build
|
36
|
+
end
|
37
|
+
|
38
|
+
# Handle the next argument in the context of the current token
|
39
|
+
#
|
40
|
+
# @return [Cliqr::CLI::Parser::Token] The new active token in case <tt>current_token</tt>
|
41
|
+
# becomes inactive
|
42
|
+
def handle_argument(arg, current_token, argument_builder, token_factory)
|
43
|
+
if current_token.active?
|
44
|
+
current_token.append(arg)
|
45
|
+
arg = nil
|
46
|
+
end
|
47
|
+
unless current_token.active?
|
48
|
+
argument_builder.add_token(current_token)
|
49
|
+
current_token = token_factory.get_token(arg)
|
50
|
+
end
|
51
|
+
current_token
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|