luban-cli 0.3.2 → 0.4.0

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: 5cefeb5c04a0122c4ddbcfe60ec89f10fa448986
4
- data.tar.gz: 63308ba45f262576aa6208d7017accbbefcc71fd
3
+ metadata.gz: c3717edf51bdf2888b4d7d7d378b8ba777e7f2df
4
+ data.tar.gz: 23c48793a99823629c9b339a7b65f64f3be9dc27
5
5
  SHA512:
6
- metadata.gz: c9783ade21c7ca9ae041262c692730fd3db7fe6cd8eaa880ff910568d57729a88688c5583edbb297b6996797d4d04deb88e7231551e9686e189d2339a0904539
7
- data.tar.gz: a3724c927f17da7c1933b69ccd82e06371c982040bf8e7840f6cf569049974373ec4961551afe7d49d0f405715da63748fa5ca206242bf989914117b6067500f
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(cmd:, argv:, args:, opts:)
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.new.run
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.new.run
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.new.run
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.new.run
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
- action do |**params|
283
- puts params.inspect
290
+ action do |**params|
291
+ puts params.inspect
292
+ end
284
293
  end
285
294
  end
286
295
 
287
- MyApp.new.run
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.new(auto_help: false)
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.new.run
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.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
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 should be defined under the the application class, otherwise NoMethodError will be raised.
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
- # Define a help command to list all commands or help for one command.
391
- auto_help_command
403
+ command :cmd1 do
404
+ desc 'Description for command 1'
392
405
 
393
- command :cmd1 do
394
- desc 'Description for command 1'
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
- command :task1 do
397
- desc 'Description for task 1'
398
- argument :arg1, 'Description for arg1', type: :string
399
- action :exec_command1_task1
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 :task2 do
403
- desc 'Description for task 2'
404
- option :opt1, 'Description for opt1', type: :integer
405
- action :exec_command1_task2
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
- command :cmd2 do
410
- desc 'Description for command 1'
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.new.run
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 class. Usually you don't need to call this method directly. Luban::CLI dispatches the specified command to the corresponding command method/handler properly.
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 Task1Command < Luban::CLI::Command
477
+ class Task1 < Luban::CLI::Command
462
478
  configure do
463
479
  ... ...
464
480
  end
465
481
  end
466
482
 
467
- class Task2Command < Luban::CLI::Command
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 instances, 'app:control_tasks'
519
+ * For instance, 'app:control_tasks'
504
520
  * Alternatively, commands can be injected individually
505
521
  * Command name should be fully qualified
506
- * Suffix "Command" is no more needed
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 validate; end
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 be callable."
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
@@ -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 :app
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(app, action_name, prefix: default_prefix, auto_help: true, &config_blk)
38
- @app = app
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 = block_given? ? blk : self.class.config_blk
85
- instance_eval(&config_blk) unless config_blk.nil?
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
- method = @action_method
90
- action do |**opts|
91
- raise NotImplementedError, "#{self.class.name}##{method} is an abstract method."
113
+ if has_commands?
114
+ action_noops
115
+ else
116
+ action_abort
92
117
  end
93
118
  end
94
119
 
95
- def dispatch_command(context, cmd:, argv:)
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
- cmd_method = commands[cmd].action_method
98
- if respond_to?(cmd_method)
99
- send(cmd_method, argv)
100
- else
101
- context.send(cmd_method, argv)
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, "Missing command. Expected command: #{list_commands.join(', ')}"
157
+ raise MissingCommand, "Please specify a command to execute."
108
158
  end
109
159
  unless has_command?(cmd)
110
- raise InvalidCommand, "Invalid command. Expected command: #{list_commands.join(', ')}"
160
+ raise InvalidCommand, "Invalid command: #{cmd}"
111
161
  end
112
162
  end
113
163
 
@@ -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 help_command(**opts, &blk)
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| send(method_name, **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, self, parse_method, argv) do |params|
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
- @app.send(method_creator, action_method, &action_blk)
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(context, parse_method, argv)
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(context, cmd: result[:cmd], argv: result[:argv])
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
@@ -4,3 +4,4 @@ require_relative "base/switch"
4
4
  require_relative "base/core"
5
5
  require_relative "base/parse"
6
6
  require_relative "base/dsl"
7
+ require_relative "base/commands"
@@ -4,10 +4,10 @@ module Luban
4
4
  attr_reader :name
5
5
  attr_reader :command_chain
6
6
 
7
- def initialize(app, name, command_chain: [name], **opts, &config_blk)
7
+ def initialize(parent, name, command_chain: [name], **opts, &config_blk)
8
8
  @name = name
9
9
  @command_chain = command_chain
10
- super(app, name, **opts, &config_blk)
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
@@ -1,5 +1,5 @@
1
1
  module Luban
2
2
  module CLI
3
- VERSION = "0.3.2"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
data/lib/luban/cli.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  require_relative "cli/error"
2
2
  require_relative "cli/version"
3
- require_relative "cli/commands"
4
3
  require_relative "cli/base"
5
4
  require_relative "cli/command"
6
5
  require_relative "cli/application"
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.2
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-04-28 00:00:00.000000000 Z
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
@@ -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