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.
@@ -0,0 +1,72 @@
1
+ # encoding: utf-8
2
+
3
+ module Cliqr
4
+ module CLI
5
+ module Parser
6
+ # Token handler for parsing a option and its value
7
+ #
8
+ # @api private
9
+ class OptionToken < Token
10
+ # Name of the option token
11
+ #
12
+ # @return [String]
13
+ attr_accessor :name
14
+
15
+ # Argument that was used to parse the option name from
16
+ #
17
+ # @return [String]
18
+ attr_accessor :arg
19
+
20
+ # Create a new option token. Initial state will be <tt>active</tt>
21
+ #
22
+ # @param [String] name Long name of the option
23
+ # @param [String] arg Value of the option
24
+ #
25
+ # @return [Cliqr::CLI::Parser::OptionToken] A new Token instance
26
+ def initialize(name, arg)
27
+ @name = name
28
+ @arg = arg
29
+
30
+ @value = nil
31
+ @active = true
32
+ @type = :option
33
+ end
34
+
35
+ # Check if the token handler is active and needs more arguments
36
+ #
37
+ # @return [Boolean] <tt>true</tt> if the token handler is active
38
+ def active?
39
+ @active
40
+ end
41
+
42
+ # Append the next argument in the series and set token to inactive
43
+ #
44
+ # @param [String] arg Argument value of the next command line parameter
45
+ #
46
+ # @return [Boolean] Active state of the token handler
47
+ def append(arg)
48
+ @value = arg
49
+ @active = false
50
+ end
51
+
52
+ # Get the token representation
53
+ #
54
+ # @return [Hash] A hash of the token parameters and their values
55
+ def build
56
+ {
57
+ :name => @name.to_s,
58
+ :value => @value
59
+ }
60
+ end
61
+
62
+ # Called if this token handler was still active once the argument list ends
63
+ #
64
+ # @return [Cliqr::CLI::Parser::OptionToken] Current instance object
65
+ def finalize
66
+ # should not be called
67
+ fail Cliqr::Error::OptionValueMissing, "a value must be defined for option \"#{@arg}\""
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,66 @@
1
+ # encoding: utf-8
2
+
3
+ module Cliqr
4
+ module CLI
5
+ module Parser
6
+ # Builder for collecting parsed command line arguments that can be used to
7
+ # build a command context
8
+ #
9
+ # @api private
10
+ class ParsedArgumentBuilder
11
+ # Initialize a new instance
12
+ #
13
+ # @param [Cliqr::CLI::Config] config Configuration settings for the command line interface
14
+ #
15
+ # @return [Cliqr::CLI::Parser::ParsedArgumentBuilder]
16
+ def initialize(config)
17
+ @config = config
18
+ @options = []
19
+ @option_names = Set.new
20
+ end
21
+
22
+ # Add a new parsed token from the list of arguments
23
+ #
24
+ # @param [Cliqr::CLI::Parser::Token] token A parsed token from command line arguments
25
+ #
26
+ # @return [Boolean] <tt>true</tt> if the token was added
27
+ def add_token(token)
28
+ case token.type
29
+ when :option
30
+ add_option_name(token)
31
+ @options.push(token.build)
32
+ else
33
+ return false
34
+ end
35
+ true
36
+ end
37
+
38
+ # Build the hash of parsed command line arguments
39
+ #
40
+ # @return [Hash] Parsed arguments
41
+ def build
42
+ {
43
+ :command => @config.basename,
44
+ :options => @options
45
+ }
46
+ end
47
+
48
+ private
49
+
50
+ # Add option's name to a list of already added options and fail if duplicate
51
+ #
52
+ # @param [Cliqr::CLI::Parser::Token] token A parsed token from command line arguments
53
+ #
54
+ # @return [Set<String>] Current list of option names
55
+ def add_option_name(token)
56
+ option_config = @config.option(token.name)
57
+ old_config = @option_names.add?(option_config.name)
58
+ fail Cliqr::Error::MultipleOptionValues,
59
+ "multiple values for option \"#{token.arg}\"" if old_config.nil?
60
+ @option_names.add(option_config.short) if option_config.short?
61
+ @option_names
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8
2
+
3
+ module Cliqr
4
+ module CLI
5
+ module Parser
6
+ # A NO-OP argument token
7
+ #
8
+ # @api private
9
+ class Token
10
+ # Type of the token handler (:option in this case)
11
+ #
12
+ # @return [Symbol]
13
+ attr_accessor :type
14
+
15
+ # Create a new NO OP token
16
+ #
17
+ # @return [Cliqr::CLI::Parser::Token]
18
+ def initialize
19
+ @type = :NO_OP
20
+ end
21
+
22
+ # This token is never active
23
+ #
24
+ # @return [Boolean] This will always return <tt>false</tt> in this case
25
+ def active?
26
+ false
27
+ end
28
+
29
+ # Called if this token was still active once the argument list ends
30
+ #
31
+ # @return [Cliqr::CLI::Parser::TokenHandler] Current instance object
32
+ def finalize
33
+ self
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+
3
+ require 'cliqr/cli/parser/token'
4
+ require 'cliqr/cli/parser/option_token'
5
+
6
+ module Cliqr
7
+ module CLI
8
+ module Parser
9
+ # A factory class to get a instance of {Cliqr::CLI::Parser::Token}
10
+ # based on the argument
11
+ #
12
+ # @api private
13
+ class TokenFactory
14
+ # Create a new token factory instance
15
+ #
16
+ # @param [Cliqr::CLI::Config] config Command line interface configuration
17
+ #
18
+ # @return [Cliqr::CLI::Parser::TokenFactory]
19
+ def initialize(config)
20
+ @config = config
21
+ end
22
+
23
+ # Get a new instance of {Cliqr::CLI::Parser::Token} based on the argument
24
+ #
25
+ # @param [String] arg The argument used to get a token instance (default nil)
26
+ #
27
+ # @return [Cliqr::CLI::Parser::Token]
28
+ def get_token(arg = nil)
29
+ if arg.nil?
30
+ Token.new
31
+ else
32
+ case arg
33
+ when /^--([a-zA-Z][a-zA-Z0-9\-_]*)$/, /^-([a-zA-Z])$/
34
+ option_config = get_option_config(Regexp.last_match(1), arg)
35
+ OptionToken.new(option_config.name, arg)
36
+ else
37
+ fail Cliqr::Error::InvalidArgumentError, "invalid command argument \"#{arg}\""
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ # Check if a option is defined with the requested name then return it
45
+ #
46
+ # @param [String] name Long name of the option
47
+ # @param [String] arg THe argument that was parsed to get the option name
48
+ #
49
+ # @return [Cliqr::CLI::OptionConfig] Requested option configuration
50
+ def get_option_config(name, arg)
51
+ fail Cliqr::Error::UnknownCommandOption,
52
+ "unknown option \"#{arg}\"" unless @config.option?(name)
53
+ @config.option(name)
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -1,11 +1,35 @@
1
1
  # encoding: utf-8
