luban-cli 0.3.0 → 0.3.1

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: ad584f0174c5361200ed88f252387ac294ff8768
4
- data.tar.gz: 9af48be33bc18fa6d049780354d0a6dc3abb34fd
3
+ metadata.gz: 2cef4bc2d5883cf60fc715d577ce581b4b99a37d
4
+ data.tar.gz: f9621b3ad7ef039ff84595cc6dc5950059519019
5
5
  SHA512:
6
- metadata.gz: 932c919d74edc1e23cc4a45fe10240913603864aa25cf71a24b76e0a542da0a54296d20348fb5f7dc5c645b1b143e3557d897a6fbaf36df2ae88b5a0ba1bd400
7
- data.tar.gz: 889e415204354c2c6db2440e1297fb52a85e4f59a0d662707c3bc404066eeb835c592b156fc3594bb35562c9db64f463504b2ab3bb1ebd97934de4a0e0b465b1
6
+ metadata.gz: 1e7ccbaef417438934e5d2a49d587c138d7c230122d0b96241935e3cc211b38e3a755f9c7302268229ccc110ae1d9712b5d08ec7b4e271eb616947020488808a
7
+ data.tar.gz: cebee1212f04f353fefb7457e0fcd65abd89cf8d4c008139684e497862ba26309fdfbea473c060773ce1865451d6767b33f71c09cf083565efbe4ce0409b70d5
data/CHANGELOG.md CHANGED
@@ -39,3 +39,13 @@ Bug fixes:
39
39
  * Apply the correct method creator when defining action method
40
40
  * Dispatch command under the right class context
41
41
  * Show correct command chain between program name and synopsis when composing parser banner
42
+
43
+ ## Version 0.3.1 (Apr 15, 2015)
44
+
45
+ Minor enhancements:
46
+ * Simplify keyword arguments for dispatch_command and action handler
47
+ * Include command chain as part of action method name for commands
48
+ * Enrich README with more documentation
49
+
50
+ Bug fixes:
51
+ * Handle validation for multiple values correctly
data/README.md CHANGED
@@ -6,21 +6,454 @@ Luban::CLI requires Ruby 2.1 or later.
6
6
 
7
7
  ## Installation
8
8
 
9
- Add this line to your application's Gemfile:
9
+ Add this line to your application Gemfile:
10
10
 
11
- gem 'luban-cli'
11
+ ```ruby
12
+ gem "luban-cli"
13
+ ```
12
14
 
13
15
  And then execute:
14
16
 
15
- $ bundle
17
+ ```
18
+ $ bundle
19
+ ```
16
20
 
17
21
  Or install it yourself as:
18
22
 
19
- $ gem install luban-cli
23
+ ```
24
+ $ gem install luban-cli
25
+ ```
20
26
 
21
27
  ## Usage
22
28
 
