luban-cli 0.2.0 → 0.3.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 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