2
2
 
3
+ require 'cliqr/cli/command_runner_factory'
4
+
3
5
  module Cliqr
4
6
  module CLI
5
7
  # Used for routing the command to the appropriate command handler based on the interface config
6
8
  #
7
9
  # @api private
8
10
  class Router
11
+ # Create a new Router instance
12
+ #
13
+ # @param [Cliqr::CLI::Config] config Command line configuration
14
+ #
15
+ # @return [Cliqr::CLI::Router]
16
+ def initialize(config)
17
+ @config = config
18
+ end
19
+
20
+ # Handle a command invocation by routing to appropriate command handler
21
+ #
22
+ # @param [Cliqr::CLI::CommandContext] context Context in which to execute the command
23
+ # @param [Hash] options Hash of options to configure the [Cliqr::CLI::CommandRunner]
24
+ #
25
+ # @return [Integer] Exit code of the command execution
26
+ def handle(context, **options)
27
+ handler = @config.handler.new
28
+ runner = CommandRunnerFactory.get(options)
29
+ runner.run do
30
+ handler.execute(context)
31
+ end
32
+ end
9
33
  end
10
34
  end
11
35
  end
@@ -7,14 +7,37 @@ module Cliqr
7
7
  # @api private
8
8
  class CliqrError < StandardError
9
9
  # Set up the error to wrap another error's trace
10
- def initialize(error_message, e = nil)
11
- super e
10
+ #
11
+ # @param [String] error_message A short error description
12
+ # @param [Error] cause The cause of the error
13
+ def initialize(error_message, cause = nil)
14
+ super cause
15
+
16
+ @error_message = error_message
17
+ @cause = cause
12
18
 
13
19
  # Preserve the original exception's data if provided
14
- return unless e && e.is_a?(Exception)
20
+ set_backtrace cause.backtrace if cause?
21
+ end
22
+
23
+ # Build a error message based on the cause of the error
24
+ #
25
+ # @return [String] Error message including the cause of the error
26
+ def message
27
+ if cause?
28
+ "#{@error_message}\n\nCause: #{@cause.class} - #{@cause.message}"
29
+ else
30
+ @error_message
31
+ end
32
+ end
33
+
34
+ private
15
35
 