23
- TODO: Write usage instructions here
29
+ ### Simple Example
30
+
31
+ ```ruby
32
+ require 'luban/cli'
33
+
34
+ class MyApp < Luban::CLI::Application
35
+ configure do
36
+ # program "my_app"
37
+ version "1.0.0"
38
+ long_desc "Demo app for Luban::CLI"
39
+ option :prefix, "Prefix to a name, e.g., Mr, Ms, etc.", short: :p
40
+ option :suffix, "Suffix to a name, e.g., Jr, Sr, etc.", short: :s
41
+ switch :verbose, "Run in verbose mode", short: :V
42
+ argument :name, "Name to say hi"
43
+ action :say_hi
44
+ end
45
+
46
+ def say_hi(cmd:, argv:, args:, opts:)
47
+ name = compose_name(opts[:prefix], opts[:suffix], args[:name])
48
+ if opts[:verbose]
49
+ say_hi_verbosely(name, opts, args)
50
+ else
51
+ say_hi_concisely(name)
52
+ end
53
+ end
54
+
55
+ protected
56
+
57
+ def say_hi_verbosely(name, opts, args)
58
+ puts "Options: #{opts.inspect}"
59
+ puts "Arguments: #{args.inspect}"
60
+ say_hi_concisely(name)
61
+ end
62
+
63
+ def say_hi_concisely(name)
64
+ puts "Hi, #{name}!"
65
+ end
66
+
67
+ def compose_name(prefix, suffix, name)
68
+ name = name.capitalize
69
+ name = "#{prefix.capitalize}. #{name}" unless prefix.nil?
70
+ name = "#{name} #{suffix.capitalize}." unless suffix.nil?
71
+ name
72
+ end
73
+ end
74
+
75
+ MyApp.new.run
76
+ ```
77
+
78
+ ```
79
+ $ ruby my_app.rb -h
80
+ Usage: my_app [options] NAME
81
+
82
+ Options:
83
+ -v, --version Show hi version.
84
+ -p, --prefix PREFIX Prefix to a name, e.g., Mr, Ms, etc.
85
+ -s, --suffix SUFFIX Suffix to a name, e.g., Jr, Sr, etc.
86
+ -V, --verbose Run in verbose mode
87
+ -h, --help Show this help message.
88
+
89
+ Arguments:
90
+ NAME Name to say hi
91
+
92
+ Description:
93
+ Demo app for Luban::CLI
94
+
95
+ $ ruby my_app.rb -v
96
+ my_app 1.0.0
97
+
98
+ $ ruby my_app.rb john -p mr -s jr
99
+ Hi, Mr. John Jr.!
100
+
101
+ ruby examples/hi.rb john -p mr -s jr -V
102
+ Options: {:version=>false, :prefix=>"mr", :suffix=>"jr", :verbose=>true, :help=>false}
103
+ Arguments: {:name=>"chi"}
104
+ Hi, Mr. John Jr.!
105
+ ```
106
+
107
+ Please refer to [examples](examples) for more sample usage.
108
+
109
+ ## DSL
110
+
111
+ The following is an overview of the Luban::CLI DSL.
112
+
113
+ ### program
114
+
115
+ The name of the application. Default: $0.
116
+
117
+ ### desc
118
+
119
+ A short description of what the application does.
120
+
121
+ ### long_desc
122
+
123
+ A long description of what the application does.
124
+
125
+ ### argument
126
+
127
+ An arguement is a positioned parameter passed from command-line. To declare an argument:
128
+
129
+ ```ruby
130
+ argument :name, 'description', **modifiers, &blk
131
+ ```
132
+
133
+ The modifiers below can be used:
134
+
135
+ * :default - Default value for the argument.
136
+ * :required - Flag to indicate if the argument is mandatory or not. Default: true.
137
+ * :multiple - Flag to indicate argument is a list of values or a single value. Default: false.
138
+ * :type - Value type for the argument. Default: :string.
139
+ * :match - A regex for argument value matching.
140
+ * :within - A range or a list of values the argument value to be within.
141
+ * :assure - A code block for argument value validation.
142
+
143
+ If required argument is not provided, Luban::CLI::Base::MissingRequiredArguments will be raised.
144
+
145
+ If validation failed, Luban::CLI::Argument::InvalidArgumentValue will be raised.
146
+
147
+ Note: An argument with multiple values needs to be positioned at the last argument. Furthermore, you cannot specify more than one arguements with multiple values.
148
+
149
+ Here is an example how to use argument:
150
+
151
+ ```ruby
152
+ class MyApp < Luban::CLI::Application
153
+ configure do
154
+ argument :name, 'Name for an employee'
155
+ argument :gender, 'Gender for an employee',
156
+ type: :symbol, within: [:male, :femal]
157
+ argument :age, 'Age for an employee',
158
+ type: :integer, assure: ->(age) { age < 60 }
159
+ argument :level, 'Level for an employee',
160
+ type: :integer, within: 1..4
161
+ argument :email, 'Email for an employee',
162
+ match: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i,
163
+ multiple: true, required: false
164
+ action do |**params|
165
+ puts params.inspect
166
+ end
167
+ end
168
+ end
169
+
170
+ MyApp.new.run
171
+ ```
172
+
173
+ ```
174
+ $ ruby my_app.rb
175
+ Missing required argument(s): NAME, GENDER, LEVEL (Luban::CLI::Base::MissingRequiredArguments)
176
+ ... ...
177
+
178
+ $ ruby my_app.rb john male 90
179
+ Invalid value of argument AGE: 90 (Luban::CLI::Argument::InvalidArgumentValue)
180
+ ... ...
181
+
182
+ $ ruby my_app.rb john male 30 2
183
+ {:cmd=>nil, :argv=>[], :args=>{:name=>"john", :gender=>:male, :age=>30, :level=>2, :email=>nil}, :opts=>{:help=>false}}
184
+
185
+ $ ruby my_app.rb john male 30 2 john@company.com
186
+ {:cmd=>nil, :argv=>[], :args=>{:name=>"john", :gender=>:male, :age=>30, :level=>2, :email=>["john@company.com"]}, :opts=>{:help=>false}}
187
+
188
+ $ ruby my_app.rb john male 30 2 john@company.com john@personal.com
189
+ {:cmd=>nil, :argv=>[], :args=>{:name=>"john", :gender=>:male, :age=>30, :level=>2, :email=>["john@company.com", "john@personal.com"]}, :opts=>{:help=>false}}
190
+ ```
191
+
192
+ ### option
193
+
194
+ An option usually takes an argument, e.g. --require LIBRARIES. To declare an option:
195
+
196
+ ```ruby
197
+ option :name, 'description', **modifiers, &blk
198
+ ```
199
+
200
+ The extra modifiers below can be used along with all modifiers applicable to arguments:
201
+
202
+ * :long - Long style argument name.
203
+ * :short - Short style argument alias.
204
+
205
+ Note: modifier :required is turned off by default. Therefore, all options are not mandatory unless they are declared explicitly.
206
+
207
+ Here is an example how to use option:
208
+
209
+ ```ruby
210
+ class MyApp < Luban::CLI::Application
211
+ configure do
212
+ option :libraries, 'Require the LIBRARIES before executing your script',
213
+ long: :require, short: :r, multiple: true
214
+ action do |**params|
215
+ puts params.inspect
216
+ end
217
+ end
218
+ end
219
+
220
+ MyApp.new.run
221
+ ```
222
+
223
+ ```
224
+ $ ruby my_app.rb --require bundler
225
+ {:cmd=>nil, :argv=>[], :args=>{}, :opts=>{:libraries=>["bundler"], :help=>false}}
226
+
227
+ $ ruby my_app.rb -r bundler,rails
228
+ {:cmd=>nil, :argv=>[], :args=>{}, :opts=>{:libraries=>["bundler", "rails"], :help=>false}}
229
+ ```
230
+
231
+ Occassionally an option might take an optional argument, e.g. --inplace [EXTENSION]. This kind of option is called nullable option. The nullable option is set to true if the optional argument is not provided; otherwise, the value of the option is set to the value of the argument. To declare a nullable option, you can explicitly turn off nullable modifier which is off by default.
232
+
233
+ Here is an example how to use nullable option:
234
+
235
+ ```ruby
236
+ class MyApp < Luban::CLI::Application
237
+ configure do
238
+ option :inplace, 'Edit in place (make backup if EXTENSION supplied)',
239
+ nullable: true # Turn the option into nullable
240
+ action do |**params|
241
+ puts params.inspect
242
+ end
243
+ end
244
+ end
245
+
246
+ MyApp.new.run
247
+ ```
248
+
249
+ ```
250
+ $ ruby my_app.rb
251
+ {:cmd=>nil, :argv=>[], :args=>{}, :opts=>{:inplace=>nil, :help=>false}}
252
+
253
+ $ ruby my_app.rb --inplace
254
+ {:cmd=>nil, :argv=>[], :args=>{}, :opts=>{:inplace=>true, :help=>false}}
255
+
256
+ $ ruby my_app.rb --inplace .bak
257
+ {:cmd=>nil, :argv=>[], :args=>{}, :opts=>{:inplace=>".bak", :help=>false}}
258
+ ```
259
+
260
+ ### switch
261
+
262
+ A switch is a special option that doesn't take any arguments, e.g., --verbose, --help, etc. To declare a switch:
263
+
264
+ ```ruby
265
+ switch :name, 'description', **modifiers, &blk
266
+ ```
267
+
268
+ All modifiers applied to an option can be used for a switch except the following:
269
+
270
+ * :type - Type for switch is set to :bool (true/false) and it cannot be changed.
271
+ * :multiple - Set to false to ensure to handle a single value and it cannot be changed.
272
+
273
+ Negatable switch is also supported, e.g., --local, --no-local. To declare a negatable switch, you can explicitly turn on negatable modifier which is off by default.
274
+
275
+ Here is an example how to use negatable option:
276
+
277
+ ```ruby
278
+ class MyApp < Luban::CLI::Application
279
+ configure do
280
+ switch :local, 'Check repository locally',
281
+ negatable: true # Turn the switch into negatable: --local or --no-local
282
+ action do |**params|
283
+ puts params.inspect
284
+ end
285
+ end
286
+
287
+ MyApp.new.run
288
+ ```
289
+
290
+ ### help
291
+
292
+ Add a switch for help message display. The modifiers below can be used:
293
+
294
+ * :short - Short style switch for help display. Default: :h
295
+ * :desc - Set a switch description for help display. Default: "Show this help message."
296
+
297
+ DSL alias #auto_help is provided which applies default values for the above options.
298
+
299
+ The switch of help display is turned on by default unless explicitly turning off when creating an application or a command.
300
+
301
+ ```ruby
302
+ class MyApp < Luban::CLI::Application
303
+ ... ...
304
+ end
305
+
306
+ MyApp.new(auto_help: false)
307
+ ```
308
+
309
+ ### version
310
+
311
+ Specify or retrieve the version of the application.
312
+
313
+ If calling without any parameters, version previously set is returned.
314
+
315
+ If calling with a version, a switch for version display is added to the application. The modifiers below can be used:
316
+
317
+ * :short - Set a short style switch for version display. Default: :v
318
+ * :desc - Set a switch description for version disiplay. Default: "Show #{program_name} version."
319
+
320
+ ### action
321
+
322
+ Specify handler for the CLI application or command.
323
+
324
+ It accepts a code block or an instance method name from the application as the action handler. If a code block is given, the code block is executed in the binding of the application. If both a code block and an instance method name are provided, the instance method name is used and the code block is ignored.
325
+
326
+ The handler accepts the following keyword arguments:
327
+
328
+ * :args - arguments parsed from the command-line
329
+ * :opts - options parsed from the command-line
330
+
331
+
332
+ ### action!
333
+
334
+ Same as action, but removes command-line arguments destructively.
335
+
336
+ ### configure
337
+
338
+ This is used to configure CLI application during definition.
339
+
340
+ Here is an example for how to configure CLI application:
341
+
342
+ ```ruby
343
+
344
+ class MyApp < Luban::CLI::Application
345
+ configure do
346
+ program "my_app"
347
+ version "1.0.0"
348
+ long_desc "Demo app for Luban::CLI"
349
+ option :opt1, "Description for opt1", short: :o
350
+ switch :swt1, "Description for swt1", short: :s
351
+ argument :arg1, "Description to arg1"
352
+ action :do_something
353
+ end
354
+
355
+ def do_something(args:, opts:)
356
+ ... ...
357
+ end
358
+ end
359
+
360
+ MyApp.new.run
361
+
362
+ ```
363
+
364
+ However, it can be overriden if a configuration block is provided during application instance creation:
365
+
366
+ ```ruby
367
+ MyApp.new.run do
368
+ program "my_app"
369
+ version "1.0.0"
370
+ long_desc "Demo app for Luban::CLI"
371
+ option :opt2, "Description for opt2", short: :p
372
+ switch :swt2, "Description for swt2", short: :w
373
+ action :do_something_else
374
+ end
375
+
376
+ ```
377
+
378
+ ## Commands
379
+
380
+ Luban::CLI supports commands/subcommands. Commands can also be nested. However, all action handlers should be defined under the the application class, otherwise NoMethodError will be raised.
381
+
382
+ ```ruby
383
+ class MyApp < Luban::CLI::Application
384
+ configure do
385
+ version '1.0.0'
386
+ desc 'Short description for the application'
387
+ long_desc 'Long description for the application'
388
+ end
389
+
390
+ # Define a help command to list all commands or help for one command.
391
+ auto_help_command
392
+
393
+ command :cmd1 do
394
+ desc 'Description for command 1'
395
+
396
+ command :task1 do
397
+ desc 'Description for task 1'
398
+ argument :arg1, 'Description for arg1', type: :string
399
+ action :exec_command1_task1
400
+ end
401
+
402
+ command :task2 do
403
+ desc 'Description for task 2'
404
+ option :opt1, 'Description for opt1', type: :integer
405
+ action :exec_command1_task2
406
+ end
407
+ end
408
+
409
+ command :cmd2 do
410
+ desc 'Description for command 1'
411
+ switch :swt1, 'Description for swt1'
412
+ action :exec_command2
413
+ end
414
+
415
+ def exec_command1_task1(args:, opts:); puts 'In command 1/task 1'; end
416
+ def exec_command1_task2(args:, opts:); puts 'In command 1/task 2'; end
417
+ def exec_command2(args:, opts:); puts 'In command 2'; end
418
+ end
419
+
420
+ MyApp.new.run
421
+ ```
422
+
423
+ ### Command method/handler
424
+
425
+ By default, a new method will be defined for each command under the application class. Usually you don't need to call this method directly. Luban::CLI dispatches the specified command to the corresponding command method/handler properly.
426
+
427
+ The command method name is composed of the following, concatenating with an underscore:
428
+
429
+ * prefix - Default prefix is "__command_".
430
+ * command chain
431
+ * For regular command, it is the command name itself
432
+ * For nested commands, it is the commands from the top to the bottom one
433
+
434
+ In the example above, there are following command methods defined in MyApp:
435
+
436
+ * __command_cmd1_task1
437
+ * __command_cmd1_task2
438
+ * __command_cmd2
439
+
440
+ You can also change the prefix to your preferred one by setting the modifier :prefix when defining a command:
441
+
442
+ ```ruby
443
+ command :cmd1, prefix: '__my_prefix_' do
444
+ ... ...
445
+ end
446
+ ```
447
+
448
+ ### auto_help_command / help_command
449
+
450
+ DSL method #auto_help_command is used to define a command to list all commands or help for one command. Under rare circumstances that you need to customize the help command (i.e., use a different command name like :manual), you can use DSL method #help_command which accepts the same parameters that for #command.
451
+
452
+ ## Applications
453
+
454
+ Luban::CLI provides a base class for cli application, Luban::CLI::Application. You can define your own cli application by inheriting it as examples shown in the previous sections.
455
+
456
+ In addition to command-line argument parsing capabilities, Luban::CLI::Application also supports a rc file. For example, if an application called "my_app.rb", it looks up rc file ".my_apprc" under user home when the application starts up. The rc file uses YML format. If rc file is found, the content will be loaded into the instance variable :rc; if rc file is not found, the instance variable :rc will be initialized as an empty hash.
24
457
 
