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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 21be1b361f2ce33096983d813f3a9185018e5ffd
4
- data.tar.gz: f6abf3c9cdeddeb784d690b584505f23f350828f
3
+ metadata.gz: dbf6874d54c340fea81a85d5d9c5d495eaf4bf70
4
+ data.tar.gz: c3a763238d2cfb47d3cf479d7922a9fa43357656
5
5
  SHA512:
6
- metadata.gz: b0dfd50fbfeb23536cd14839f249cdc5f857a4562524c0935eb7788750e28c4cb1ec98c105384b77d2fa34ae8de8ec1e3752d39f45350acc9ac195a3d3f7a972
7
- data.tar.gz: 7e43a504e6548df5a6525832498715fd96ef291669af4f0b3d5a252bb53a5ffd8d68bfcd35c5c278605345727368d623f48dec4487f9589656313bb5c7be03a8
6
+ metadata.gz: db2d7b59f422dd286fc244b6ed263ddbef70f9a088b3c95018fda4113daf3525ba53d96b4e6d0d6b970ba1fe52bf4772be34b2acdc60131c08aa3a242a5ddc2b
7
+ data.tar.gz: e470554fbb6649e3685173b78bd6559f5d104b6215ef8fd7cdc4265679cf1df2d7a29973c46a0ef009cf3278f3e84ea6b82fe04820989985d9af8bda512655b5
data/CHANGELOG.md CHANGED
@@ -5,6 +5,16 @@ item in this nested table for further details.
5
5
  <!-- markdown-toc start - Don't edit this section. Run M-x markdown-toc/generate-toc again -->
6
6
  **Table of Contents**
7
7
 
8
+ - [1.0.0 / 2015-06-04](#100--2015-06-04)
9
+ - [Features](#features)
10
+ - [Support for option types](#support-for-option-types)
11
+ - [Improvements](#improvements)
12
+ - [Generic CLI config validator implementation](#generic-cli-config-validator-implementation)
13
+ - [Minor Improvements](#minor-improvements)
14
+ - [Reduce the number of error types](#reduce-the-number-of-error-types)
15
+ - [Maintain example in readme](#maintain-example-in-readme)
16
+ - [Bug-fixes](#bug-fixes)
17
+ - [It should be optional to include options in a command](#it-should-be-optional-to-include-options-in-a-command)
8
18
  - [0.1.0 / 2015-05-29](#010--2015-05-29)
9
19
  - [Features](#features)
10
20
  - [First pass at building ability to parse command line arguments](#first-pass-at-building-ability-to-parse-command-line-arguments)
@@ -51,6 +61,51 @@ item in this nested table for further details.
51
61
 
52
62
  <!-- markdown-toc end -->
53
63
 
64
+ 1.0.0 / 2015-06-04
65
+ ==================
66
+
67
+ Aaaaand here it is ladies and gentlemen! A major release version of
68
+ `cliqr`
69
+
70
+ The main feature that was added in this release is support for option
71
+ types. Namely: `:boolean` and `:numeric`. Along with this, a major
72
+ refactoring was done for the config validator framework to make it more
73
+ generic and easy to manage in the long run.
74
+
75
+ ## Features
76
+
77
+ ### Support for option types
78
+
79
+ We can now restrict option values to either a number or define a option
80
+ which is either present or not. This also requires a new and improved
81
+ option parser and validator.
82
+
83
+ ## Improvements
84
+
85
+ ### Generic CLI config validator implementation
86
+
87
+ The idea for this came from [https://github.com/lotus/validations/]. By
88
+ doing this we can make sure that it will be fairly easy to maintain the
89
+ validation logic and extend it in future.
90
+
91
+ ## Minor Improvements
92
+
93
+ ### Reduce the number of error types
94
+
95
+ This was made possible by using a generic validator. So, along with all
96
+ the custom validation code we were able to get rid of the custom error
97
+ types.
98
+
99
+ ### Maintain example in readme
100
+
101
+ The example in the readme file was updated to reflect the latest changes.
102
+
103
+ ## Bug-fixes
104
+
105
+ ### It should be optional to include options in a command
106
+
107
+ We will be adding a restriction to allow users to mark certain options
108
+ as required in future versions.
54
109
 
55
110
  0.1.0 / 2015-05-29
56
111
  ==================
data/README.md CHANGED
@@ -49,7 +49,12 @@ require 'cliqr'
49
49
  class MyCommandHandler < Cliqr.command
50
50
  def execute(context)
51
51
  puts 'executing my awesome command'
52
- puts "value for option 'test' is '#{context.option('test').value}'"
52
+ puts "value for option 'an-option' is '#{context.option('an-option').value}'"
53
+ puts "value for option 'count' is '#{context.option('count').value}'"
54
+ puts "value for option 'single' is '#{context.option('single').value}'"
55
+ puts "value for option 'test-1' is '#{context.option('test-1').value}'"
56
+ puts "has count argument" if context.option?('count')
57
+ puts "does not have test argument" unless context.option?('test-2')
53
58
  end
54
59
  end
55
60
 
@@ -62,25 +67,49 @@ cli = Cliqr.interface do
62
67
  short 'a'
63
68
  description 'this is a option'
64
69
  end
65
-
66
- option 'test'
70
+
71
+ option 'count' do
72
+ short 'c'
73
+ description 'count of something'
74
+ type :numeric
75
+ end
76
+
77
+ option 'single' do
78
+ short 's'
79
+ description 'a boolean option'
80
+ type :boolean
81
+ end
82
+
83
+ option 'test-1'
84
+ option 'test-2'
67
85
  end
68
86
 
69
87
  puts cli.usage
70
- #> my-command -- this is an awesome command...try it out
71
- #>
72
- #> USAGE:
73
- #> my-command
74
- #>
75
- #> Available options:
76
- #>
77
- #> --an-option, -a : this is a option
78
- #>
79
- #> --test
80
-
81
- cli.execute %w(--test some-value)
82
- #> executing my awesome command
83
- #> value for option 'test' is 'some-value'
88
+ # my-command -- this is an awesome command...try it out
89
+ #
90
+ # USAGE:
91
+ # my-command [options]
92
+ #
93
+ # Available options:
94
+ #
95
+ # --an-option, -a : this is a option
96
+ #
97
+ # --count, -c : <numeric> count of something
98
+ #
99
+ # --[no-]single, -s : <boolean> a boolean option
100
+ #
101
+ # --test-1
102
+ #
103
+ # --test-2
104
+
105
+ cli.execute %w(--an-option qwerty -c 86 --no-single --test-1 some-value)
106
+ # executing my awesome command
107
+ # value for option 'an-option' is 'qwerty'
108
+ # value for option 'count' is '86'
109
+ # value for option 'single' is 'false'
110
+ # value for option 'test-1' is 'some-value'
111
+ # has count argument
112
+ # does not have test argument
84
113
  ```
85
114
 
86
115
  ## Installation
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+ module Cliqr
3
+ module ArgumentValidation
4
+ # Validates type of a argument
5
+ class ArgumentTypeValidator
6
+ # Run the validation on a argument based on the option's configuration
7
+ #
8
+ # @return [Cliqr:ValidationErrors]
9
+ def validate(argument, option, errors)
10
+ errors.add("only values of type '#{option.type}' allowed for option '#{option.name}'") \
11
+ unless type_of?(argument, option.type)
12
+ errors
13
+ end
14
+
15
+ private
16
+
17
+ # Check if a type of a argument matches a required type
18
+ def type_of?(argument, required_type)
19
+ case required_type
20
+ when :numeric
21
+ Integer(argument)
22
+ when :boolean
23
+ fail ArgumentError unless argument.class == TrueClass || argument.class == FalseClass
24
+ end
25
+ true
26
+ rescue ArgumentError
27
+ false
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+ module Cliqr
3
+ module ArgumentValidation
4
+ # Runs all validators configured for an option
5
+ class OptionValidator
6
+ # Create a new option validator
7
+ def initialize
8
+ @validators = Set.new []
9
+ end
10
+
11
+ # Add a validator for this option
12
+ #
13
+ # @return [Set] All validators configured so far
14
+ def add(validator)
15
+ @validators.add(validator)
16
+ end
17
+
18
+ # Run all the validators for a option
19
+ #
20
+ # @return [Cliqr::ValidationErrors]
21
+ def validate(argument, option, errors)
22
+ @validators.each { |validator| validator.validate(argument, option, errors) }
23
+ errors
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+
3
+ require 'cliqr/error'
4
+ require 'cliqr/argument_validation/option_validator'
5
+ require 'cliqr/argument_validation/argument_type_validator'
6
+
7
+ module Cliqr
8
+ # Validate command arguments based on pre-configured settings
9
+ #
10
+ # @api private
11
+ module ArgumentValidation
12
+ # Utiity class to validate input to a command
13
+ #
14
+ # @api private
15
+ class Validator
16
+ # Initialize a new Validator instance
17
+ def initialize
18
+ @option_validators = {}
19
+ end
20
+
21
+ # Validate parsed command line arguments
22
+ #
23
+ # @param [Cliqr::Parser::ParsedInput] args Parsed input instance
24
+ #
25
+ # @return [Cliqr::Parser::ParsedInput] Validated parsed input
26
+ def validate(args, config)
27
+ errors = ValidationErrors.new
28
+ config.options.each do |option|
29
+ validate_argument(args.option(option.name), option, errors) \
30
+ if args.options.key?(option.name)
31
+ end
32
+ fail(Cliqr::Error::IllegalArgumentError, "illegal argument error - #{errors}") \
33
+ unless errors.empty?
34
+ args
35
+ end
36
+
37
+ private
38
+
39
+ # Validate a argument for an option and return errors
40
+ #
41
+ # @return [Cliqr::ValidationErrors]
42
+ def validate_argument(argument, option, errors)
43
+ option_validator = get_option_validator(option)
44
+ option_validator.validate(argument, option, errors)
45
+ errors
46
+ end
47
+
48
+ # Get or create validator for a option
49
+ #
50
+ # @return [Hash] A hash of all option mapped to its validator
51
+ def get_option_validator(option)
52
+ @option_validators[option] = build_option_validator(option) \
53
+ unless @option_validators.key?(option)
54
+ @option_validators[option]
55
+ end
56
+
57
+ # Create a new option validator
58
+ #
59
+ # @return [Cliqr::ArgumentValidation::OptionValidator]
60
+ def build_option_validator(option)
61
+ option_validator = OptionValidator.new
62
+ option_validator.add(ArgumentTypeValidator.new) if option.type?
63
+ option_validator
64
+ end
65
+ end
66
+ end
67
+ end
@@ -14,11 +14,11 @@ module Cliqr
14
14
 
15
15
  # Build a instance of command context based on the parsed set of arguments
16
16
  #
17
- # @param [Hash] parsed_args A hash of parsed command line arguments
17
+ # @param [Cliqr::Parser::ParsedInput] parsed_input Parsed input object
18
18
  #
19
19
  # @return [Cliqr::CLI::CommandContext]
20
- def self.build(parsed_args)
21
- CommandContextBuilder.new(parsed_args).build
20
+ def self.build(parsed_input)
21
+ CommandContextBuilder.new(parsed_input).build
22
22
  end
23
23
 
24
24
  # Initialize the command context (called by the CommandContextBuilder)
@@ -46,6 +46,15 @@ module Cliqr
46
46
  @options[name]
47
47
  end
48
48
 
49
+ # Check if a option with a specified name has been passed
50
+ #
51
+ # @param [String] name Name of the option
52
+ #
53
+ # @return [Boolean] <tt>true</tt> if the option has a argument value
54
+ def option?(name)
55
+ @options.key?(name)
56
+ end
57
+
49
58
  private :initialize
50
59
  end
51
60
 
@@ -57,19 +66,19 @@ module Cliqr
57
66
  class CommandContextBuilder
58
67
  # Initialize builder for CommandContext
59
68
  #
60
- # @param [Hash] parsed_args Parsed and validated command line arguments
69
+ # @param [Cliqr::Parser::ParsedInput] parsed_input Parsed and validated command line arguments
61
70
  #
62
71
  # @return [Cliqr::CLI::CommandContextBuilder]
63
- def initialize(parsed_args)
64
- @parsed_args = parsed_args
72
+ def initialize(parsed_input)
73
+ @parsed_input = parsed_input
65
74
  end
66
75
 
67
76
  # Build a new instance of CommandContext
68
77
  #
69
78
  # @return [Cliqr::CLI::CommandContext] A newly created CommandContext instance
70
79
  def build
71
- CommandContext.new @parsed_args[:command],
72
- @parsed_args[:options].map { |args| CommandOption.new(args) }
80
+ CommandContext.new @parsed_input.command,
81
+ @parsed_input.options.map { |option| CommandOption.new(option) }
73
82
  end
74
83
  end
75
84
 
@@ -89,12 +98,12 @@ module Cliqr
89
98
 
90
99
  # Create a new command line option instance
91
100
  #
92
- # @param [Hash] args Arguments for creating a command line option
101
+ # @param [Array] option Parsed arguments for creating a command line option
93
102
  #
94
103
  # @return [Cliqr::CLI::CommandContext] A new CommandOption object
95
- def initialize(args)
96
- @name = args[:name]
97
- @value = args[:value]
104
+ def initialize(option)
105
+ @value = option.pop
106
+ @name = option.pop
98
107
  end
99
108
  end
100
109
  end
@@ -1,6 +1,8 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  require 'cliqr/dsl'
4
+ require 'cliqr/validation/verifiable'
5
+ require 'cliqr/cli/command'
4
6
 
5
7
  module Cliqr
6
8
  # A extension for CLI module to group all config classes
@@ -13,11 +15,14 @@ module Cliqr
13
15
  # @api private
14
16
  class Config
15
17
  extend Cliqr::DSL
18
+ include Cliqr::Validation
16
19
 
17
20
  # Base name of the command
18
21
  #
19
22
  # @return [String]
20
23
  attr_accessor :basename
24
+ validates :basename,
25
+ non_empty_format: /^[a-zA-Z0-9_\-]+$/
21
26
 
22
27
  # Description for the base command
23
28
  #
@@ -26,13 +31,17 @@ module Cliqr
26
31
 
27
32
  # Command handler for the base command
28
33
  #
29
- # @return [Class]
34
+ # @return [Class<Cliqr::CLI::Command>]
30
35
  attr_accessor :handler
36
+ validates :handler,
37
+ extend: Cliqr::CLI::Command
31
38
 
32
39
  # Array of options applied to the base command
33
40
  #
34
41
  # @return [Array<OptionConfig>]
35
42
  attr_accessor :options
43
+ validates :options,
44
+ collection: true
36
45
 
37
46
  # New config instance with all attributes set as UNSET
38
47
  def initialize
@@ -123,7 +132,7 @@ module Cliqr
123
132
  option_config = OptionConfig.build(&block)
124
133
  option_config.name = name
125
134
 
126
- OptionConfigValidator.validate(option_config, self)
135
+ validate_option_name(option_config)
127
136
 
128
137
  @options.push option_config
129
138
 
@@ -132,6 +141,23 @@ module Cliqr
132
141
 
133
142
  option_config
134
143
  end
144
+
145
+ # Make sure that the option's name is unique
146
+ #
147
+ # @param [Cliqr::CLI::OptionConfig] option_config Config for this particular option
148
+ #
149
+ # @return [Cliqr::CLI::OptionConfig] Validated OptionConfig instance
150
+ def validate_option_name(option_config)
151
+ fail Cliqr::Error::DuplicateOptions,
152
+ "multiple options with long name \"#{option_config.name}\"" \
153
+ if option?(option_config.name)
154
+
155
+ fail Cliqr::Error::DuplicateOptions,
156
+ "multiple options with short name \"#{option_config.short}\"" \
157
+ if option?(option_config.short)
158
+
159
+ option_config
160
+ end
135
161
  end
136
162
 
137
163
  # Config attributes for a command's option
@@ -139,22 +165,35 @@ module Cliqr
139
165
  # @api private
140
166
  class OptionConfig
141
167
  extend Cliqr::DSL
168
+ include Cliqr::Validation
142
169
 
143
170
  # Long option name
144
171
  #
145
172
  # @return [String]
146
173
  attr_accessor :name
174
+ validates :name,
175
+ non_empty: true,
176
+ format: /^[a-zA-Z0-9_\-]*$/
147
177
 
148
178
  # Optional short name for the option
149
179
  #
150
180
  # @return [String]
151
181
  attr_accessor :short
182
+ validates :short,
183
+ non_empty_nil_ok_format: /^[a-z0-9A-Z]$/
152
184
 
153
185
  # A description string for the option
154
186
  #
155
187
  # @return [String]
156
188
  attr_accessor :description
157
189
 
190
+ # Optional field that restricts values of this option to a certain type
191
+ #
192
+ # @return [Symbol] Type of the option
193
+ attr_accessor :type
194
+ validates :type,
195
+ inclusion: [:any, :numeric, :boolean]
196
+
158
197
  # Set value for command option's attribute
159
198
  #
160
199
  # @param [Symbol] name Name of the attribute
@@ -171,6 +210,7 @@ module Cliqr
171
210
  @name = UNSET
172
211
  @short = UNSET
173
212
  @description = UNSET
213
+ @type = UNSET
174
214
  end
175
215
 
176
216
  # Finalize option's config by adding default values for unset values
@@ -180,17 +220,11 @@ module Cliqr
180
220
  @name = nil if @name == UNSET
181
221
  @short = nil if @short == UNSET
182
222
  @description = nil if @description == UNSET
223
+ @type = :any if @type == UNSET
183
224
 
184
225
  self
185
226
  end
186
227
 
187
- # Check if a option's name is defined
188
- #
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
228
  # Check if a option's short name is defined
195
229
  #
196
230
  # @return [Boolean] <tt>true</tt> if options' short name is not null neither empty
@@ -205,6 +239,20 @@ module Cliqr
205
239
  !(@description.nil? || @description.empty?)
206
240
  end
207
241
 
242
+ # Check if a option's type is defined
243
+ #
244
+ # @return [Boolean] <tt>true</tt> if options' type is not nil and not equal to <tt>:any</tt>
245
+ def type?
246
+ !@type.nil? && @type != :any
247
+ end
248
+
249
+ # Check if a option is of boolean type
250
+ #
251
+ # @return [Boolean] <tt>true</tt> is the option is of type <tt>:boolean</tt>
252
+ def boolean?
253
+ @type == :boolean
254
+ end
255
+
208
256
  private
209
257
 
210
258
  # Set value for config option without evaluating a block