16
- set_backtrace e.backtrace
17
- message.prepend "#{error_message}\n\nCause:\n#{e.class}: "
36
+ # Check if there was a nested cause for this error
37
+ #
38
+ # @return [Boolean] <tt>true</tt> if there was a valid cause for this error
39
+ def cause?
40
+ @cause && @cause.is_a?(Exception)
18
41
  end
19
42
  end
20
43
 
@@ -38,5 +61,26 @@ module Cliqr
38
61
 
39
62
  # Raised if config's option array is nil
40
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
+ # Indicates to the user that the command line option is invalid
69
+ class UnknownCommandOption < CliqrError; end
70
+
71
+ # Raised to signal missing value for a option
72
+ class OptionValueMissing < CliqrError; end
73
+
74
+ # Indicates that a option has multiple values in the command line
75
+ class MultipleOptionValues < CliqrError; end
76
+
77
+ # Raised if two options are defined with same long or short name
78
+ class DuplicateOptions < CliqrError; end
79
+
80
+ # Raised if an option is not defined properly
81
+ class InvalidOptionDefinition < CliqrError; end
82
+
83
+ # Raised if an option is not defined properly
84
+ class InvalidArgumentError < CliqrError; end
41
85
  end
42
86
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  # Versioned gem
4
4
  module Cliqr
5
- VERSION = '0.0.4'
5
+ VERSION = '0.1.0'
6
6
  end
@@ -2,23 +2,33 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
- describe Cliqr::CLI::Validator do
5
+ require 'cliqr/cli/config_validator'
6
+
7
+ require 'fixtures/test_command'
8
+
9
+ describe Cliqr::CLI::ConfigValidator do
6
10
  it 'does not allow empty config' do
7
- expect { Cliqr::CLI::Builder.new(nil).build }.to raise_error(Cliqr::Error::ConfigNotFound)
11
+ expect { Cliqr::CLI::Interface.build(nil) }.to(
12
+ raise_error(Cliqr::Error::ConfigNotFound, 'a valid config should be defined')
13
+ )
8
14
  end
9
15
 
10
16
  it 'does not allow empty basename' do
11
17
  config = Cliqr::CLI::Config.new
12
18
  config.basename = ''
13
19
  config.finalize
14
- expect { Cliqr::CLI::Builder.new(config).build }.to raise_error(Cliqr::Error::BasenameNotDefined)
20
+ expect { Cliqr::CLI::Interface.build(config) }.to(
21
+ raise_error(Cliqr::Error::BasenameNotDefined, 'basename not defined')
22
+ )
15
23
  end
16
24
 
17
25
  it 'does not allow command handler to be null' do
18
26
  config = Cliqr::CLI::Config.new
19
27
  config.basename = 'my-command'
20
28
  config.finalize
21
- expect { Cliqr::CLI::Builder.new(config).build }.to raise_error(Cliqr::Error::HandlerNotDefined)
29
+ expect { Cliqr::CLI::Interface.build(config) }.to(
30
+ raise_error(Cliqr::Error::HandlerNotDefined, 'handler not defined for command "my-command"')
31
+ )
22
32
  end
23
33
 
24
34
  it 'only accepts command handler that extend from Cliqr::CLI::Command' do
@@ -26,7 +36,10 @@ describe Cliqr::CLI::Validator do
26
36
  config.basename = 'my-command'
27
37
  config.handler = Object
28
38
  config.finalize
29
- expect { Cliqr::CLI::Builder.new(config).build }.to raise_error(Cliqr::Error::InvalidCommandHandler)
39
+ expect { Cliqr::CLI::Interface.build(config) }.to(
40
+ raise_error(Cliqr::Error::InvalidCommandHandler,
41
+ 'handler for command "my-command" should extend from [Cliqr::CLI::Command]')
42
+ )
30
43
  end
31
44
 
32
45
  it 'expects that config options should not be nil' do
@@ -35,6 +48,8 @@ describe Cliqr::CLI::Validator do
35
48
  config.handler = TestCommand
36
49
  config.options = nil
37
50
  config.finalize
38
- expect { Cliqr::CLI::Builder.new(config).build }.to raise_error(Cliqr::Error::OptionsNotDefinedException)
51
+ expect { Cliqr::CLI::Interface.build(config) }.to(
52
+ raise_error(Cliqr::Error::OptionsNotDefinedException, 'option array is nil for command "my-command"')
53
+ )
39
54
  end
40
55
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
- require 'executor/fixtures/test_command'
5
+ require 'fixtures/test_command'
6
6
 
