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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -0
  3. data/README.md +46 -17
  4. data/lib/cliqr/argument_validation/argument_type_validator.rb +31 -0
  5. data/lib/cliqr/argument_validation/option_validator.rb +27 -0
  6. data/lib/cliqr/argument_validation/validator.rb +67 -0
  7. data/lib/cliqr/cli/command_context.rb +21 -12
  8. data/lib/cliqr/cli/config.rb +57 -9
  9. data/lib/cliqr/cli/executor.rb +17 -11
  10. data/lib/cliqr/cli/interface.rb +7 -3
  11. data/lib/cliqr/error.rb +17 -19
  12. data/lib/cliqr/parser/argument_parser.rb +21 -0
  13. data/lib/cliqr/parser/argument_tree_walker.rb +51 -0
  14. data/lib/cliqr/parser/boolean_option_token.rb +26 -0
  15. data/lib/cliqr/parser/parsed_input.rb +53 -0
  16. data/lib/cliqr/parser/parsed_input_builder.rb +61 -0
  17. data/lib/cliqr/parser/single_valued_option_token.rb +53 -0
  18. data/lib/cliqr/parser/token.rb +45 -0
  19. data/lib/cliqr/parser/token_factory.rb +69 -0
  20. data/lib/cliqr/validation/validation_set.rb +48 -0
  21. data/lib/cliqr/validation/validator_factory.rb +265 -0
  22. data/lib/cliqr/validation/verifiable.rb +89 -0
  23. data/lib/cliqr/validation_errors.rb +61 -0
  24. data/lib/cliqr/version.rb +1 -1
  25. data/spec/config/config_validator_spec.rb +51 -30
  26. data/spec/config/option_config_validator_spec.rb +143 -0
  27. data/spec/dsl/interface_spec.rb +48 -114
  28. data/spec/executor/executor_spec.rb +19 -1
  29. data/spec/fixtures/test_option_checker_command.rb +8 -0
  30. data/spec/parser/argument_parser_spec.rb +33 -39
  31. data/spec/validation/argument_validation_spec.rb +141 -0
  32. data/spec/validation/error_spec.rb +22 -0
  33. data/spec/validation/validation_spec.rb +11 -0
  34. metadata +27 -10
  35. data/lib/cliqr/cli/argument_validator.rb +0 -19
  36. data/lib/cliqr/cli/config_validator.rb +0 -104
  37. data/lib/cliqr/cli/parser/argument_parser.rb +0 -23
  38. data/lib/cliqr/cli/parser/argument_tree_walker.rb +0 -56
  39. data/lib/cliqr/cli/parser/option_token.rb +0 -72
  40. data/lib/cliqr/cli/parser/parsed_argument_builder.rb +0 -66
  41. data/lib/cliqr/cli/parser/token.rb +0 -38
  42. data/lib/cliqr/cli/parser/token_factory.rb +0 -58
@@ -2,8 +2,8 @@
2
2
 
3
3
  require 'cliqr/cli/router'
4
4
  require 'cliqr/cli/command_context'
5
- require 'cliqr/cli/argument_validator'
6
- require 'cliqr/cli/parser/argument_parser'
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 = ArgumentValidator.new
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
- 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)
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
- # @return [Hash] Parsed hash of command line arguments
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
- parsed_args = Parser.parse(@config, args)
43
- @validator.validate(parsed_args)
44
- parsed_args
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
@@ -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
- ConfigValidator.validate @config
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 < StandardError; end
52
+ class ConfigNotFound < CliqrError; end
46
53
 
47
- # Raised when basename is not defined
48
- class BasenameNotDefined < StandardError; end
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