consoler 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 54af0ed85f838038f475a6933ccc51cc3cc5f3d6
4
- data.tar.gz: c0456cdb8e72feef9b54fa726a532900ae5e0778
3
+ metadata.gz: 317d28b9a15f0ea07c01410191cac9d0d59a41dd
4
+ data.tar.gz: f9a363ba2b596a02df673c1600441f2f6d7b7010
5
5
  SHA512:
6
- metadata.gz: 6c3df9e819bad404d84afd1a9b99263e2ecee61a71a8db9af4507648e495021fcfe45d9338351b1ebe6e3432706be8bd4acb1c57bc0b26a922af5b3c433ddbb1
7
- data.tar.gz: 7ebde44d27aaede0fa7a6a1a599a81fe7261808ed1472c068732b550c1e5ce3673199cef9a3733ecee6511ba5f11f05dbc70a046c5f8f2e2de3d48f46b8b796d
6
+ metadata.gz: b42c0a91ed2df03ef7d76796f1da5e986782d2d0415a14167159f34b10b76eb939e97f378109328bdd768c7f234607907871bcd03e2e5ad097180c7edaf385c6
7
+ data.tar.gz: df6627a850424d6053c4525bea35014c1c8ec42bbe9cbe43f981119b535a747af53c4130dec92242309d36891c9e7368450c49838c37c55ea2662a682582fb26
data/.yardopts ADDED
@@ -0,0 +1,2 @@
1
+ --private
2
+ --protected
data/README.md CHANGED
@@ -1,27 +1,47 @@
1
- # Consoler [![Build Status](https://api.travis-ci.org/justim/consoler-rb.svg?branch=master)](http://travis-ci.org/justim/consoler-rb)
1
+ # Consoler [![Build Status](https://api.travis-ci.org/justim/consoler-rb.svg?branch=master)](https://travis-ci.org/justim/consoler-rb) [![Gem Version](https://badge.fury.io/rb/consoler.svg)](https://badge.fury.io/rb/consoler)
2
2
 
3
3
  > Sinatra-like application builder for the console
4
4
 
5
- ## Usage
5
+ ## Quick usage
6
6
 
7
7
  ```ruby
8
- # create a application
8
+ # create an application
9
9
  app = Consoler::Application.new description: 'A simple app'
10
10
 
11
11
  # define a command
12
- app.build 'target [--clean]' do |target, clean|
12
+ app.build '[--clean] output_dir' do |clean, output_dir|
13
13
  # clean contains a boolean
14
14
  clean_up if clean
15
15
 
16
- # target contains a string
17
- build_project target
16
+ # output_dir contains a string
17
+ build_project output_dir
18
18
  end
19
19
  app.run(['build', 'production', '--clean'])
20
20
 
21
21
  # this does not match, nothing is executed and the usage message is printed
22
22
  app.run(['deploy', 'production'])
23
+
24
+ # defaults to ARGV
25
+ app.run
23
26
  ```
24
27
 
28
+ ## Features
29
+
30
+ - No fiddling with `ARGV` and friends
31
+ - Arguments filled based on their name, for easy access
32
+ - Grouped optionals
33
+
34
+ ## Requirements
35
+
36
+ Tests are run against multiple ruby versions (latest supported):
37
+
38
+ - `2.2.x`
39
+ - `2.3.x`
40
+ - `2.4.x`
41
+ - `2.5.x`
42
+
43
+ No other requirements exist.
44
+
25
45
  ## Installation
26
46
 
27
47
  ```sh
@@ -31,13 +51,233 @@ gem install consoler
31
51
  or add to your `Gemfile` for applications
32
52
 
33
53
  ```ruby
34
- gem 'consoler', '~> 1.0.0'
54
+ gem 'consoler', '~> 1.0.3'
35
55
  ```
36
56
 
37
57
  or to your `.gemspec` file for gems
38
58
 
39
59
  ```ruby