25
458
  ## Contributing
26
459
 
@@ -49,7 +49,9 @@ module Luban
49
49
  end
50
50
 
51
51
  def valid?(value = @value)
52
- !missing?(value) and match?(value) and within?(value) and assured?(value)
52
+ (multiple? ? value : [value]).all? do |v|
53
+ !missing?(v) and match?(v) and within?(v) and assured?(v)
54
+ end
53
55
  end
54
56
 
55
57
  def missing?(value = @value)
@@ -113,7 +115,7 @@ module Luban
113
115
  unless err_msg.nil?
114
116
  raise ArgumentError, "Default value for #{kind} #{display_name} #{err_msg}"
115
117
  end
116
- unless (multiple? ? default : [default]).all? { |v| valid?(v) }
118
+ unless valid?(default)
117
119
  raise ArgumentError, "Invalid default value for #{kind} #{display_name}: #{default.inspect}"
118
120
  end
119
121
  end
@@ -21,7 +21,6 @@ module Luban
21
21
 
22
22
  attr_reader :app
23
23
  attr_reader :prefix
24
- attr_reader :action_method
25
24
  attr_reader :program_name
26
25
  attr_reader :options
27
26
  attr_reader :arguments
@@ -39,7 +38,6 @@ module Luban
39
38
  @app = app