7
7
  describe Cliqr::CLI::Interface do
8
8
  it 'builds a base command with name' do
@@ -78,4 +78,140 @@ Available options:
78
78
  --option-1
79
79
  EOS
80
80
  end
81
+
82
+ it 'does not allow multiple options with same long name' do
83
+ expect do
84
+ Cliqr.interface do
85
+ basename 'my-command'
86
+ description 'a command used to test cliqr'
87
+ handler TestCommand
88
+
89
+ option 'option-1' do
90
+ short 'p'
91
+ end
92
+
93
+ option 'option-1' do
94
+ short 't'
95
+ end
96
+ end
97
+ end.to(raise_error(Cliqr::Error::DuplicateOptions, 'multiple options with long name "option-1"'))
98
+ end
99
+
100
+ it 'does not allow multiple options with same short name' do
101
+ expect do
102
+ Cliqr.interface do
103
+ basename 'my-command'
104
+ description 'a command used to test cliqr'
105
+ handler TestCommand
106
+
107
+ option 'option-1' do
108
+ short 'p'
109
+ end
110
+
111
+ option 'option-1' do
112
+ short 't'
113
+ end
114
+ end
115
+ end.to(raise_error(Cliqr::Error::DuplicateOptions, 'multiple options with long name "option-1"'))
116
+ end
117
+
118
+ it 'does not allow multiple options with same short name' do
119
+ expect do
120
+ Cliqr.interface do
121
+ basename 'my-command'
122
+ description 'a command used to test cliqr'
123
+ handler TestCommand
124
+
125
+ option 'option-1' do
126
+ short 'p'
127
+ end
128
+
129
+ option 'option-2' do
130
+ short 'p'
131
+ end
132
+ end
133
+ end.to(raise_error(Cliqr::Error::DuplicateOptions, 'multiple options with short name "p"'))
134
+ end
135
+
136
+ it 'does not allow option with empty long name' do
137
+ expect do
138
+ Cliqr.interface do
139
+ basename 'my-command'
140
+ description 'a command used to test cliqr'
141
+ handler TestCommand
142
+
143
+ option '' do
144
+ short 'p'
145
+ end
146
+ end
147
+ end.to(raise_error(Cliqr::Error::InvalidOptionDefinition, 'option number 1 does not have a name field'))
148
+ end
149
+
150
+ it 'does not allow option with empty short name' do
151
+ expect do
152
+ Cliqr.interface do
153
+ basename 'my-command'
154
+ description 'a command used to test cliqr'
155
+ handler TestCommand
156
+
157
+ option 'option-1' do
158
+ short ''
159
+ end
160
+ end
161
+ end.to(raise_error(Cliqr::Error::InvalidOptionDefinition, "option \"option-1\" has empty short name"))
162
+ end
163
+
164
+ it 'does not allow option with nil long name' do
165
+ expect do
166
+ Cliqr.interface do
167
+ basename 'my-command'
168
+ description 'a command used to test cliqr'
169
+ handler TestCommand
170
+
171
+ option nil
172
+ end
173
+ end.to(raise_error(Cliqr::Error::InvalidOptionDefinition, 'option number 1 does not have a name field'))
174
+ end
175
+
176
+ it 'does not allow option with nil long name for second option' do
177
+ expect do
178
+ Cliqr.interface do
179
+ basename 'my-command'
180
+ description 'a command used to test cliqr'
181
+ handler TestCommand
182
+
183
+ option 'option-1'
184
+ option ''
185
+ end
186
+ end.to(raise_error(Cliqr::Error::InvalidOptionDefinition, 'option number 2 does not have a name field'))
187
+ end
188
+
189
+ it 'does not allow multiple characters in short name' do
190
+ expect do
191
+ Cliqr.interface do
192
+ basename 'my-command'
193
+ description 'a command used to test cliqr'
194
+ handler TestCommand
195
+
196
+ option 'option-1' do
197
+ short 'p1'
198
+ end
199
+ end
200
+ end.to(raise_error(Cliqr::Error::InvalidOptionDefinition,
201
+ 'short option name can not have more than one characters in "option-1"'))
202
+ end
203
+
204
+ it 'has options if added during build phase' do
205
+ cli = Cliqr.interface do
206
+ basename 'my-command'
207
+ description 'a command used to test cliqr'
208
+ handler TestCommand
209
+
210
+ option 'option-1' do
211
+ short 'p'
212
+ description 'a nice option to have'
213
+ end
214
+ end
215
+ expect(cli.config.options?).to be_truthy
216
+ end
81
217
  end