40
- Gem::Specification.new do |s|
41
- s.add_dependency 'consoler', '~> 1.0.0'
60
+ Gem::Specification.new do |spec|
61
+ spec.add_dependency 'consoler', '~> 1.0.3'
62
+ end
63
+ ```
64
+
65
+ ## Docs
66
+
67
+ Full API documentation can the found here: http://www.rubydoc.info/github/justim/consoler-rb/
68
+
69
+ ### API
70
+
71
+ #### Creating an application
72
+
73
+ ```ruby
74
+ # create an application
75
+ app = Consoler::Application.new
76
+
77
+ # .. or with a description that will show up in the usage message
78
+ app = Consoler::Application.new description: 'A simple app'
79
+ ```
80
+
81
+ #### Adding commands
82
+
83
+ All actions you want to create must have a command name, there is no top-level matching available at this point.
84
+
85
+ ```ruby
86
+ # create an application
87
+ app = Consoler::Application.new
88
+
89
+ # in the most simple way, you provide a name (the method name) and a block you
90
+ # want to execute if it matches
91
+ app.build do
92
+ # your code here
93
+ end
94
+
95
+ # to add options to your command, provide a options definition as an argument
96
+ app.build '[--clean] [--env=] [-v] output_dir' do |clean, env, v, output_dir|
97
+ # parameters are matched based on name
98
+ # `clean` contains a boolean
99
+ # `env` contains a string or nil if not provided
100
+ # `v` contains a number, counting the times it occurred in the arguments
101
+ # `output_dir` contains a string, if needed to match this command
42
102
  end