40
39
  @action_name = action_name
41
40
  @prefix = prefix
42
- @action_method = "#{@prefix}#{@action_name}"
43
41
  @action_defined = false
44
42
 
45
43
  @program_name = default_program_name
@@ -62,6 +60,10 @@ module Luban
62
60
 
63
61
  def default_prefix; ''; end
64
62
 
63
+ def action_method
64
+ @action_method ||= "#{@prefix}#{@action_name}"
65
+ end
66
+
65
67
  def parser
66
68
  @parser ||= create_parser
67
69
  end
@@ -90,7 +92,7 @@ module Luban
90
92
  end
91
93
  end
92
94
 
93
- def dispatch_command(context, cmd:, argv:, **params)
95
+ def dispatch_command(context, cmd:, argv:)
94
96
  validate_command(cmd)
95
97
  context.send(commands[cmd].action_method, argv)
96
98
  end
@@ -103,8 +103,8 @@ module Luban
103
103
  _base = self
104
104
  parse_method = preserve_argv ? :parse : :parse!
105
105
  define_action_method do |argv = _base.default_argv|
106
- _base.send(:process, self, parse_method, argv) do |result|
107
- instance_exec(**result, &handler)
106
+ _base.send(:process, self, parse_method, argv) do |params|
107
+ instance_exec(**params, &handler)
108
108
  end
109
109
  end
110
110
  @action_defined = true
@@ -126,11 +126,11 @@ module Luban
126
126
  show_version
127
127
  else
128
128
  if has_commands?
129
- dispatch_command(context, **result)
129
+ dispatch_command(context, cmd: result[:cmd], argv: result[:argv])
130
130
  else
131
131
  validate_required_options
132
132
  validate_required_arguments
133
- yield result
133
+ yield args: result[:args], opts: result[:opts]
134
134
  end
135
135
  end
136
136
  rescue OptionParser::ParseError, Error => e
@@ -12,6 +12,10 @@ module Luban
12
12
 
13
13
  def default_prefix; '__command_'; end
14
14
 
15
+ def action_method
16
+ @action_method ||= "#{@prefix}#{command_chain.map(&:to_s).join('_')}"
17
+ end
18
+
15
19
  protected
16
20
 
17
21
  def compose_banner
@@ -1,5 +1,5 @@
1
1
  module Luban
2
2
  module CLI
3
- VERSION = "0.3.0"
3
+ VERSION = "0.3.1"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: luban-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rubyist Chi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-11 00:00:00.000000000 Z
11
+ date: 2015-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler