luban-cli 0.2.0 → 0.3.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: 275afb8e84ecc64947db6b5e147e37996a7d1df1
4
- data.tar.gz: 866526700250c04f22aab3467ba7ee9dbadc1b66
3
+ metadata.gz: ad584f0174c5361200ed88f252387ac294ff8768
4
+ data.tar.gz: 9af48be33bc18fa6d049780354d0a6dc3abb34fd
5
5
  SHA512:
6
- metadata.gz: eb7fcbd25cfe82c7bfcfb99755a1272de32bddf1e12061fedc28a95298e26e1f5d06525717b4341d9debb42dc742acc81f23d93087c477e77f68915f58129416
7
- data.tar.gz: 13c432783506cd550ec5a012f2b36e25420669af7f5851145323ccd021958f002982b39b2fa4f8285d56db3dfe99077a1937df93c869b517fbe4fe5d93c00238
6
+ metadata.gz: 932c919d74edc1e23cc4a45fe10240913603864aa25cf71a24b76e0a542da0a54296d20348fb5f7dc5c645b1b143e3557d897a6fbaf36df2ae88b5a0ba1bd400
7
+ data.tar.gz: 889e415204354c2c6db2440e1297fb52a85e4f59a0d662707c3bc404066eeb835c592b156fc3594bb35562c9db64f463504b2ab3bb1ebd97934de4a0e0b465b1
data/CHANGELOG.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Bootstrapped Luban::CLI
6
6
 
7
- Features:
7
+ New features:
8
8
  * Support general command-line parsing
9
9
  * Support options
10
10
  * Support switches (boolean options)
@@ -15,9 +15,27 @@ Features:
15
15
  ## Version 0.2.0 (Apr 02, 2015)
16
16
 
17
17
  Minor enhancements:
18
- * Refractor error class
19
- * Refractor argument validation
18
+ * Refactor error class
19
+ * Refactor argument validation
20
20
  * Validate required options/arguments in Luban::CLI::Base
21
21
  * Create singleton action handler method on application instance
22
22
  * Move parse error handling to action handler
23
23
  * Exclude examples and spec from the gem itself
24
+
25
+ ## Version 0.3.0 (Apr 11, 2015)
26
+
27
+ New features:
28
+ * Support prefix for action name
29
+ * By default, subcommand method is prefixed with "__command_"
30
+ * By default, application action method has no prefix
31
+ * Support nested subcommands
32
+ * Add help option by default in Luban::CLI::Base in order to turn on auto help by default
33
+
34
+ Minor enhancements:
35
+ * Refactor action creation to be more readable
36
+ * Add an example for subcommands and nested subcommands
37
+
38
+ Bug fixes:
39
+ * Apply the correct method creator when defining action method
40
+ * Dispatch command under the right class context
41
+ * Show correct command chain between program name and synopsis when composing parser banner
@@ -5,8 +5,8 @@ module Luban
5
5
  class Application < Base
6
6
  attr_reader :rc
7
7
 
8
- def initialize(starter_method = :run, &config_blk)
9
- super(self, starter_method, &config_blk)
8
+ def initialize(action_name = :run, **opts, &config_blk)
9
+ super(self, action_name, **opts, &config_blk)
10
10
  @rc = init_rc
11
11
  validate
12
12
  end
@@ -19,6 +19,9 @@ module Luban
19
19
  DefaultSummaryIndent = 4
20
20
  DefaultTitleIndent = 2
21
21
 
22
+ attr_reader :app
23
+ attr_reader :prefix
24
+ attr_reader :action_method
22
25
  attr_reader :program_name
23
26
  attr_reader :options
24
27
  attr_reader :arguments
@@ -32,9 +35,11 @@ module Luban
32
35
  attr_accessor :summary_width
33
36
  attr_accessor :summary_indent
34
37
 
35
- def initialize(app, starter_method, &config_blk)
38
+ def initialize(app, action_name, prefix: default_prefix, auto_help: true, &config_blk)
36
39
  @app = app
37
- @starter_method = starter_method
40
+ @action_name = action_name
41
+ @prefix = prefix
42
+ @action_method = "#{@prefix}#{@action_name}"
38
43
  @action_defined = false
39
44
 
40
45
  @program_name = default_program_name