43
103
  ```
104
+
105
+ #### Running the application
106
+
107
+ ```ruby
108
+ # filename: app.rb
109
+
110
+ # create an application
111
+ app = Consoler::Application.new
112
+
113
+ # a build command
114
+ app.build '[--clean] [--env=] [-v] output_dir' do |clean, env, v, output_dir|
115
+ puts 'Starting build...' if v > 0
116
+
117
+ do_clean_up if clean
118
+
119
+ do_build env || 'development', output_dir
120
+
121
+ puts 'Build complete' if v > 0
122
+ end
123
+
124
+ # a deploy command
125
+ app.build '[-v] [--env=]' do |env|
126
+ puts 'Starting deploy...' if v > 0
127
+
128
+ do_deploy env || 'development'
129
+
130
+ puts 'Deploy complete' if v > 0
131
+ end
132
+ ```
133
+
134
+ _Shell commands:_
135
+ ```sh
136
+ # start a build
137
+ ruby app.rb build --env production dist
138
+
139
+ # deploy
140
+ ruby app.rb deploy
141
+ ```
142
+
143
+ #### Options definition
144
+
145
+ | Option | Meaning | Example |
146
+ | ------------ | ------------------------------- | -------------------------------------- |
147
+ | `-f` | Short option with name `f` | `ruby app.rb build -f` |
148
+ | `--clean` | Long option with name `clean` | `ruby app.rb build --clean` |
149
+ | `output_dir` | Argument with name `output_dir` | `ruby app.rb build dist/` |
150
+ | `--env=` | Long option with value | `ruby app.rb build --env production` |
151
+ | `-e=` | Short option with value | `ruby app.rb build -e production` |
152
+ | `[ .. ]` | Optional options/arguments | `ruby app.rb build` would match `[-v]` |
153
+
154
+ Optional-tokens can occur anywhere in the options, as long as they are not nested and properly closed. You can group optional parts, meaning that both options/arguments should be available or both not.
155
+
156
+ Options and/or arguments are mandatory unless specified otherwise.
157
+
158
+ Grouping of optionals allows you do things like this:
159
+
160
+ ```ruby
161
+ app = Consoler::Application.new
162
+ app.shout '[first_name last_name] [name]' do |first_name, last_name, name|
163
+ # by definition, `last_name` is also filled
164
+ unless first_name.nil? then
165
+ puts "Hello #{first_name} #{last_name}!"
166
+ end
167
+
168
+ unless name.nil? then
169
+ puts "Hello #{name}!"
170
+ end
171
+ end
172
+
173
+ # calling with two arguments can fill the first group
174
+ # prints "Hello John Doe!"
175
+ app.run(['shout', 'John', 'Doe'])
176
+
177
+ # calling with one argument it is not possible to fill the first group
178
+ # prints "Hello Mr. White!"
179
+ app.run(['shout', 'Mr. White!'])
180
+ ```
181
+
182
+ #### Return types in action block
183
+
184
+ | Option type | Return type (ex.) | Default (if optional) |
185
+ | ----------- | ----------------------- | --------------------- |
186
+ | Short | `Integer` (`1`) | `0` |
187
+ | Long | `Boolean` (`true`) | `false` |
188
+ | Value | `String` (`production`) | `nil` |
189
+ | Argument | `String` (`dist/`) | `nil` |
190
+
191
+ #### Subapplications
192
+
193
+ To make application nesting possible you can provide a complete application to a command, instead of an action block.
194
+
195
+ ```ruby
196
+ # filename: app.rb
197
+
198
+ # create a subapplication
199
+ android = Consoler::Application.new
200
+ android.build do; end
201
+
202
+ # options are supported just like regular apps
203
+ android.clean '--force' do; end
204
+
205
+ # create an application
206
+ app = Consoler::Application.new
207
+
208
+ # mount the android application on top of the android command
209
+ # note that the command does not support options
210
+ app.android android
211
+ ```
212
+
213
+ You can now run the next bit to run the android build command:
214
+
215
+ ```sh
216
+ ruby app.rb android build
217
+ ```
218
+
219
+ You can build them as complicated as you like :)
220
+
221
+ ### Complete example
222
+
223
+ ```ruby
224
+ #!/usr/bin/env ruby
225
+
226
+ db = Consoler::Application.new
227
+ db.migrate '-- run all pending migrations' do
228
+ run_migrate
229
+ end
230
+
231
+ db.rollback '[--migrations=] -- rollback a number of migrations' do |migrations|
232
+ run_rollback migrations || 1
233
+ end
234
+
235
+ cache = Consoler::Application.new
236
+ cache.clear '[--env=] -- clear the cache for a given environment' do |env|
237
+ run_cache_clear env || 'development'
238
+ end
239
+
240
+ cache.warmup '[--env=] -- warmup the cache for a given environment' do |env|
241
+ run_cache_warmup env || 'development'
242
+ end
243
+
244
+ app = Consoler::Application.new
245
+ app.db db
246
+ app.cache cache
247
+
248
+ app.build '[--clean] [--env=] [-v] output_dir -- build the project' do |clean, env, v, output_dir|
249
+ puts 'Starting build...' if v > 0
250
+
251
+ if clean
252
+ puts 'Starting clean...' if v > 1
253
+ do_clean_up if clean
254
+ end
255
+
256
+ do_build env || 'development', output_dir
257
+
258
+ puts 'Build complete' if v > 0
259
+ end
260
+ ```
261
+
262
+ Make the file executable with `chmod a+x app.rb`, you can now call it with `./app.rb` without `ruby` in front of it, saves a couple keystrokes.
263
+
264
+ ```sh
265
+ # run all migration
266
+ ./app.rb db migrate
267
+ # rollback the last 4 migrations
268
+ ./app.rb db rollback 4
269
+ # clean production cache
270
+ ./app.rb cache clear --env production
271
+ # build the project, including cleaning and logging
272
+ ./app.rb build --clean --env production -vv dist/
273
+ # print the usage message
274
+ ./app.rb
275
+ ```
276
+
277
+ ### Current wish-list
278
+
279
+ - Aliases, mostly to "document" short options, `-v|--verbose`
280
+
281
+ ## Final notes
282
+
283
+ If you like what you see, feel free to use it anyway you like. Also, don't hold back if you have suggestions/question and create an issue to let me know, and we can talk about it. And if you don't like what you see, PRs are welcome. You should probably file an issue first to make sure it's something worth doing, and the right way to do it.
@@ -98,12 +98,18 @@ module Consoler
98
98
 
99
99
  protected
100
100
 
101
+ # Run the app
102
+ #
103
+ # @param [Array<String>] args Arguments
104
+ # @return [(mixed, Boolean)] Result of the command, and, did the args match a command at all
101
105
  def _run(args)
102
106
  arg = args.shift
103
107
  arguments = Consoler::Arguments.new args
104
108
 
105
109
  @commands.each do |command|
106
110
  if command.command == arg then
111
+ # the matched command contains a subapp, run subapp with the same
112
+ # arguments (excluding the arg that matched this command)
107
113
  if command.action.instance_of? Consoler::Application then
108
114
  result, matched = command.action._run(args)
109
115
 
@@ -123,8 +129,13 @@ module Consoler
123
129
  return nil, false
124
130
  end
125
131
 
132
+ # Print the usage message for this command
133
+ #
134
+ # @param [String] prefix A prefix for the command from a parent app
135
+ # @return [Consoler::Application]
126
136
  def _commands_usage(prefix='')
127
137
  @commands.each do |command|
138
+ # print the usage message of a subapp with a prefix from the current command
128
139
  if command.action.instance_of? Consoler::Application then
129
140
  command.action._commands_usage "#{prefix} #{command.command}"
130
141
  else
@@ -141,24 +152,67 @@ module Consoler
141
152
  print "\n"
142
153
  end
143
154
  end
155
+
156
+ self
144
157
  end
145
158
 
146
159
  private
147
160
 
161
+ # Add a command
162
+ #
163
+ # @param [String] command Command name
164
+ # @param [String] options_def Definition of options
165
+ # @param [Proc, Consoler::Application] action Action or subapp
166
+ # @return [Consoler::Application]
148
167
  def _add_command(command, options_def, action)
149
168
  @commands.push(Consoler::Command.new(
150
169
  command: command,
151
170
  options: Consoler::Options.new(options_def),
152
171
  action: action,
153
172
  ))
173
+
174
+ self
154
175
  end
155
176
 
177
+ # Execute an action with argument match info
178
+ #
179
+ # @param [Proc] action Action
180
+ # @param [Hash] match Argument match information
156
181
  def _dispatch(action, match)
182
+ # match parameter names to indices of match information
157
183
  arguments = action.parameters.map do |parameter|
158
- match[parameter[1].to_s]
184
+ parameter_name = parameter[1].to_s
185
+
186
+ if match.has_key? parameter_name then
187
+ match[parameter_name]
188
+ else
189
+ # check for the normalized name of every match to see
190
+ # if it fits the parameter name
191
+ match.each do |name, value|
192
+ normalized_name = _normalize name
193
+
194
+ if parameter_name == normalized_name then
195
+ return value
196
+ end
197
+ end
198
+ end
159
199
  end
160
200
 
161
201
  action.call(*arguments)
162
202
  end
203
+
204
+ # Normalize a name to be used as a variable name
205
+ #
206
+ # @param [String] name Name
207
+ # @return [String] Normalized name
208
+ def _normalize(name)
209
+ # maybe do something more, maybe not.. ruby does allow for
210
+ # some weird stuff to be used as a variable name. the user
211
+ # should use some common sense. and, other things might
212
+ # also be an syntax error, like starting with a number.
213
+ # this normalization is more of a comvenience than anything
214
+ # else
215
+ name.gsub('-', '_')
216
+ end
163
217
  end
164
218
  end
@@ -26,12 +26,14 @@ module Consoler
26
26
  def match
27
27
  parse_options = true
28
28
 
29
- loop_args do |arg|
29
+ _loop_args do |arg|
30
30
  unless parse_options then
31
31
  @argument_values.push arg
32
32
  next
33
33
  end
34
34
 
35
+ # when "argument" is --, then stop parsing the rest of the arguments
36
+ # and treat the rest as regular arguments
35
37
  if arg == '--' then
36
38
  parse_options = false
37
39
  next
@@ -57,6 +59,10 @@ module Consoler
57
59
 
58
60
  private
59
61
 
62
+ # Analyze a single argument
63
+ #
64
+ # @param [String] arg Single argument
65
+ # @return [true, nil] true on success, nil on failure
60
66
  def _analyze(arg)
61
67
  is_long = false
62
68
  is_short = false
@@ -70,12 +76,14 @@ module Consoler
70
76
  name = arg[1..-1]
71
77
  end
72
78
 
73
- if name.nil?
79
+ # arg is not a long/short option, add to arguments values
80
+ unless is_long or is_short then
74
81
  @argument_values.push arg
75
82
  return true
76
83
  end
77
84
 
78
85
  unless name.nil? then
86
+ # get the name of the option, short options use the first character
79
87
  option_name = if is_short then
80
88
  name[0]
81
89
  else
@@ -84,11 +92,13 @@ module Consoler
84
92
 
85
93
  option = @options.get option_name
86
94
 
95
+ # no option by this name in options
87
96
  return nil if option.nil?
88
97
 
89
98
  needs_short = option.is_short
90
99
  needs_long = option.is_long
91
100
 
101
+ # see if the type if right, short or long
92
102
  if needs_long and not is_long then
93
103
  return nil
94
104
  elsif needs_short and not is_short then
@@ -97,9 +107,10 @@ module Consoler
97
107
 
98
108
  if is_long then
99
109
  if option.is_value then
100
- return nil if peek_next.nil?
101
- @matched_options[name] = peek_next
102
- skip
110
+ # is_value needs a next argument for its value
111
+ return nil if _peek_next.nil?
112
+ @matched_options[name] = _peek_next
113
+ _skip_next
103
114
  else
104
115
  @matched_options[name] = true
105
116
  end
@@ -107,11 +118,16 @@ module Consoler
107
118
 
108
119
  if is_short then
109
120
  if name.size == 1 and option.is_value then
110
- return nil if peek_next.nil?
111
- @matched_options[name] = peek_next
112
- skip
121
+ # is_value needs a next argument for its value
122
+ return nil if _peek_next.nil?
123
+ @matched_options[name] = _peek_next
124
+ _skip_next
113
125
  else
126
+ # for every character (short option) increment the option value
114
127
  name.split('').each do |n|
128
+ short_option = @options.get n
129
+ return nil if short_option.nil?
130
+
115
131
  if @matched_options[n].nil? then
116
132
  @matched_options[n] = 0
117
133
  end
@@ -125,29 +141,49 @@ module Consoler
125
141
  return true
126
142
  end
127
143
 
128
- def current
129
- @arguments.args[@index]
130
- end
131
-
132
- def peek_next
133
- @arguments.args[@index + 1]
134
- end
135
-
136
- def loop_args
144
+ # Loop through the arguments
145
+ #
146
+ # @yield [String] An argument
147
+ # @return [Consoler::Matcher]
148
+ def _loop_args
137
149
  @index = 0
138
150
  size = @arguments.args.size
139
151
 
152
+ # use an incrementing index, to be able to peek to the next in the list
153
+ # and to skip an item
140
154
  while @index < size do
141
- yield current
155
+ yield @arguments.args[@index]
142
156
 
143
- skip
157
+ _skip_next
144
158
  end
159
+
160
+ self
161
+ end
162
+
163
+ # Peek at the next argument
164
+ #
165
+ # Only useful inside {Consoler::Matcher#_loop_args}
166
+ #
167
+ # @return [String, nil]
168
+ def _peek_next
169
+ @arguments.args[@index + 1]
145
170
  end
146
171
 
147
- def skip
172
+ # Skip to the next argument
173
+ #
174
+ # Useful if you use a peeked argument
175
+ #
176
+ # @return [nil]
177
+ # @return [Consoler::Matcher]
178
+ def _skip_next
148
179
  @index += 1
180
+
181
+ self
149
182
  end
150
183
 
184
+ # Match arguments to defined option arguments
185
+ #
186
+ # @return [Array<String>] The remaining args
151
187
  def _match_arguments
152
188
  @optionals_before = {}
153
189
  @optionals_before_has_remaining = false
@@ -157,6 +193,8 @@ module Consoler
157
193
  _match_arguments_optionals_before
158
194
 
159
195
  @optionals_before.each do |mandatory_arg_name, optionals|
196
+ # fill the optional argument option with a value if there are enough
197
+ # arguments supplied (info available from optionals map)
160
198
  optionals.each do |_, optional|
161
199
  optional.each do |before|
162
200
  if before[:included] then
@@ -166,6 +204,7 @@ module Consoler
166
204
  end
167
205
  end
168
206
 
207
+ # only fill mandatory argument if its not the :REMAINING key
169
208
  if mandatory_arg_name != :REMAINING then
170
209
  @matched_options[mandatory_arg_name] = @argument_values[argument_values_index]
171
210
  argument_values_index += 1
@@ -174,6 +213,7 @@ module Consoler
174
213
 
175
214
  remaining = []
176
215
 
216
+ # left over arguments
177
217
  while argument_values_index < @argument_values.size do
178
218
  remaining.push @argument_values[argument_values_index]
179
219
  argument_values_index += 1
@@ -182,6 +222,9 @@ module Consoler
182
222
  remaining
183
223
  end
184
224
 
225
+ # Create a map of all optionals and before which mandatory argument they appear
226
+ #
227
+ # @return [Consoler::Matcher]
185
228
  def _match_arguments_optionals_before
186
229
  @optionals_before = {}
187
230
  tracker = {}
@@ -190,8 +233,10 @@ module Consoler
190
233
  next unless option.is_argument
191
234
 
192
235
  if option.is_optional then
236
+ # setup tracker for optional group
193
237
  tracker[option.is_optional] = [] if tracker[option.is_optional].nil?
194
238
 
239
+ # mark all optionals as not-included
195
240
  tracker[option.is_optional].push({
196
241
  included: false,
197
242
  name: option.name,
@@ -202,24 +247,35 @@ module Consoler
202
247
  end
203
248
  end
204
249
 
250
+ # make sure all optionals are accounted for in the map
205
251
  if tracker != {} then
252
+ # use a special key so we can handle it differently in the filling process
206
253
  @optionals_before[:REMAINING] = tracker
207
254
  @optionals_before_has_remaining = true
208
255
  end
209
256
 
210
- _match_arguments_optoins_before_matcher
257
+ _match_arguments_options_before_matcher
258
+
259
+ self
211
260
  end
212
261
 
213
- def _match_arguments_optoins_before_matcher
262
+ # Match remaining args against the optionals map
263
+ #
264
+ # @return [Consoler::Matcher]
265
+ def _match_arguments_options_before_matcher
266
+ # number of arguments that are needed to fill our mandatory argument options
214
267
  mandatories_matched = @optionals_before.size
215
268
 
269
+ # there are optionals at the end of the options, don't match the void
216
270
  if @optionals_before_has_remaining then
217
271
  mandatories_matched -= 1
218
272
  end
219
273
 
220
274
  total = 0
221
275
 
276
+ # loop through optional map
222
277
  _each_optional_before_sorted do |before|
278
+ # are there enough arguments left to fill this optional group
223
279
  if (total + before.size + mandatories_matched) <= @argument_values.size then
224
280
  total += before.size
225
281
 
@@ -228,8 +284,13 @@ module Consoler
228
284
  end
229
285
  end
230
286
  end
287
+
288
+ self
231
289
  end
232
290
 
291
+ # Give all unmatched optional options there default value
292
+ #
293
+ # @return [Consoler::Matcher]
233
294
  def _fill_defaults
234
295
  @options.each do |option|
235
296
  if option.is_optional then
@@ -238,8 +299,15 @@ module Consoler
238
299
  end
239
300
  end
240
301
  end
302
+
303
+ self
241
304
  end
242
305
 
306
+ # Loop through the optionals before map
307
+ #
308
+ # Sorted by number of optionals in a group
309
+ #
310
+ # @return [Consoler::Matcher]
243
311
  def _each_optional_before_sorted
244
312
  @optionals_before.each do |_, optionals|
245
313
  tmp = []
@@ -254,6 +322,8 @@ module Consoler
254
322
  yield optionals[item[:index]]
255
323
  end
256
324
  end
325
+
326
+ self
257
327
  end
258
328
  end
259
329
  end
@@ -27,10 +27,13 @@ module Consoler
27
27
  def self.create(option_def, tracker)
28
28
  option = Option.new option_def, tracker
29
29
 
30
+ # split short options with more than 1 char in multiple options
30
31
  if option.is_short and option.name.size > 1 then
32
+ # remember state
31
33
  old_tracking = tracker.is_tracking
32
34
  old_is_value = option.is_value
33
35
 
36
+ # if the complete option is optional, fake the tracker
34
37
  if option.is_optional then
35
38
  tracker.is_tracking = true
36
39
  end
@@ -40,6 +43,7 @@ module Consoler
40
43
  names.each_with_index do |name, i|
41
44
  new_name = "-#{name}"
42
45
 
46
+ # if the short option should have a value, this only counts for the last option
43
47
  if old_is_value and i == names.count - 1 then
44
48
  new_name = "#{new_name}="
45
49
  end
@@ -47,6 +51,7 @@ module Consoler
47
51
  yield Option.new new_name, tracker
48
52
  end
49
53
 
54
+ # reset to saved state
50
55
  tracker.is_tracking = old_tracking
51
56
  else
52
57
  yield option
@@ -92,7 +97,12 @@ module Consoler
92
97
  #
93
98
  # @param [String] option_def Definition of the option
94
99
  # @param [Consoler::Tracker] tracker tracker
100
+ # @raise [RuntimeError] if the option name is empty
101
+ # @raise [RuntimeError] if the option is long _and_ short
95
102
  def initialize(option_def, tracker)
103
+ # Check for multiple attributes in the option definition till we got the
104
+ # final name and all of its attributes
105
+
96
106
  option, @is_optional = _is_optional option_def, tracker
97
107
  option, @is_long = _is_long option
98
108
  option, @is_short = _is_short option
@@ -112,9 +122,22 @@ module Consoler
112
122
 
113
123
  private
114
124
 
125
+ # Check optional definition
126
+ #
127
+ # Does it open an optional group
128
+ # Does it close an optional group (can be both)
129
+ # Updates the tracker
130
+ # Removes leading [ and trailing ]
131
+ #
132
+ # @param [String] option Option definition
133
+ # @param [Consoler::Tracker] tracker Optional tracker
134
+ # @raise [RuntimeError] if you try to nest optional groups
135
+ # @raise [RuntimeError] if you try to close an unopened optional
136
+ # @return [(String, Integer|nil)] Remaining option definition, and, optional group if available
115
137
  def _is_optional(option, tracker)
116
138
  if option[0] == '[' then
117
139
  if !tracker.is_tracking then
140
+ # mark tracker as tracking
118
141
  tracker.is_tracking = true
119
142
  tracker.index += 1
120
143
  option = option[1..-1]
@@ -123,6 +146,7 @@ module Consoler
123
146
  end
124
147
  end
125
148
 
149
+ # get optional group index from tracking, if tracking
126
150
  optional = if tracker.is_tracking then
127
151
  tracker.index
128
152
  else
@@ -131,6 +155,7 @@ module Consoler
131
155
 
132
156
  if option[-1] == ']' then
133
157
  if tracker.is_tracking then
158
+ # mark tracker as non-tracking
134
159
  tracker.is_tracking = false
135
160
  option = option[0..-2]
136
161
  else
@@ -141,6 +166,10 @@ module Consoler
141
166
  return option, optional
142
167
  end
143
168
 
169
+ # Check long definition
170
+ #
171
+ # @param [String] option Option definition
172
+ # @return [(String, Boolean)]
144
173
  def _is_long(option)
145
174
  if option[0..1] == '--' then
146
175
  long = true
@@ -152,6 +181,10 @@ module Consoler
152
181
  return option, long
153
182
  end
154
183
 
184
+ # Check short definition
185
+ #
186
+ # @param [String] option Option definition
187
+ # @return [(String, Boolean)]
155
188
  def _is_short(option)
156
189
  if option[0] == '-' then
157
190
  short = true
@@ -163,6 +196,11 @@ module Consoler
163
196
  return option, short
164
197
  end
165
198
 
199
+ # Check value definition
200
+ #
201
+ # @param [String] option Option definition
202
+ # @raise [RuntimeError] if you try to assign a value to an argument
203
+ # @return [(String, Boolean)]
166
204
  def _value(option, argument)
167
205
  if option[-1] == '=' then
168
206
  if argument then
@@ -13,12 +13,14 @@ module Consoler
13
13
  # Create a list of option based on a string definition
14
14
  #
15
15
  # @param options_def [String] A string definition of the desired options
16
+ # @raise [RuntimeError] if you try to use a duplicate name
16
17
  def initialize(options_def)
17
18
  @options = []
18
19
  @description = nil
19
20
 
20
21
  return if options_def.nil?
21
22
 
23
+ # strip the description
22
24
  if match = /(^|\s+)-- (?<description>.*)$/.match(options_def) then
23
25
  @description = match[:description]
24
26
  options_def = options_def[0...-match[0].size]
@@ -44,7 +46,7 @@ module Consoler
44
46
  # @param name [String] Name of the option
45
47
  # @return [Consoler::Option, nil]
46
48
  def get(name)
47
- each do |option|
49
+ each do |option, _|
48
50
  if option.name == name then
49
51
  return option
50
52
  end
@@ -72,6 +74,9 @@ module Consoler
72
74
  @options.size
73
75
  end
74
76
 
77
+ # Get the definition for these options
78
+ #
79
+ # @return [String] Options definition
75
80
  def to_definition
76
81
  definition = ''
77
82
  optional = nil
@@ -87,6 +92,7 @@ module Consoler
87
92
  definition += option.to_definition
88
93
 
89
94
  if option.is_optional then
95
+ # only close when the next option is not optional, or another optional group
90
96
  if @options[i + 1].nil? or optional != @options[i + 1].is_optional then
91
97
  definition += ']'
92
98
  optional = nil
@@ -3,5 +3,5 @@
3
3
  module Consoler
4
4
 
5
5
  # Current version number
6
- VERSION = '1.0.2'
6
+ VERSION = '1.0.3'
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: consoler
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-14 00:00:00.000000000 Z
11
+ date: 2018-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -89,6 +89,7 @@ files:
89
89
  - ".editorconfig"
90
90
  - ".gitignore"
91
91
  - ".travis.yml"
92
+ - ".yardopts"
92
93
  - Gemfile
93
94
  - LICENSE.txt
94
95
  - README.md