luban-cli 0.3.2 → 0.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +61 -45
- data/lib/luban/cli/application.rb +10 -4
- data/lib/luban/cli/base/argument.rb +1 -1
- data/lib/luban/cli/base/commands.rb +72 -0
- data/lib/luban/cli/base/core.rb +68 -18
- data/lib/luban/cli/base/dsl.rb +34 -33
- data/lib/luban/cli/base.rb +1 -0
- data/lib/luban/cli/command.rb +7 -2
- data/lib/luban/cli/version.rb +1 -1
- data/lib/luban/cli.rb +0 -1
- metadata +3 -3
- data/lib/luban/cli/commands.rb +0 -98
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3717edf51bdf2888b4d7d7d378b8ba777e7f2df
|
4
|
+
data.tar.gz: 23c48793a99823629c9b339a7b65f64f3be9dc27
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d5dd0736e211101a179af588fab6892445fc3e6b77d4203237f383cb14267da9ce61dbdc565c6ffa8cf4a46fbf7feaba43c1347afbfc8cb27afa0b00e42222ab
|
7
|
+
data.tar.gz: abe9f13478171fd6c9bb4b5163f73919666b2f830c5a43896812da9e09e938da9ed16c23699486657b94752ef68849f902ff5384ffee6dcb6bce5e1026f636fa
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
# Change log
|
2
2
|
|
3
|
+
## Version 0.4.0 (Oct 14, 2015)
|
4
|
+
|
5
|
+
New features:
|
6
|
+
* Provide convenient method :start to create and run new instance of CLI application
|
7
|
+
* Added method #alter to allow change cli configuration during runtime
|
8
|
+
|
9
|
+
Minor enhancements:
|
10
|
+
* Merge Commands module into Luban::CLI::Base
|
11
|
+
* Handled commands in instance level instead of class level
|
12
|
+
* Handled command definition and action method on its eigenclass
|
13
|
+
* Enhanced action method invocation to look up thru its parent
|
14
|
+
* Update examples accordingly
|
15
|
+
* Some other minor refactoring
|
16
|
+
|
3
17
|
## Version 0.3.2 (Apr 28, 2015)
|
4
18
|
|
5
19
|
Minor enhancements:
|
data/README.md
CHANGED
@@ -43,7 +43,7 @@ class MyApp < Luban::CLI::Application
|
|
43
43
|
action :say_hi
|
44
44
|
end
|
45
45
|
|
46
|
-
def say_hi(
|
46
|
+
def say_hi(args:, opts:)
|
47
47
|
name = compose_name(opts[:prefix], opts[:suffix], args[:name])
|
48
48
|
if opts[:verbose]
|
49
49
|
say_hi_verbosely(name, opts, args)
|
@@ -72,7 +72,7 @@ class MyApp < Luban::CLI::Application
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
-
MyApp.
|
75
|
+
MyApp.start
|
76
76
|
```
|
77
77
|
|
78
78
|
```
|
@@ -149,6 +149,8 @@ Note: An argument with multiple values needs to be positioned at the last argume
|
|
149
149
|
Here is an example how to use argument:
|
150
150
|
|
151
151
|
```ruby
|
152
|
+
require 'luban/cli'
|
153
|
+
|
152
154
|
class MyApp < Luban::CLI::Application
|
153
155
|
configure do
|
154
156
|
argument :name, 'Name for an employee'
|
@@ -167,7 +169,7 @@ class MyApp < Luban::CLI::Application
|
|
167
169
|
end
|
168
170
|
end
|
169
171
|
|
170
|
-
MyApp.
|
172
|
+
MyApp.start
|
171
173
|
```
|
172
174
|
|
173
175
|
```
|
@@ -207,6 +209,8 @@ Note: modifier :required is turned off by default. Therefore, all options are no
|
|
207
209
|
Here is an example how to use option:
|
208
210
|
|
209
211
|
```ruby
|
212
|
+
require 'luban/cli'
|
213
|
+
|
210
214
|
class MyApp < Luban::CLI::Application
|
211
215
|
configure do
|
212
216
|
option :libraries, 'Require the LIBRARIES before executing your script',
|
@@ -217,7 +221,7 @@ class MyApp < Luban::CLI::Application
|
|
217
221
|
end
|
218
222
|
end
|
219
223
|
|
220
|
-
MyApp.
|
224
|
+
MyApp.start
|
221
225
|
```
|
222
226
|
|
223
227
|
```
|
@@ -233,6 +237,8 @@ Occassionally an option might take an optional argument, e.g. --inplace [EXTENSI
|
|
233
237
|
Here is an example how to use nullable option:
|
234
238
|
|
235
239
|
```ruby
|
240
|
+
require 'luban/cli'
|
241
|
+
|
236
242
|
class MyApp < Luban::CLI::Application
|
237
243
|
configure do
|
238
244
|
option :inplace, 'Edit in place (make backup if EXTENSION supplied)',
|
@@ -243,7 +249,7 @@ class MyApp < Luban::CLI::Application
|
|
243
249
|
end
|
244
250
|
end
|
245
251
|
|
246
|
-
MyApp.
|
252
|
+
MyApp.start
|
247
253
|
```
|
248
254
|
|
249
255
|
```
|
@@ -275,16 +281,19 @@ Negatable switch is also supported, e.g., --local, --no-local. To declare a nega
|
|
275
281
|
Here is an example how to use negatable option:
|
276
282
|
|
277
283
|
```ruby
|
284
|
+
require 'luban/cli'
|
285
|
+
|
278
286
|
class MyApp < Luban::CLI::Application
|
279
287
|
configure do
|
280
288
|
switch :local, 'Check repository locally',
|
281
289
|
negatable: true # Turn the switch into negatable: --local or --no-local
|
282
|
-
|
283
|
-
|
290
|
+
action do |**params|
|
291
|
+
puts params.inspect
|
292
|
+
end
|
284
293
|
end
|
285
294
|
end
|
286
295
|
|
287
|
-
MyApp.
|
296
|
+
MyApp.start
|
288
297
|
```
|
289
298
|
|
290
299
|
### help
|
@@ -299,11 +308,13 @@ DSL alias #auto_help is provided which applies default values for the above opti
|
|
299
308
|
The switch of help display is turned on by default unless explicitly turning off when creating an application or a command.
|
300
309
|
|
301
310
|
```ruby
|
311
|
+
require 'luban/cli'
|
312
|
+
|
302
313
|
class MyApp < Luban::CLI::Application
|
303
314
|
... ...
|
304
315
|
end
|
305
316
|
|
306
|
-
MyApp.
|
317
|
+
MyApp.start(auto_help: false)
|
307
318
|
```
|
308
319
|
|
309
320
|
### version
|
@@ -340,6 +351,7 @@ This is used to configure CLI application during definition.
|
|
340
351
|
Here is an example for how to configure CLI application:
|
341
352
|
|
342
353
|
```ruby
|
354
|
+
require 'luban/cli'
|
343
355
|
|
344
356
|
class MyApp < Luban::CLI::Application
|
345
357
|
configure do
|
@@ -357,59 +369,61 @@ class MyApp < Luban::CLI::Application
|
|
357
369
|
end
|
358
370
|
end
|
359
371
|
|
360
|
-
MyApp.
|
372
|
+
MyApp.start
|
361
373
|
|
362
374
|
```
|
363
375
|
|
364
376
|
However, it can be overriden if a configuration block is provided during application instance creation:
|
365
377
|
|
366
378
|
```ruby
|
367
|
-
MyApp.
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
379
|
+
MyApp.start do
|
380
|
+
program "my_app"
|
381
|
+
version "1.0.0"
|
382
|
+
long_desc "Demo app for Luban::CLI"
|
383
|
+
option :opt2, "Description for opt2", short: :p
|
384
|
+
switch :swt2, "Description for swt2", short: :w
|
385
|
+
action :do_something_else
|
374
386
|
end
|
375
387
|
|
376
388
|
```
|
377
389
|
|
378
390
|
## Commands
|
379
391
|
|
380
|
-
Luban::CLI supports commands/subcommands. Commands can also be nested. However, all action handlers
|
392
|
+
Luban::CLI supports commands/subcommands. Commands can also be nested. However, all action handlers are preferred to be defined under the the application class; otherwise RuntimeError will be raised.
|
381
393
|
|
382
394
|
```ruby
|
395
|
+
require 'luban/cli'
|
396
|
+
|
383
397
|
class MyApp < Luban::CLI::Application
|
384
398
|
configure do
|
385
399
|
version '1.0.0'
|
386
400
|
desc 'Short description for the application'
|
387
401
|
long_desc 'Long description for the application'
|
388
|
-
end
|
389
402
|
|
390
|
-
|
391
|
-
|
403
|
+
command :cmd1 do
|
404
|
+
desc 'Description for command 1'
|
392
405
|
|
393
|
-
|
394
|
-
|
406
|
+
command :task1 do
|
407
|
+
desc 'Description for task 1'
|
408
|
+
argument :arg1, 'Description for arg1', type: :string
|
409
|
+
action :exec_command1_task1
|
410
|
+
end
|
395
411
|
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
412
|
+
command :task2 do
|
413
|
+
desc 'Description for task 2'
|
414
|
+
option :opt1, 'Description for opt1', type: :integer
|
415
|
+
action :exec_command1_task2
|
416
|
+
end
|
400
417
|
end
|
401
418
|
|
402
|
-
command :
|
403
|
-
desc 'Description for
|
404
|
-
|
405
|
-
action :
|
419
|
+
command :cmd2 do
|
420
|
+
desc 'Description for command 1'
|
421
|
+
switch :swt1, 'Description for swt1'
|
422
|
+
action :exec_command2
|
406
423
|
end
|
407
|
-
end
|
408
424
|
|
409
|
-
|
410
|
-
|
411
|
-
switch :swt1, 'Description for swt1'
|
412
|
-
action :exec_command2
|
425
|
+
# Define a help command to list all commands or help for one command.
|
426
|
+
auto_help_command
|
413
427
|
end
|
414
428
|
|
415
429
|
def exec_command1_task1(args:, opts:); puts 'In command 1/task 1'; end
|
@@ -417,12 +431,12 @@ class MyApp < Luban::CLI::Application
|
|
417
431
|
def exec_command2(args:, opts:); puts 'In command 2'; end
|
418
432
|
end
|
419
433
|
|
420
|
-
MyApp.
|
434
|
+
MyApp.start
|
421
435
|
```
|
422
436
|
|
423
437
|
### Command method/handler
|
424
438
|
|
425
|
-
By default, a new method will be defined for each command under the application
|
439
|
+
By default, a new method will be defined for each command under the application/command instance's eigenclass. Usually you don't need to call this method directly. Luban::CLI dispatches the specified command to the corresponding command method/handler properly.
|
426
440
|
|
427
441
|
The command method name is composed of the following, concatenating with an underscore:
|
428
442
|
|
@@ -456,15 +470,17 @@ Commands can be defined directly within the Luban app class like examples shown
|
|
456
470
|
Below is an example demonstrating how command injection is supposed to work.
|
457
471
|
|
458
472
|
```ruby
|
473
|
+
require 'luban/cli'
|
474
|
+
|
459
475
|
module App
|
460
476
|
module ControlTasks
|
461
|
-
class
|
477
|
+
class Task1 < Luban::CLI::Command
|
462
478
|
configure do
|
463
479
|
... ...
|
464
480
|
end
|
465
481
|
end
|
466
482
|
|
467
|
-
class
|
483
|
+
class Task2 < Luban::CLI::Command
|
468
484
|
configure do
|
469
485
|
... ...
|
470
486
|
end
|
@@ -485,6 +501,8 @@ class MyApp < Luban::CLI::Application
|
|
485
501
|
# command 'app:control_tasks:task1'
|
486
502
|
# command 'app:control_tasks:task2'
|
487
503
|
end
|
504
|
+
|
505
|
+
MyApp.start
|
488
506
|
```
|
489
507
|
|
490
508
|
```
|
@@ -495,16 +513,14 @@ $ ruby my_app.rb app:control_tasks:task2
|
|
495
513
|
|
496
514
|
As shown above, there are a few naming conventions about the command class naming and injection:
|
497
515
|
|
498
|
-
* Command class should has suffix "Command"
|
499
|
-
* For instances, Task1Command, Task2Command
|
500
516
|
* Module name to be injected should be fully qualified
|
501
517
|
* namespaces/modules should be in snake case and
|
502
518
|
* Separated by colon as the delimiter
|
503
|
-
* For
|
519
|
+
* For instance, 'app:control_tasks'
|
504
520
|
* Alternatively, commands can be injected individually
|
505
521
|
* Command name should be fully qualified
|
506
|
-
*
|
507
|
-
|
522
|
+
* For instance, 'app:control_tasks:task2'
|
523
|
+
|
508
524
|
## Applications
|
509
525
|
|
510
526
|
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.
|
@@ -3,12 +3,16 @@ require 'pathname'
|
|
3
3
|
module Luban
|
4
4
|
module CLI
|
5
5
|
class Application < Base
|
6
|
+
class << self
|
7
|
+
def start(action_name = :run, **opts, &config_blk)
|
8
|
+
new(action_name, **opts, &config_blk).send(action_name)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
6
12
|
attr_reader :rc
|
7
13
|
|
8
14
|
def initialize(action_name = :run, **opts, &config_blk)
|
9
15
|
super(self, action_name, **opts, &config_blk)
|
10
|
-
@rc = init_rc
|
11
|
-
validate
|
12
16
|
end
|
13
17
|
|
14
18
|
def rc_file
|
@@ -29,7 +33,9 @@ module Luban
|
|
29
33
|
|
30
34
|
protected
|
31
35
|
|
32
|
-
def
|
36
|
+
def on_configure
|
37
|
+
@rc = init_rc
|
38
|
+
end
|
33
39
|
|
34
40
|
def init_rc
|
35
41
|
if rc_file_exists?
|
@@ -45,4 +51,4 @@ module Luban
|
|
45
51
|
end
|
46
52
|
end
|
47
53
|
end
|
48
|
-
end
|
54
|
+
end
|
@@ -134,7 +134,7 @@ module Luban
|
|
134
134
|
|
135
135
|
def verify_config_assurance
|
136
136
|
unless @config[:assure].respond_to?(:call)
|
137
|
-
raise ArgumentError, "Assurance of #{kind} #{display_name} must
|
137
|
+
raise ArgumentError, "Assurance of #{kind} #{display_name} must respond to #call."
|
138
138
|
end
|
139
139
|
end
|
140
140
|
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Luban
|
2
|
+
module CLI
|
3
|
+
class Base
|
4
|
+
def command(cmd, base: Command, **opts, &blk)
|
5
|
+
cmd = cmd.to_sym
|
6
|
+
@commands[cmd] = command_class(cmd, base).new(self, cmd, **opts, &blk)
|
7
|
+
end
|
8
|
+
|
9
|
+
def undef_command(cmd)
|
10
|
+
undef_singleton_method(@commands.delete(cmd.to_sym).action_method)
|
11
|
+
end
|
12
|
+
|
13
|
+
def use_commands(module_name, **opts, &blk)
|
14
|
+
module_class = Object.const_get(camelcase(module_name.to_s), false)
|
15
|
+
module_class.constants(false).map { |c| module_class.const_get(c, false) }.each do |c|
|
16
|
+
command(snakecase(c.name), base: c, **opts, &blk) if c < Command
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def list_commands
|
21
|
+
@commands.keys
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_command?(cmd)
|
25
|
+
@commands.has_key?(cmd.to_sym)
|
26
|
+
end
|
27
|
+
|
28
|
+
def has_commands?
|
29
|
+
!@commands.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def command_class(cmd, base)
|
35
|
+
class_name = camelcase(cmd)
|
36
|
+
if command_class_defined?(class_name)
|
37
|
+
get_command_class(class_name)
|
38
|
+
else
|
39
|
+
define_command_class(class_name, base)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def command_class_defined?(class_name)
|
44
|
+
self.class.const_defined?(class_name, false)
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_command_class(class_name)
|
48
|
+
self.class.const_get(class_name, false)
|
49
|
+
end
|
50
|
+
|
51
|
+
def define_command_class(class_name, base)
|
52
|
+
self.class.send(:define_class, class_name, base: base, namespace: self.class)
|
53
|
+
end
|
54
|
+
|
55
|
+
def camelcase(str)
|
56
|
+
str = str.to_s.dup
|
57
|
+
str.gsub!(/(\:|\/)(.?)/){ "::#{$2.upcase}" }
|
58
|
+
str.gsub!(/(?:_+|-+)([a-z])/){ $1.upcase }
|
59
|
+
str.gsub!(/(\A|\s)([a-z])/){ $1 + $2.upcase }
|
60
|
+
str
|
61
|
+
end
|
62
|
+
|
63
|
+
def snakecase(str)
|
64
|
+
str.gsub(/::/, ':').
|
65
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
66
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
67
|
+
tr("-", "_").
|
68
|
+
downcase
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/luban/cli/base/core.rb
CHANGED
@@ -8,8 +8,6 @@ OptionParser::Officious.delete('version')
|
|
8
8
|
module Luban
|
9
9
|
module CLI
|
10
10
|
class Base
|
11
|
-
include Commands
|
12
|
-
|
13
11
|
class MissingCommand < Error; end
|
14
12
|
class InvalidCommand < Error; end
|
15
13
|
class MissingRequiredOptions < Error; end
|
@@ -19,7 +17,7 @@ module Luban
|
|
19
17
|
DefaultSummaryIndent = 4
|
20
18
|
DefaultTitleIndent = 2
|
21
19
|
|
22
|
-
attr_reader :
|
20
|
+
attr_reader :parent
|
23
21
|
attr_reader :prefix
|
24
22
|
attr_reader :program_name
|
25
23
|
attr_reader :options
|
@@ -29,13 +27,14 @@ module Luban
|
|
29
27
|
attr_reader :version
|
30
28
|
attr_reader :result
|
31
29
|
attr_reader :default_argv
|
30
|
+
attr_reader :commands
|
32
31
|
|
33
32
|
attr_accessor :title_indent
|
34
33
|
attr_accessor :summary_width
|
35
34
|
attr_accessor :summary_indent
|
36
35
|
|
37
|
-
def initialize(
|
38
|
-
@
|
36
|
+
def initialize(parent, action_name, prefix: default_prefix, auto_help: true, &config_blk)
|
37
|
+
@parent = parent
|
39
38
|
@action_name = action_name
|
40
39
|
@prefix = prefix
|
41
40
|
@action_defined = false
|
@@ -48,6 +47,7 @@ module Luban
|
|
48
47
|
@version = ''
|
49
48
|
@default_argv = ARGV
|
50
49
|
@result = { cmd: nil, argv: @default_argv, args: {}, opts: {} }
|
50
|
+
@commands = {}
|
51
51
|
|
52
52
|
@title_indent = DefaultTitleIndent
|
53
53
|
@summary_width = DefaultSummaryWidth
|
@@ -78,36 +78,86 @@ module Luban
|
|
78
78
|
@result = { cmd: nil, argv: @default_argv, args: {}, opts: {} }
|
79
79
|
end
|
80
80
|
|
81
|
+
def alter(&blk)
|
82
|
+
instance_eval(&blk)
|
83
|
+
on_alter
|
84
|
+
end
|
85
|
+
|
81
86
|
protected
|
82
87
|
|
88
|
+
def self.define_class(class_name, base:, namespace:)
|
89
|
+
mods = class_name.split('::')
|
90
|
+
cmd_class = mods.pop
|
91
|
+
mods.inject(namespace) do |ns, mod|
|
92
|
+
ns.const_set(mod, Module.new) unless ns.const_defined?(mod, false)
|
93
|
+
ns.const_get(mod, false)
|
94
|
+
end.const_set(cmd_class, Class.new(base))
|
95
|
+
end
|
96
|
+
|
97
|
+
def undef_singleton_method(method_name)
|
98
|
+
# Undefine methods that are defined in the eigenclass
|
99
|
+
(class << self; self; end).send(:undef_method, method_name)
|
100
|
+
end
|
101
|
+
|
83
102
|
def configure(&blk)
|
84
|
-
config_blk
|
85
|
-
|
103
|
+
[self.class.config_blk, blk].each do |callback|
|
104
|
+
instance_eval(&callback) unless callback.nil?
|
105
|
+
end
|
106
|
+
on_configure
|
86
107
|
end
|
87
108
|
|
109
|
+
def on_configure; end
|
110
|
+
def on_alter; end
|
111
|
+
|
88
112
|
def setup_default_action
|
89
|
-
|
90
|
-
|
91
|
-
|
113
|
+
if has_commands?
|
114
|
+
action_noops
|
115
|
+
else
|
116
|
+
action_abort
|
92
117
|
end
|
93
118
|
end
|
94
119
|
|
95
|
-
def
|
120
|
+
def action_noops
|
121
|
+
action { } # NOOPS
|
122
|
+
end
|
123
|
+
|
124
|
+
def action_abort
|
125
|
+
name = @action_name
|
126
|
+
action do
|
127
|
+
abort "Aborted! Action is NOT defined for #{name} in #{self.class.name}."
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def dispatch_command(cmd:, argv:)
|
96
132
|
validate_command(cmd)
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
133
|
+
send(commands[cmd].action_method, argv)
|
134
|
+
end
|
135
|
+
|
136
|
+
def invoke_action(action_method, **opts)
|
137
|
+
handler = find_action_handler(action_method)
|
138
|
+
if handler.nil?
|
139
|
+
raise RuntimeError, "Action handler #{action_method} is MISSING."
|
140
|
+
else
|
141
|
+
handler.send(action_method, **opts)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def find_action_handler(action_method)
|
146
|
+
if respond_to?(action_method, true)
|
147
|
+
self
|
148
|
+
elsif @parent != self and @parent.respond_to?(:find_action_handler, true)
|
149
|
+
@parent.send(:find_action_handler, action_method)
|
150
|
+
else
|
151
|
+
nil
|
102
152
|
end
|
103
153
|
end
|
104
154
|
|
105
155
|
def validate_command(cmd)
|
106
156
|
if cmd.nil?
|
107
|
-
raise MissingCommand, "
|
157
|
+
raise MissingCommand, "Please specify a command to execute."
|
108
158
|
end
|
109
159
|
unless has_command?(cmd)
|
110
|
-
raise InvalidCommand, "Invalid command
|
160
|
+
raise InvalidCommand, "Invalid command: #{cmd}"
|
111
161
|
end
|
112
162
|
end
|
113
163
|
|
data/lib/luban/cli/base/dsl.rb
CHANGED
@@ -1,28 +1,18 @@
|
|
1
1
|
module Luban
|
2
2
|
module CLI
|
3
3
|
class Base
|
4
|
-
include Commands
|
5
|
-
|
6
4
|
class << self
|
5
|
+
def inherited(subclass)
|
6
|
+
super
|
7
|
+
# Ensure configuration block from base class
|
8
|
+
# got inherited to its subclasses
|
9
|
+
blk = instance_variable_get('@config_blk')
|
10
|
+
subclass.instance_variable_set('@config_blk', blk.clone) unless blk.nil?
|
11
|
+
end
|
12
|
+
|
7
13
|
attr_reader :config_blk
|
8
|
-
|
9
|
-
def configure(&blk); @config_blk = blk; end
|
10
14
|
|
11
|
-
def
|
12
|
-
if block_given?
|
13
|
-
command(**opts, &blk)
|
14
|
-
else
|
15
|
-
validator = method(:has_command?)
|
16
|
-
command(:help, **opts) do
|
17
|
-
desc "List all commands or help for one command"
|
18
|
-
argument :command, "Command to help for",
|
19
|
-
type: :symbol, required: false,
|
20
|
-
assure: validator
|
21
|
-
action :show_help_for_command
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
alias_method :auto_help_command, :help_command
|
15
|
+
def configure(&blk); @config_blk = blk; end
|
26
16
|
end
|
27
17
|
|
28
18
|
def program(name)
|
@@ -72,6 +62,22 @@ module Luban
|
|
72
62
|
end
|
73
63
|
end
|
74
64
|
|
65
|
+
def help_command(**opts, &blk)
|
66
|
+
if block_given?
|
67
|
+
command(**opts, &blk)
|
68
|
+
else
|
69
|
+
validator = method(:has_command?)
|
70
|
+
command(:help, **opts) do
|
71
|
+
desc "List all commands or help for one command"
|
72
|
+
argument :command, "Command to help for",
|
73
|
+
type: :symbol, required: false,
|
74
|
+
assure: validator
|
75
|
+
action :show_help_for_command
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
alias_method :auto_help_command, :help_command
|
80
|
+
|
75
81
|
def version(ver = nil, short: :v, desc: "Show #{program_name} version.", &blk)
|
76
82
|
if ver.nil?
|
77
83
|
@version
|
@@ -95,7 +101,7 @@ module Luban
|
|
95
101
|
|
96
102
|
def create_action(method_name = nil, preserve_argv: true, &blk)
|
97
103
|
handler = if method_name
|
98
|
-
lambda { |**opts|
|
104
|
+
lambda { |**opts| invoke_action(method_name, **opts) }
|
99
105
|
elsif block_given?
|
100
106
|
blk
|
101
107
|
end
|
@@ -105,7 +111,7 @@ module Luban
|
|
105
111
|
_base = self
|
106
112
|
parse_method = preserve_argv ? :parse : :parse!
|
107
113
|
define_action_method do |argv = _base.default_argv|
|
108
|
-
_base.send(:process,
|
114
|
+
_base.send(:process, parse_method, argv) do |params|
|
109
115
|
instance_exec(**params, &handler)
|
110
116
|
end
|
111
117
|
end
|
@@ -113,26 +119,21 @@ module Luban
|
|
113
119
|
end
|
114
120
|
|
115
121
|
def define_action_method(&action_blk)
|
116
|
-
@
|
117
|
-
end
|
118
|
-
|
119
|
-
def method_creator
|
120
|
-
@method_creator ||= @app.is_a?(Class) ? :define_method : :define_singleton_method
|
122
|
+
@parent.send(:define_singleton_method, action_method, &action_blk)
|
121
123
|
end
|
122
124
|
|
123
|
-
def process(
|
125
|
+
def process(parse_method, argv)
|
124
126
|
send(parse_method, argv)
|
125
127
|
if result[:opts][:help]
|
126
128
|
show_help
|
127
|
-
elsif result[:opts][:version]
|
129
|
+
elsif !@version.empty? and result[:opts][:version]
|
128
130
|
show_version
|
129
131
|
else
|
132
|
+
validate_required_options
|
133
|
+
validate_required_arguments
|
134
|
+
yield args: result[:args], opts: result[:opts]
|
130
135
|
if has_commands?
|
131
|
-
dispatch_command(
|
132
|
-
else
|
133
|
-
validate_required_options
|
134
|
-
validate_required_arguments
|
135
|
-
yield args: result[:args], opts: result[:opts]
|
136
|
+
dispatch_command(cmd: result[:cmd], argv: result[:argv])
|
136
137
|
end
|
137
138
|
end
|
138
139
|
rescue OptionParser::ParseError, Error => e
|
data/lib/luban/cli/base.rb
CHANGED
data/lib/luban/cli/command.rb
CHANGED
@@ -4,10 +4,10 @@ module Luban
|
|
4
4
|
attr_reader :name
|
5
5
|
attr_reader :command_chain
|
6
6
|
|
7
|
-
def initialize(
|
7
|
+
def initialize(parent, name, command_chain: [name], **opts, &config_blk)
|
8
8
|
@name = name
|
9
9
|
@command_chain = command_chain
|
10
|
-
super(
|
10
|
+
super(parent, name, **opts, &config_blk)
|
11
11
|
end
|
12
12
|
|
13
13
|
def default_prefix; '__command_'; end
|
@@ -16,6 +16,11 @@ module Luban
|
|
16
16
|
@action_method ||= "#{@prefix}#{command_chain.map(&:to_s).join('_').gsub(':', '_')}"
|
17
17
|
end
|
18
18
|
|
19
|
+
def command(cmd, **opts, &blk)
|
20
|
+
opts[:command_chain] = self.command_chain.clone.push(cmd)
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
19
24
|
protected
|
20
25
|
|
21
26
|
def compose_banner
|
data/lib/luban/cli/version.rb
CHANGED
data/lib/luban/cli.rb
CHANGED
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.
|
4
|
+
version: 0.4.0
|
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-
|
11
|
+
date: 2015-10-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -56,13 +56,13 @@ files:
|
|
56
56
|
- lib/luban/cli/application.rb
|
57
57
|
- lib/luban/cli/base.rb
|
58
58
|
- lib/luban/cli/base/argument.rb
|
59
|
+
- lib/luban/cli/base/commands.rb
|
59
60
|
- lib/luban/cli/base/core.rb
|
60
61
|
- lib/luban/cli/base/dsl.rb
|
61
62
|
- lib/luban/cli/base/option.rb
|
62
63
|
- lib/luban/cli/base/parse.rb
|
63
64
|
- lib/luban/cli/base/switch.rb
|
64
65
|
- lib/luban/cli/command.rb
|
65
|
-
- lib/luban/cli/commands.rb
|
66
66
|
- lib/luban/cli/error.rb
|
67
67
|
- lib/luban/cli/version.rb
|
68
68
|
- luban-cli.gemspec
|
data/lib/luban/cli/commands.rb
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
module Luban
|
2
|
-
module CLI
|
3
|
-
module Commands
|
4
|
-
def self.included(base)
|
5
|
-
base.extend(ClassMethods)
|
6
|
-
base.send(:include, InstanceMethods)
|
7
|
-
end
|
8
|
-
|
9
|
-
module CommonMethods
|
10
|
-
def list_commands
|
11
|
-
commands.keys
|
12
|
-
end
|
13
|
-
|
14
|
-
def has_command?(cmd)
|
15
|
-
commands.has_key?(cmd)
|
16
|
-
end
|
17
|
-
|
18
|
-
def has_commands?
|
19
|
-
!commands.empty?
|
20
|
-
end
|
21
|
-
|
22
|
-
def use_commands(mod_name)
|
23
|
-
mod_class = Kernel.const_get(camelcase(mod_name.to_s))
|
24
|
-
mod_class.constants.map { |c| mod_class.const_get(c) }.each do |c|
|
25
|
-
command(snakecase(c.name.sub(/Command$/, ''))) if c < Luban::CLI::Command
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
protected
|
30
|
-
|
31
|
-
def camelcase(str)
|
32
|
-
str = str.to_s.dup
|
33
|
-
str.gsub!(/\:(.?)/){ "::#{$1.upcase}" }
|
34
|
-
str.gsub!(/(?:_+|-+)([a-z])/){ $1.upcase }
|
35
|
-
str.gsub!(/(\A|\s)([a-z])/){ $1 + $2.upcase }
|
36
|
-
str
|
37
|
-
end
|
38
|
-
|
39
|
-
def snakecase(str)
|
40
|
-
str.gsub(/::/, ':').
|
41
|
-
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
42
|
-
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
43
|
-
tr("-", "_").
|
44
|
-
downcase
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
module ClassMethods
|
49
|
-
include CommonMethods
|
50
|
-
|
51
|
-
def commands
|
52
|
-
@commands ||= {}
|
53
|
-
end
|
54
|
-
|
55
|
-
def command_class(cmd)
|
56
|
-
"#{camelcase(cmd)}Command"
|
57
|
-
end
|
58
|
-
|
59
|
-
def command(app = self, cmd, **opts, &blk)
|
60
|
-
cmd = cmd.to_sym
|
61
|
-
cmd_class = command_class(cmd)
|
62
|
-
klass = if self.const_defined?(command_class(cmd))
|
63
|
-
self.const_get(command_class(cmd))
|
64
|
-
else
|
65
|
-
self.const_set(command_class(cmd), Class.new(Command))
|
66
|
-
end
|
67
|
-
commands[cmd] = klass.new(app, cmd, **opts, &blk)
|
68
|
-
end
|
69
|
-
|
70
|
-
def undef_command(cmd)
|
71
|
-
undef_method(commands.delete(cmd).action_method)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
module InstanceMethods
|
76
|
-
include CommonMethods
|
77
|
-
|
78
|
-
def commands
|
79
|
-
self.class.commands
|
80
|
-
end
|
81
|
-
|
82
|
-
def command(cmd, **opts, &blk)
|
83
|
-
cmd = cmd.to_sym
|
84
|
-
if self.is_a?(Command)
|
85
|
-
opts[:command_chain] = self.command_chain.clone.push(cmd)
|
86
|
-
self.class.command(self.app, cmd, **opts, &blk)
|
87
|
-
else
|
88
|
-
self.class.command(cmd, **opts, &blk)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def undef_command(cmd)
|
93
|
-
self.class.undef_command(cmd)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|