@@ -51,9 +56,12 @@ module Luban
51
56
  @summary_indent = DefaultSummaryIndent
52
57
 
53
58
  configure(&config_blk)
54
- setup_default_starter unless @action_defined
59
+ setup_default_action unless @action_defined
60
+ self.auto_help if auto_help
55
61
  end
56
62
 
63
+ def default_prefix; ''; end
64
+
57
65
  def parser
58
66
  @parser ||= create_parser
59
67
  end
@@ -75,20 +83,16 @@ module Luban
75
83
  instance_eval(&config_blk) unless config_blk.nil?
76
84
  end
77
85
 
78
- def setup_default_starter
79
- method = @starter_method
80
- if has_commands?
81
- action :dispatch_command
82
- else
83
- action do |**opts|
84
- raise NotImplementedError, "#{self.class.name}##{method} is an abstract method."
85
- end
86
+ def setup_default_action
87
+ method = @action_method
88
+ action do |**opts|
89
+ raise NotImplementedError, "#{self.class.name}##{method} is an abstract method."
86
90
  end
87
91
  end
88
92
 
89
- def dispatch_command(cmd:, argv:, **params)
93
+ def dispatch_command(context, cmd:, argv:, **params)
90
94
  validate_command(cmd)
91
- send(cmd, argv)
95
+ context.send(commands[cmd].action_method, argv)
92
96
  end
93
97
 
94
98
  def validate_command(cmd)
@@ -8,15 +8,14 @@ module Luban
8
8
 
9
9
  def configure(&blk); @config_blk = blk; end
10
10
 
11
- def help_command(auto_help: true, &blk)
11
+ def help_command(**opts, &blk)
12
12
  if block_given?
13
- command(:help, &blk)
13
+ command(**opts, &blk)
14
14
  else
15
- command(:help) do
15
+ command(:help, **opts) do
16
16
  desc "List all commands or help for one command"
17
17
  argument :command, "Command to help for",
18
18
  type: :symbol, required: false
19
- self.auto_help if auto_help
20
19
  action :show_help_for_command
21
20
  end
22
21
  end
@@ -99,29 +98,45 @@ module Luban
99
98
  blk
100
99
  end
101
100
  if handler.nil?
102
- raise ArgumentError, "Code block to execute command #{@starter_method} is MISSING."
101
+ raise ArgumentError, "Code block to execute command #{@action_name} is MISSING."
103
102
  end
104
103
  _base = self
105
104
  parse_method = preserve_argv ? :parse : :parse!
106
- @app.define_singleton_method(@starter_method) do |argv=_base.default_argv|
107
- _base.send(parse_method, argv)
108
- begin
109
- if _base.result[:opts][:help]
110
- _base.show_help
111
- elsif _base.result[:opts][:version]
112
- _base.show_version
113
- else
114
- _base.validate_required_options
115
- _base.validate_required_arguments
116
- instance_exec(**_base.result, &handler)
117
- end
118
- rescue OptionParser::ParseError, Error => e
119
- _base.on_parse_error(e)
105
+ define_action_method do |argv = _base.default_argv|
106
+ _base.send(:process, self, parse_method, argv) do |result|
107
+ instance_exec(**result, &handler)
120
108
  end
121
109
  end
122
110
  @action_defined = true
123
111
  end
124
112
 
113
+ def define_action_method(&action_blk)
114
+ @app.send(method_creator, action_method, &action_blk)
115
+ end
116
+
117
+ def method_creator
118
+ @method_creator ||= @app.is_a?(Class) ? :define_method : :define_singleton_method
119
+ end
120
+
121
+ def process(context, parse_method, argv)
122
+ send(parse_method, argv)
123
+ if result[:opts][:help]
124
+ show_help
125
+ elsif result[:opts][:version]
126
+ show_version
127
+ else
128
+ if has_commands?
129
+ dispatch_command(context, **result)
130
+ else
131
+ validate_required_options
132
+ validate_required_arguments
133
+ yield result
134
+ end
135
+ end
136
+ rescue OptionParser::ParseError, Error => e
137
+ on_parse_error(e)
138
+ end
139
+
125
140
  def on_parse_error(error)
126
141
  show_error_and_exit(error)
127
142
  end
@@ -2,16 +2,20 @@ module Luban
2
2
  module CLI
3
3
  class Command < Base
4
4
  attr_reader :name
5
+ attr_reader :command_chain
5
6
 
6
- def initialize(app, name, &config_blk)
7
- super
7
+ def initialize(app, name, command_chain: [name], **opts, &config_blk)
8
8
  @name = name
9
+ @command_chain = command_chain
10
+ super(app, name, **opts, &config_blk)
9
11
  end
10
12
 
13
+ def default_prefix; '__command_'; end
14
+
11
15
  protected
12
16
 
13
17
  def compose_banner
14
- "Usage: #{program_name} #{name} #{compose_synopsis}"
18
+ "Usage: #{program_name} #{command_chain.map(&:to_s).join(' ')} #{compose_synopsis}"
15
19
  end
16
20
  end
17
21
  end
@@ -6,21 +6,7 @@ module Luban
6
6
  base.send(:include, InstanceMethods)
7
7
  end
8
8
 
9
- module ClassMethods
10
- def inherited(subclass)
11
- # Ensure commands from base class
12
- # got inherited to its subclasses
13
- subclass.instance_variable_set(
14
- '@commands',
15
- Marshal.load(Marshal.dump(instance_variable_get('@commands')))
16
- )
17
- super
18
- end
19
-
20
- def commands
21
- @commands ||= {}
22
- end
23
-
9
+ module CommonMethods
24
10
  def list_commands
25
11
  commands.keys
26
12
  end
@@ -32,32 +18,62 @@ module Luban
32
18
  def has_commands?
33
19
  !commands.empty?
34
20
  end
21
+ end
22
+
23
+ module ClassMethods
24
+ include CommonMethods
25
+
26
+ def commands
27
+ @commands ||= {}
28
+ end
35
29
 
36
- def command(cmd, &blk)
37
- commands[cmd] = Command.new(self, cmd, &blk)
30
+ def command_class(cmd)
31
+ "#{classify(cmd)}Command"
32
+ end
33
+
34
+ def command(app = self, cmd, **opts, &blk)
35
+ cmd_class = command_class(cmd)
36
+ klass = if self.const_defined?(command_class(cmd))
37
+ self.const_get(command_class(cmd))
38
+ else
39
+ self.const_set(command_class(cmd), Class.new(Command))
40
+ end
41
+ commands[cmd] = klass.new(app, cmd, **opts, &blk)
38
42
  end
39
43
 
40
44
  def undef_command(cmd)
41
- commands.delete(cmd)
42
- undef_method(cmd)
45
+ undef_method(commands.delete(cmd).action_method)
46
+ end
47
+
48
+ protected
49
+
50
+ def classify(cmd)
51
+ cmd = cmd.to_s.dup
52
+ cmd.gsub!(/\/(.?)/){ "::#{$1.upcase}" }
53
+ cmd.gsub!(/(?:_+|-+)([a-z])/){ $1.upcase }
54
+ cmd.gsub!(/(\A|\s)([a-z])/){ $1 + $2.upcase }
55
+ cmd
43
56
  end
44
57
  end
45
58
 
46
59
  module InstanceMethods
60
+ include CommonMethods
61
+
47
62
  def commands
48
63
  self.class.commands
49
64
  end
50
65
 
51
- def list_commands
52
- commands.keys
53
- end
54
-
55
- def has_command?(cmd)
56
- self.class.has_command?(cmd)
66
+ def command(cmd, **opts, &blk)
67
+ if self.is_a?(Command)
68
+ opts[:command_chain] = self.command_chain.clone.push(cmd)
69
+ self.class.command(self.app, cmd, **opts, &blk)
70
+ else
71
+ self.class.command(cmd, **opts, &blk)
72
+ end
57
73
  end
58
74
 
59
- def has_commands?
60
- self.class.has_commands?
75
+ def undef_command(cmd)
76
+ self.class.undef_command(cmd)
61
77
  end
62
78
  end
63
79
  end
@@ -1,5 +1,5 @@
1
1
  module Luban
2
2
  module CLI
3
- VERSION = "0.2.0"
3
+ VERSION = "0.3.0"
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.2.0
4
+ version: 0.3.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-02 00:00:00.000000000 Z
11
+ date: 2015-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler