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.
@@ -39,7 +39,9 @@ module Cliqr
39
39
  @basename = UNSET
40
40
  @description = UNSET
41
41
  @handler = UNSET
42
- @options = UNSET
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 [Funciton] block Function which populates configuration for a sub-attribute
64
+ # @param [Function] block Function which populates configuration for a sub-attribute
64
65
  #
65
- # @return [Object] If setting a attribute's value
66
- # @return [Cliqr::CLI::OptionConfig] If adding a new option
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
- @options = [] if @options == UNSET
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 = '' if @name == UNSET
138
- @short = '' if @short == UNSET
139
- @description = '' if @description == UNSET
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
- # Set value for command option's attribute
187
+ # Check if a option's name is defined
145
188
  #
146
- # @param [Symbol] name Name of the attribute
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
- # @param [Object] value Value for the attribute
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 [Object] Value that was set for the attribute
151
- def set_config(name, value)
152
- handle_option_config name, value
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
@@ -2,20 +2,27 @@
2
2
 
3
3
  require 'cliqr/error'
4
4
 
5
- require 'cliqr/cli/router'
6
- require 'cliqr/cli/command_runner_factory'
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 [Symbol] output Either :default or :buffer
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(output: :default)
39
- handler = @config.handler.new
40
- begin
41
- runner = CommandRunnerFactory.get(output: output)
42
- runner.run do
43
- handler.execute
44
- end
45
- rescue StandardError => e
46
- raise Cliqr::Error::CommandRuntimeException.new "command '#{@config.basename}' failed", e
47
- end
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