cliqr 0.1.0 → 1.0.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 +55 -0
- data/README.md +46 -17
- data/lib/cliqr/argument_validation/argument_type_validator.rb +31 -0
- data/lib/cliqr/argument_validation/option_validator.rb +27 -0
- data/lib/cliqr/argument_validation/validator.rb +67 -0
- data/lib/cliqr/cli/command_context.rb +21 -12
- data/lib/cliqr/cli/config.rb +57 -9
- data/lib/cliqr/cli/executor.rb +17 -11
- data/lib/cliqr/cli/interface.rb +7 -3
- data/lib/cliqr/error.rb +17 -19
- data/lib/cliqr/parser/argument_parser.rb +21 -0
- data/lib/cliqr/parser/argument_tree_walker.rb +51 -0
- data/lib/cliqr/parser/boolean_option_token.rb +26 -0
- data/lib/cliqr/parser/parsed_input.rb +53 -0
- data/lib/cliqr/parser/parsed_input_builder.rb +61 -0
- data/lib/cliqr/parser/single_valued_option_token.rb +53 -0
- data/lib/cliqr/parser/token.rb +45 -0
- data/lib/cliqr/parser/token_factory.rb +69 -0
- data/lib/cliqr/validation/validation_set.rb +48 -0
- data/lib/cliqr/validation/validator_factory.rb +265 -0
- data/lib/cliqr/validation/verifiable.rb +89 -0
- data/lib/cliqr/validation_errors.rb +61 -0
- data/lib/cliqr/version.rb +1 -1
- data/spec/config/config_validator_spec.rb +51 -30
- data/spec/config/option_config_validator_spec.rb +143 -0
- data/spec/dsl/interface_spec.rb +48 -114
- data/spec/executor/executor_spec.rb +19 -1
- data/spec/fixtures/test_option_checker_command.rb +8 -0
- data/spec/parser/argument_parser_spec.rb +33 -39
- data/spec/validation/argument_validation_spec.rb +141 -0
- data/spec/validation/error_spec.rb +22 -0
- data/spec/validation/validation_spec.rb +11 -0
- metadata +27 -10
- data/lib/cliqr/cli/argument_validator.rb +0 -19
- data/lib/cliqr/cli/config_validator.rb +0 -104
- data/lib/cliqr/cli/parser/argument_parser.rb +0 -23
- data/lib/cliqr/cli/parser/argument_tree_walker.rb +0 -56
- data/lib/cliqr/cli/parser/option_token.rb +0 -72
- data/lib/cliqr/cli/parser/parsed_argument_builder.rb +0 -66
- data/lib/cliqr/cli/parser/token.rb +0 -38
- data/lib/cliqr/cli/parser/token_factory.rb +0 -58
data/lib/cliqr/cli/executor.rb
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
require 'cliqr/cli/router'
|
4
4
|
require 'cliqr/cli/command_context'
|
5
|
-
require 'cliqr/
|
6
|
-
require 'cliqr/
|
5
|
+
require 'cliqr/argument_validation/validator'
|
6
|
+
require 'cliqr/parser/argument_parser'
|
7
7
|
|
8
8
|
module Cliqr
|
9
9
|
module CLI
|
@@ -15,7 +15,7 @@ module Cliqr
|
|
15
15
|
def initialize(config)
|
16
16
|
@config = config
|
17
17
|
@router = Router.new(config)
|
18
|
-
@validator =
|
18
|
+
@validator = Cliqr::ArgumentValidation::Validator.new
|
19
19
|
end
|
20
20
|
|
21
21
|
# Execute the command
|
@@ -25,10 +25,13 @@ module Cliqr
|
|
25
25
|
#
|
26
26
|
# @return [Integer] Exit status of the command execution
|
27
27
|
def execute(args, options)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
parsed_input = parse(args)
|
29
|
+
begin
|
30
|
+
command_context = CommandContext.build(parsed_input)
|
31
|
+
@router.handle command_context, **options
|
32
|
+
rescue StandardError => e
|
33
|
+
raise Cliqr::Error::CommandRuntimeException.new("command '#{@config.basename}' failed", e)
|
34
|
+
end
|
32
35
|
end
|
33
36
|
|
34
37
|
private
|
@@ -37,11 +40,14 @@ module Cliqr
|
|
37
40
|
#
|
38
41
|
# @param [Array<String>] args List of arguments that needs to parsed
|
39
42
|
#
|
40
|
-
# @
|
43
|
+
# @throws [Cliqr::Error::ValidationError] If the input arguments do not satisfy validation
|
44
|
+
# criteria
|
45
|
+
#
|
46
|
+
# @return [Cliqr::Parser::ParsedInput] Parsed arguments wrapper
|
41
47
|
def parse(args)
|
42
|
-
|
43
|
-
@validator.validate(
|
44
|
-
|
48
|
+
parsed_input = Parser.parse(@config, args)
|
49
|
+
@validator.validate(parsed_input, @config)
|
50
|
+
parsed_input
|
45
51
|
end
|
46
52
|
end
|
47
53
|
end
|
data/lib/cliqr/cli/interface.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
require 'cliqr/error'
|
4
|
-
|
5
4
|
require 'cliqr/cli/executor'
|
6
|
-
require 'cliqr/cli/config_validator'
|
7
5
|
|
8
6
|
module Cliqr
|
9
7
|
# Definition and builder for command line interface
|
@@ -79,8 +77,14 @@ module Cliqr
|
|
79
77
|
# Validate and build a cli interface based on the configuration options
|
80
78
|
#
|
81
79
|
# @return [Cliqr::CLI::Interface]
|
80
|
+
#
|
81
|
+
# @throws [Cliqr::Error::ConfigNotFound] if a config is <tt>nil</tt>
|
82
|
+
# @throws [Cliqr::Error::ValidationError] if the validation for config fails
|
82
83
|
def build
|
83
|
-
|
84
|
+
fail Cliqr::Error::ConfigNotFound, 'a valid config should be defined' if @config.nil?
|
85
|
+
fail Cliqr::Error::ValidationError, \
|
86
|
+
"invalid Cliqr interface configuration - [#{@config.errors}]" unless @config.valid?
|
87
|
+
|
84
88
|
Interface.new(@config)
|
85
89
|
end
|
86
90
|
end
|
data/lib/cliqr/error.rb
CHANGED
@@ -25,12 +25,19 @@ module Cliqr
|
|
25
25
|
# @return [String] Error message including the cause of the error
|
26
26
|
def message
|
27
27
|
if cause?
|
28
|
-
"#{@error_message}\n\nCause: #{@cause.class} - #{@cause.message}"
|
28
|
+
"#{@error_message}\n\nCause: #{@cause.class} - #{@cause.message}\n"
|
29
29
|
else
|
30
30
|
@error_message
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
+
# Get string representation of the error
|
35
|
+
#
|
36
|
+
# @return [String]
|
37
|
+
def to_s
|
38
|
+
message
|
39
|
+
end
|
40
|
+
|
34
41
|
private
|
35
42
|
|
36
43
|
# Check if there was a nested cause for this error
|
@@ -42,16 +49,10 @@ module Cliqr
|
|
42
49
|
end
|
43
50
|
|
44
51
|
# Raised when the config parameter is nil
|
45
|
-
class ConfigNotFound <
|
52
|
+
class ConfigNotFound < CliqrError; end
|
46
53
|
|
47
|
-
#
|
48
|
-
class
|
49
|
-
|
50
|
-
# Raised when a command handler is not defined
|
51
|
-
class HandlerNotDefined < StandardError; end
|
52
|
-
|
53
|
-
# Raised if command handler does not extend from Cliqr::CLI::Command
|
54
|
-
class InvalidCommandHandler < StandardError; end
|
54
|
+
# Signifies that the validation of the configuration settings failed
|
55
|
+
class ValidationError < CliqrError; end
|
55
56
|
|
56
57
|
# Encapsulates the error that gets thrown during a command execution
|
57
58
|
class CommandRuntimeException < CliqrError; end
|
@@ -59,12 +60,6 @@ module Cliqr
|
|
59
60
|
# Error to signify that a command's runner is not available
|
60
61
|
class UnknownCommandRunnerException < CliqrError; end
|
61
62
|
|
62
|
-
# Raised if config's option array is nil
|
63
|
-
class OptionsNotDefinedException < CliqrError; end
|
64
|
-
|
65
|
-
# Indicates to the user that the command line option is invalid
|
66
|
-
class InvalidCommandOption < CliqrError; end
|
67
|
-
|
68
63
|
# Indicates to the user that the command line option is invalid
|
69
64
|
class UnknownCommandOption < CliqrError; end
|
70
65
|
|
@@ -77,10 +72,13 @@ module Cliqr
|
|
77
72
|
# Raised if two options are defined with same long or short name
|
78
73
|
class DuplicateOptions < CliqrError; end
|
79
74
|
|
80
|
-
# Raised if an option is not defined properly
|
81
|
-
class InvalidOptionDefinition < CliqrError; end
|
82
|
-
|
83
75
|
# Raised if an option is not defined properly
|
84
76
|
class InvalidArgumentError < CliqrError; end
|
77
|
+
|
78
|
+
# Raised if an argument does not conform to the option's type
|
79
|
+
class IllegalArgumentError < CliqrError; end
|
80
|
+
|
81
|
+
# Indicates that a unknown validator type is being used in a class
|
82
|
+
class UnknownValidatorType < CliqrError; end
|
85
83
|
end
|
86
84
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'cliqr/parser/argument_tree_walker'
|
4
|
+
|
5
|
+
module Cliqr
|
6
|
+
# A set of utility methods and classes used to parse the command line arguments
|
7
|
+
#
|
8
|
+
# @api private
|
9
|
+
module Parser
|
10
|
+
# Parse command line arguments based on [Cliqr::CLI::Config]
|
11
|
+
#
|
12
|
+
# @param [Cliqr::CLI::Config] config Command line configuration
|
13
|
+
# @param [Array<String>] args An array of arguments from command line
|
14
|
+
#
|
15
|
+
# @return [Hash] Parsed hash of command linet arguments
|
16
|
+
def self.parse(config, args)
|
17
|
+
tree_walker = ArgumentTreeWalker.new(config)
|
18
|
+
tree_walker.walk(args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'cliqr/parser/parsed_input_builder'
|
4
|
+
require 'cliqr/parser/token_factory'
|
5
|
+
|
6
|
+
module Cliqr
|
7
|
+
module Parser
|
8
|
+
# Walks the list of arguments and parses them one token at a time
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class ArgumentTreeWalker
|
12
|
+
# Create a new instance
|
13
|
+
#
|
14
|
+
# @param [Cliqr::CLI::Config] config Configuration settings for the command line interface
|
15
|
+
#
|
16
|
+
# @return [Cliqr::CLI::Parser::ArgumentTreeWalker]
|
17
|
+
def initialize(config)
|
18
|
+
@config = config
|
19
|
+
end
|
20
|
+
|
21
|
+
# Parse the arguments and generate tokens by iterating over command line arguments
|
22
|
+
#
|
23
|
+
# @param [Array<String>] args List of arguments that needs to parsed
|
24
|
+
#
|
25
|
+
# @return [Hash] Parsed hash of command line arguments
|
26
|
+
def walk(args)
|
27
|
+
argument_builder = ParsedInputBuilder.new(@config)
|
28
|
+
token_factory = TokenFactory.new(@config)
|
29
|
+
token = token_factory.get_token
|
30
|
+
args.each do |arg|
|
31
|
+
token = handle_argument(arg, token, token_factory)
|
32
|
+
argument_builder.add_token(token) unless token.active?
|
33
|
+
end
|
34
|
+
token.finalize if token.active?
|
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, token_factory)
|
43
|
+
if current_token.active?
|
44
|
+
current_token.append(arg)
|
45
|
+
else
|
46
|
+
token_factory.get_token(arg)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Cliqr
|
4
|
+
module Parser
|
5
|
+
# Token handler for parsing a boolean option
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class BooleanOptionToken < Token
|
9
|
+
# Create a new option token to store boolean value
|
10
|
+
def initialize(name, arg)
|
11
|
+
super(name, arg)
|
12
|
+
@value = !arg.to_s.start_with?('--no-')
|
13
|
+
end
|
14
|
+
|
15
|
+
# Get the token representation
|
16
|
+
#
|
17
|
+
# @return [Hash] A hash of the option name and its value (<tt>true</tt> or <tt>false</tt>)
|
18
|
+
def build
|
19
|
+
{
|
20
|
+
:name => @name.to_s,
|
21
|
+
:value => @value
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Cliqr
|
3
|
+
module Parser
|
4
|
+
# A wrapper to keep the parsed input arguments
|
5
|
+
#
|
6
|
+
# @api private
|
7
|
+
class ParsedInput
|
8
|
+
# Command name
|
9
|
+
#
|
10
|
+
# @return [String]
|
11
|
+
attr_accessor :command
|
12
|
+
|
13
|
+
# Hash of options parsed from the command line
|
14
|
+
#
|
15
|
+
# @return [Hash]
|
16
|
+
attr_accessor :options
|
17
|
+
|
18
|
+
# Initialize a new parsed input
|
19
|
+
def initialize(parsed_arguments)
|
20
|
+
@command = parsed_arguments[:command]
|
21
|
+
|
22
|
+
@options = Hash[parsed_arguments[:options].collect \
|
23
|
+
{ |option| [option[:name], option[:value]] }
|
24
|
+
]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Get a value of an option
|
28
|
+
#
|
29
|
+
# @param [String] name Name of the option
|
30
|
+
#
|
31
|
+
# @return [String]
|
32
|
+
def option(name)
|
33
|
+
@options[name]
|
34
|
+
end
|
35
|
+
|
36
|
+
# Test equality with another object
|
37
|
+
#
|
38
|
+
# @return [Boolean] <tt>true</tt> if this object is equal to <tt>other</tt>
|
39
|
+
def eql?(other)
|
40
|
+
self.class.equal?(other.class) &&
|
41
|
+
@command == other.command &&
|
42
|
+
@options == other.options
|
43
|
+
end
|
44
|
+
|
45
|
+
# Test equality with another object
|
46
|
+
#
|
47
|
+
# @return [Boolean] <tt>true</tt> if this object is equal to <tt>other</tt>
|
48
|
+
def ==(other)
|
49
|
+
eql?(other)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'cliqr/parser/parsed_input'
|
4
|
+
|
5
|
+
module Cliqr
|
6
|
+
module Parser
|
7
|
+
# Builder for collecting parsed command line arguments that can be used to
|
8
|
+
# build a command context
|
9
|
+
#
|
10
|
+
# @api private
|
11
|
+
class ParsedInputBuilder
|
12
|
+
# Initialize a new instance
|
13
|
+
#
|
14
|
+
# @param [Cliqr::CLI::Config] config Configuration settings for the command line interface
|
15
|
+
#
|
16
|
+
# @return [Cliqr::CLI::Parser::ParsedInputBuilder]
|
17
|
+
def initialize(config)
|
18
|
+
@config = config
|
19
|
+
@options = []
|
20
|
+
@option_names = Set.new
|
21
|
+
end
|
22
|
+
|
23
|
+
# Add a new parsed token from the list of arguments
|
24
|
+
#
|
25
|
+
# @param [Cliqr::CLI::Parser::Token] token A parsed token from command line arguments
|
26
|
+
#
|
27
|
+
# @return [Boolean] <tt>true</tt> if the token was added
|
28
|
+
def add_token(token)
|
29
|
+
return false unless token.valid?
|
30
|
+
|
31
|
+
add_option_name(token)
|
32
|
+
@options.push(token.build)
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
# Build the hash of parsed command line arguments
|
37
|
+
#
|
38
|
+
# @return [Cliqr::Parser::ParsedInput] Parsed arguments wrapper
|
39
|
+
def build
|
40
|
+
ParsedInput.new(:command => @config.basename,
|
41
|
+
:options => @options)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Add option's name to a list of already added options and fail if duplicate
|
47
|
+
#
|
48
|
+
# @param [Cliqr::CLI::Parser::Token] token A parsed token from command line arguments
|
49
|
+
#
|
50
|
+
# @return [Set<String>] Current list of option names
|
51
|
+
def add_option_name(token)
|
52
|
+
option_config = @config.option(token.name)
|
53
|
+
old_config = @option_names.add?(option_config.name)
|
54
|
+
fail Cliqr::Error::MultipleOptionValues,
|
55
|
+
"multiple values for option \"#{token.arg}\"" if old_config.nil?
|
56
|
+
@option_names.add(option_config.short) if option_config.short?
|
57
|
+
@option_names
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Cliqr
|
4
|
+
module Parser
|
5
|
+
# Token handler for parsing a option and its value
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class SingleValuedOptionToken < Token
|
9
|
+
# Create a new option token. Initial state will be <tt>active</tt>
|
10
|
+
def initialize(name, arg)
|
11
|
+
super(name, arg)
|
12
|
+
@value = nil
|
13
|
+
@active = true
|
14
|
+
end
|
15
|
+
|
16
|
+
# Check if the token handler is active and needs more arguments
|
17
|
+
#
|
18
|
+
# @return [Boolean] <tt>true</tt> if the token handler is active
|
19
|
+
def active?
|
20
|
+
@active
|
21
|
+
end
|
22
|
+
|
23
|
+
# Append the next argument in the series and set token to inactive
|
24
|
+
#
|
25
|
+
# @param [String] arg Argument value of the next command line parameter
|
26
|
+
#
|
27
|
+
# @return [Cliqr::Parser::SingleValuedOptionToken]
|
28
|
+
def append(arg)
|
29
|
+
@value = arg
|
30
|
+
@active = false
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get the token representation
|
35
|
+
#
|
36
|
+
# @return [Hash] Token name and value in a hash
|
37
|
+
def build
|
38
|
+
{
|
39
|
+
:name => @name.to_s,
|
40
|
+
:value => @value
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
# Called if this token handler was still active once the argument list ends
|
45
|
+
#
|
46
|
+
# @return [Cliqr::CLI::Parser::OptionToken] Current instance object
|
47
|
+
def finalize
|
48
|
+
# should not be called
|
49
|
+
fail Cliqr::Error::OptionValueMissing, "a value must be defined for option \"#{@arg}\""
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Cliqr
|
4
|
+
module Parser
|
5
|
+
# A NO-OP argument token
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class Token
|
9
|
+
# Name of the option token
|
10
|
+
#
|
11
|
+
# @return [String]
|
12
|
+
attr_accessor :name
|
13
|
+
|
14
|
+
# Argument that was used to parse the option name from
|
15
|
+
#
|
16
|
+
# @return [String]
|
17
|
+
attr_accessor :arg
|
18
|
+
|
19
|
+
# Create a new option token
|
20
|
+
#
|
21
|
+
# @param [String] name Long name of the option
|
22
|
+
# @param [String] arg Value of the option
|
23
|
+
#
|
24
|
+
# @return [Cliqr::CLI::Parser::OptionToken] A new Token instance
|
25
|
+
def initialize(name = nil, arg = nil)
|
26
|
+
@name = name
|
27
|
+
@arg = arg
|
28
|
+
end
|
29
|
+
|
30
|
+
# This token is never active
|
31
|
+
#
|
32
|
+
# @return [Boolean] This will always return <tt>false</tt> in this case
|
33
|
+
def active?
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
# A token is not valid if it does not have a name
|
38
|
+
#
|
39
|
+
# @return [Boolean] <tt>false</tt> if the token's name is nil
|
40
|
+
def valid?
|
41
|
+
!@name.nil?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|