dencli 0.5.2 → 0.5.6

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
  SHA256:
3
- metadata.gz: 8471e357d36b81e36d1ab99ecdf91ccd0b074e851b3ba8e85d84450943e2b476
4
- data.tar.gz: 04ed6b6810e16454b08e33e3006a7688ab4a63ce398a5acb7098918ea767bb58
3
+ metadata.gz: 567eaa6ae13836be53cfc492496ae089be944c0a352f55023ac413e4f051c8d4
4
+ data.tar.gz: 7c3cb0ff03f0a738b56e785e8d2f9da76db16d0afab5c29fd5a53881476968cd
5
5
  SHA512:
6
- metadata.gz: 6a753968bd74a46255be188e1f75097d14962df24affdba2581cf01c3aeaefe37fe917dd1bbb7ea658ced37dd2f4f469223f3661d0e02d604cab6790026f08d7
7
- data.tar.gz: d5790f9ac588beb6c7fb64a223634cff15ee6b686f549923d265a875533a5ff881ed4c0130895db1216d53906b2c30e8e8fc434326dd8467d1d1745b14207944
6
+ metadata.gz: d2bc0b876cc7eb05a8a778264c76c2721bc8328199042ae551793c4f744b4248845f2390558718cae07dd4da215b5d166caa391c0c503c7a65550549fe3e78a1
7
+ data.tar.gz: 634d5a0a2d0037b2c81e4b1d7b922413beb760b016b961560bd1bc8bfb85e5d34ed08e5947e38068ae6074f3f79f96cc5ab298d8c53b384d7a9adf6e809bb714
data/bin/example.rb CHANGED
@@ -1,34 +1,125 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'pathname'
4
- $:.unshift Pathname.new(__FILE__).dirname.dirname.join('lib').to_s
5
- require 'dencli'
4
+ require 'shellwords'
5
+ require 'stringio'
6
+ require 'ipaddr'
7
+ require_relative '../lib/dencli'
6
8
 
7
9
  cli = DenCli.new :example, "This is an example for generate a DenCli-API"
10
+
11
+ class Capture
12
+ def initialize cli, verbose:
13
+ @cli, @counter, @verbose = cli, 0, verbose
14
+ end
15
+
16
+ def capture
17
+ @args = NilClass
18
+ @counter += 1
19
+ stdout, stderr = $stdout, $stderr
20
+ $stdout = $stderr = StringIO.new
21
+ begin
22
+ yield stdout, stderr
23
+ ensure
24
+ $stderr, $stdout = stderr, stdout
25
+ end
26
+ end
27
+
28
+ def args= args
29
+ @args = args
30
+ end
31
+
32
+ def logstart command
33
+ STDERR.printf "[% 4d] \e[1;35m? \e[0m %s tests %s\r", @counter, $0.shellescape, command.shelljoin
34
+ end
35
+
36
+ def logok info, command
37
+ STDERR.printf "[% 4d] \e[1;32mok\e[0m %s | %s tests %s\e[J\n", @counter, info, $0.shellescape, command.shelljoin
38
+ end
39
+
40
+ def logfail command
41
+ STDERR.printf "[% 4d] \e[1;31mer\e[0m %s tests %s\e[J\n", @counter, $0.shellescape, command.shelljoin
42
+ end
43
+
44
+ def logexception prefix, exception
45
+ loginfo "#{prefix} (#{exception.class.name}) #{exception}"
46
+ exception.backtrace[0...-Kernel.caller.length].each {|l| loginfo " #{l}" }
47
+ end
48
+
49
+ def loginfo text
50
+ STDERR.printf " %s\n", text
51
+ end
52
+
53
+ def should_ok expect, *command
54
+ logstart command
55
+ $capture.capture { @cli.call 'tests', *command }
56
+ if expect === @args
57
+ logok @args, command
58
+ else
59
+ logfail command
60
+ loginfo "expected args: #{expect.inspect}"
61
+ loginfo "given args: #{@args.inspect}"
62
+ STDERR.puts
63
+ end
64
+ rescue SystemExit
65
+ if 0 == $!.status
66
+ logok @args, command
67
+ else
68
+ logfail command
69
+ end
70
+ rescue Object
71
+ logfail command
72
+ logexception "unexpected raise:", $!
73
+ STDERR.puts
74
+ end
75
+
76
+ def should_fail exception, message, *command
77
+ logstart command
78
+ $capture.capture { @cli.call 'tests', *command }
79
+ logfail command
80
+ rescue exception
81
+ if message === $!.message
82
+ logok exception, command
83
+ if @verbose
84
+ logexception "raised:", $!
85
+ STDERR.puts
86
+ end
87
+ else
88
+ logexception "unexpected message:", $!
89
+ STDERR.puts
90
+ end
91
+ rescue Object
92
+ logfail command
93
+ logexception "unexpected raised:", $!
94
+ STDERR.puts
95
+ end
96
+ end
97
+
8
98
  cli.cmd( :args, "Expects and prints given arguments",
9
- &lambda {|a, b, c:, d:, e:|
99
+ &lambda {|a, b, c:, d:, e:, f:, g:|
10
100
  p a: a, b: b, c: c, d: d, e: e
11
101
  }).
12
102
  opt( :c, '-c=ForC', "Option c").
13
- opt( :d, '-d=ForD', "Option d", default: "something").
103
+ opt( :d, '-dForD', "Option d", default: "something").
14
104
  opt( :e, '-e', "Toggle e", default: false).
15
- opt( :f, '--[no-]f', "Toggle f", default: false)
105
+ opt( :f, '--[no-]f', "Toggle f", default: false).
106
+ opt( :g, '--long-option=sth', "Long option, no short option", default: "nothing").
107
+ opt( :h, '-hsth', "No long option, only short option", default: "nothing")
16
108
 
17
- cli.cmd( :example, "I have an example command") { STDERR.puts "This is an example" }
18
- cli.cmd( :help, "", aliases: [nil, '-h', '--help'], &lambda {|*args, full:|
109
+ cli.cmd( :example, "I have an example command") { $stderr.puts "This is an example" }
110
+ cli.cmd( :help, "An example for help", aliases: [nil, '-h', '--help'], &lambda {|*commands, full:|
19
111
  if full
20
- cli.help_full *args, output: STDERR
112
+ cli.help_full *commands, output: $stderr
21
113
  else
22
- cli.help *args, output: STDERR
114
+ cli.help *commands, output: $stderr
23
115
  end
24
116
  }).
25
117
  opt( :full, '-f', '--[no-]full', "Print all commands and sub-commands.", default: false)
26
118
 
27
119
  cli.sub( :more, "Sub-Commands are also possible with a new cli") do |sub|
28
- sub.cmd( :help, "", aliases: [nil, '-h', '--help']) {|*args| STDERR.puts sub.help(*args) }
29
- sub.cmd( :help, "") {|*args| STDERR.puts cli.help( 'more', *args) }
30
- sub.cmd( :example, "Here is an example, too") { STDERR.puts "This is an other example" }
31
- sub.cmd( :foo, "BAR") { STDERR.puts "FOO bar"}
120
+ sub.cmd( :help, "", aliases: [nil, '-h', '--help']) {|*args| $stderr.puts sub.help(*args) }
121
+ sub.cmd( :example, "Here is an example, too") { $stderr.puts "This is an other example" }
122
+ sub.cmd( :foo, "BAR") { $stderr.puts "FOO bar"}
32
123
 
33
124
  sub.cmd( :args, "Expects and prints given arguments", &lambda {|a, b=1, c:, d: 5, e:|
34
125
  p a: a, b: b, c: c, d: d, e: e
@@ -38,19 +129,137 @@ cli.sub( :more, "Sub-Commands are also possible with a new cli") do |sub|
38
129
  opt( :e, '-e', "Toggle e")
39
130
 
40
131
  sub.sub( :deeper, "You want to have Sub-Sub-Commands?") do |sub2|
41
- sub2.cmd( :help, "", aliases: [nil, '-h', '--help'], &lambda {|*args| sub2.help( *args, output: STDERR) })
42
- sub2.cmd( :last, "The last example", &lambda { STDERR.puts "The last example" })
132
+ sub2.cmd( :help, "", aliases: [nil, '-h', '--help'], &lambda {|*commands| sub2.help( *commands, output: $stderr) })
133
+ sub2.cmd( :last, "The last example", &lambda { $stderr.puts "The last example" })
134
+
135
+ sub2.sub( :'sub-commands', "Endless Sub-Sub- ... with a special alias") do |sub3|
136
+ # h -> help
137
+ # he -> hehe
138
+ # hel -> help
139
+ # help -> help
140
+ # heh -> hehe
141
+ # hehe -> hehe
142
+ sub3.cmd( :help, "", min: 3, aliases: [nil, :h]) {|*args| $stderr.puts sub3.help( *args) }
143
+ sub3.cmd( :hehe, "The real last example", min: 2) { $stderr.puts "Trust me!" }
144
+ end
145
+ end
146
+ end
43
147
 
44
- sub2.sub( :'sub-commands', "Endless Sub-Sub- ...") do |sub3|
45
- sub3.cmd( :help, "") {|*args| STDERR.puts sub3.help( sub3, *args) }
46
- sub3.cmd( :hehe, "The real last example", min: 2) { STDERR.puts "Trust me!" }
148
+ cli.cmd( :cli, "Interactive shell", min: 3, &lambda {||
149
+ cli.interactive( File.basename($0, '.rb')).run
150
+ })
151
+
152
+ cli.sub :tests, "Some tests", noshortaliases: true do |tcli|
153
+ tcli.cmd( :help, "", min: 4) {|*args| $stderr.puts tcli.help( *args) }
154
+ OptionParser.accept IPAddr do |arg|
155
+ begin
156
+ IPAddr.new arg
157
+ rescue IPAddr::InvalidAddressError
158
+ raise OptionParser::InvalidArgument, "#{$!.message}: #{arg}"
47
159
  end
48
160
  end
161
+
162
+ tcli.cmd( :'-', "No arguments no options expected", &lambda {|| $capture.args = [] })
163
+ tcli.cmd( :'arg', "", &lambda {|one| $capture.args = [one] })
164
+ tcli.cmd( :'oar', "", &lambda {|one=nil| $capture.args = [one] })
165
+ tcli.cmd( :'arg-arg', "", &lambda {|one, two| $capture.args = [one, two] })
166
+ tcli.cmd( :'oar-oar', "", &lambda {|one=nil, two=nil| $capture.args = [one, two] })
167
+ tcli.cmd( :'arg-oar', "expected", &lambda {|one, two=nil| $capture.args = [one, two] })
168
+ tcli.cmd( :'oar-arg', "expected", &lambda {|one=nil, two| $capture.args = [one, two] })
169
+ tcli.cmd( :'bool', '', &lambda {|a:| $capture.args = [a] }).opt(:a, '-a', '')
170
+ tcli.cmd( :'optbool', '', &lambda {|a: nil| $capture.args = [a] }).opt(:a, '-a', '')
171
+ tcli.cmd( :'defbool', '', &lambda {|a:| $capture.args = [a] }).opt(:a, '-a', '', default: 'default')
172
+ tcli.cmd( :'str', '', &lambda {|a:| $capture.args = [a] }).opt(:a, '-a=STR', '')
173
+ tcli.cmd( :'lstr', '', &lambda {|a:| $capture.args = [a] }).opt(:a, '--astr=STR', '')
174
+ tcli.cmd( :'bstr', '', &lambda {|a:| $capture.args = [a] }).opt(:a, '-a', '--astr=STR', '')
175
+ tcli.cmd( :'ipaddr', '', &lambda {|a:| $capture&.args = [a] }).opt(:a, '-a', '--addr=ADDR', IPAddr, '')
176
+
177
+ tcli.cmd( :run, "Run all tests") do |verbose:|
178
+ $capture = Capture.new cli, verbose: verbose
179
+
180
+ $capture.should_fail DenCli::UnknownCommand, //, 'unknown-command'
181
+
182
+ $capture.should_ok [], '-'
183
+ $capture.should_fail DenCli::UsageError, //, '-', 'unexpected'
184
+
185
+ $capture.should_ok %w[first], 'arg', 'first'
186
+ $capture.should_fail DenCli::UsageError, //, 'arg'
187
+ $capture.should_fail DenCli::UsageError, //, 'arg', 'first', 'unexpected'
188
+
189
+ $capture.should_ok %w[first], 'oar', 'first'
190
+ $capture.should_ok [nil], 'oar'
191
+ $capture.should_fail DenCli::UsageError, //, 'oar', 'first', 'unexpected'
192
+
193
+ $capture.should_ok %w[first two], 'oar-oar', 'first', 'two'
194
+ $capture.should_ok ['first', nil], 'oar-oar', 'first'
195
+ $capture.should_ok [nil,nil], 'oar-oar'
196
+ $capture.should_fail DenCli::UsageError, //, 'oar-oar', 'first', 'two', 'unexpected'
197
+
198
+ $capture.should_ok %w[first two], 'arg-oar', 'first', 'two'
199
+ $capture.should_ok ['first', nil], 'arg-oar', 'first'
200
+ $capture.should_fail DenCli::UsageError, //, 'arg-oar'
201
+
202
+ $capture.should_ok [nil, 'first'], 'oar-arg', 'first'
203
+ $capture.should_ok ['first', 'second'], 'oar-arg', 'first', 'second'
204
+ $capture.should_fail DenCli::UsageError, //, 'oar-arg'
205
+ $capture.should_fail DenCli::UsageError, //, 'oar-arg', 'first', 'two', 'unexpected'
206
+
207
+ $capture.should_ok [true], 'bool', '-a'
208
+ $capture.should_fail DenCli::UsageError, //, 'bool'
209
+ $capture.should_fail DenCli::UsageError, //, 'bool', '-a', 'unexpected'
210
+ $capture.should_fail OptionParser::InvalidOption, //, 'bool', '-b'
211
+ $capture.should_fail OptionParser::InvalidOption, //, 'bool', '--unexpected'
212
+ $capture.should_fail DenCli::UsageError, //, 'bool', 'unexpected'
213
+
214
+ $capture.should_ok [true], 'optbool', '-a'
215
+ $capture.should_ok [nil], 'optbool'
216
+ $capture.should_fail DenCli::UsageError, //, 'optbool', '-a', 'unexpected'
217
+ $capture.should_fail OptionParser::InvalidOption, //, 'optbool', '-b'
218
+ $capture.should_fail OptionParser::InvalidOption, //, 'optbool', '--unexpected'
219
+ $capture.should_fail DenCli::UsageError, //, 'optbool', 'unexpected'
220
+
221
+ $capture.should_ok [true], 'defbool', '-a'
222
+ $capture.should_ok ['default'], 'defbool'
223
+ $capture.should_fail DenCli::UsageError, //, 'defbool', '-a', 'unexpected'
224
+ $capture.should_fail OptionParser::InvalidOption, //, 'defbool', '-b'
225
+ $capture.should_fail OptionParser::InvalidOption, //, 'defbool', '--unexpected'
226
+ $capture.should_fail DenCli::UsageError, //, 'defbool', 'unexpected'
227
+
228
+ $capture.should_ok %w[first], 'str', '-a', 'first'
229
+ $capture.should_ok %w[first], 'str', '-afirst'
230
+ $capture.should_fail OptionParser::MissingArgument, //, 'str', '-a'
231
+ $capture.should_fail DenCli::UsageError, //, 'str'
232
+ $capture.should_fail OptionParser::InvalidOption, //, 'str', '-b'
233
+ $capture.should_fail OptionParser::InvalidOption, //, 'str', '--unexpected'
234
+ $capture.should_fail DenCli::UsageError, //, 'str', 'unexpected'
235
+
236
+ $capture.should_ok %w[first], 'lstr', '--astr', 'first'
237
+ $capture.should_ok %w[first], 'lstr', '--astr=first'
238
+ $capture.should_fail OptionParser::MissingArgument, //, 'lstr', '--astr'
239
+ $capture.should_fail DenCli::UsageError, //, 'lstr'
240
+ $capture.should_fail OptionParser::InvalidOption, //, 'lstr', '-b'
241
+ $capture.should_fail OptionParser::InvalidOption, //, 'lstr', '--unexpected'
242
+ $capture.should_fail DenCli::UsageError, //, 'lstr', 'unexpected'
243
+
244
+ $capture.should_ok %w[first], 'bstr', '-a', 'first'
245
+ $capture.should_ok %w[first], 'bstr', '-afirst'
246
+ $capture.should_ok %w[first], 'bstr', '--astr', 'first'
247
+ $capture.should_ok %w[first], 'bstr', '--astr=first'
248
+ $capture.should_fail OptionParser::MissingArgument, //, 'bstr', '--astr'
249
+ $capture.should_fail OptionParser::MissingArgument, //, 'bstr', '-a'
250
+ $capture.should_fail DenCli::UsageError, //, 'bstr'
251
+ $capture.should_fail OptionParser::InvalidOption, //, 'bstr', '-b'
252
+ $capture.should_fail OptionParser::InvalidOption, //, 'bstr', '--unexpected'
253
+ $capture.should_fail DenCli::UsageError, //, 'bstr', 'unexpected'
254
+
255
+ $capture.should_ok [IPAddr.new('1.2.3.4')], 'ipaddr', '-a', '1.2.3.4'
256
+ $capture.should_fail OptionParser::InvalidArgument, /invalid address/, 'ipaddr', '-a', '1.2.3.400'
257
+ end.opt( :verbose, '-v', 'Prints additional information per test', default: false)
49
258
  end
50
259
 
51
260
  begin
52
261
  cli.call *ARGV
53
262
  rescue DenCli::UsageError
54
- STDERR.puts $!
263
+ $stderr.puts $!
55
264
  exit 1
56
265
  end
data/lib/dencli/cmd.rb CHANGED
@@ -2,11 +2,11 @@ require_relative '../dencli'
2
2
 
3
3
 
4
4
  class DenCli::CMD
5
- attr_reader :parent, :name, :description, :exe, :completion, :options
5
+ attr_reader :parent, :name, :description, :exe, :completion, :options, :defined_in
6
6
 
7
- def initialize parent, name, description, exe
7
+ def initialize parent, name, description, exe, defined_in
8
8
  raise "Proc expected, instead of: #{exe.inspect}" unless Proc === exe
9
- @parent, @name, @description, @exe = parent, name, description, lambda( &exe)
9
+ @parent, @name, @description, @exe, @defined_in = parent, name, description, lambda( &exe), defined_in
10
10
  @parameters = @exe.parameters
11
11
  @arguments_required = @exe.parameters.select {|e| :req == e[0] }.map {|e| e[1] }
12
12
  @arguments_additional = @exe.parameters.select {|e| :opt == e[0] }.map {|e| e[1] }
@@ -60,7 +60,11 @@ class DenCli::CMD
60
60
  raise DenCli::UsageError, "Missing argument(s): #{kr.map {|_, o| o.long || o.short }.join ', '}"
61
61
  end
62
62
  end
63
- @exe.call *as, **os
63
+ if os.empty?
64
+ @exe.call *as
65
+ else
66
+ @exe.call *as, **os
67
+ end
64
68
  end
65
69
 
66
70
  def usage output: nil
@@ -72,14 +76,22 @@ class DenCli::CMD
72
76
  def _usage output
73
77
  output << full_cmd.join( ' ')
74
78
  @options.each do |_, o|
75
- s = "#{o.short||o.long}#{o.val ? ?= : ''}#{o.val}"
79
+ s = "#{o.short||o.long}#{(!o.short && o.val) ? ?= : ''}#{o.val}"
76
80
  output << (o.required? ? " #{s}" : " [#{s}]")
77
81
  end
78
82
  if @exe.lambda?
79
- required.each {|s| output << " <#{s}>" }
80
- output << " [#{additional.map{|s|"<#{s}>"}.join " "}]" unless additional.empty?
83
+ parameters.each do |(type, name)|
84
+ case type
85
+ when :req
86
+ output << " <#{name}>"
87
+ when :opt
88
+ output << " [<#{name}>]"
89
+ when :rest
90
+ output << " [<#{name}> ...]"
91
+ end
92
+ end
81
93
  else
82
- output << ' ...'
94
+ output << ' [...]'
83
95
  end
84
96
  end
85
97
 
@@ -130,7 +142,7 @@ class DenCli::CMD
130
142
  @options.map do |_, o|
131
143
  s, l, v, y = o.short, o.long, o.val, ','
132
144
  if l.nil?
133
- s += "=#{v}" if v
145
+ s += "#{v}" if v
134
146
  y = ' '
135
147
  elsif s.nil?
136
148
  l += "=#{v}" if v
@@ -148,22 +160,22 @@ class DenCli::CMD
148
160
  end
149
161
 
150
162
  class Opt
151
- attr_reader :name, :long, :short, :val, :desc, :os, :conv, :req
163
+ attr_reader :name, :long, :short, :type, :val, :desc, :conv, :req
152
164
  def required?() @req end
153
165
  def default?() NilClass != @default end
154
166
  def default() NilClass == @default ? nil : @default end
155
167
 
156
168
  def parse_opt_string opt
157
169
  case opt
158
- when /\A(--\[no-\][^=]+)\z/
170
+ when /\A(--\[no-\][^= ]+)\z/
159
171
  @long, @val = $1, nil
160
- when /\A(--[^=]+)=(.+)\z/
172
+ when /\A(--[^= ]+)[= ](.+)\z/
161
173
  @long, @val = $1, $2 || @val
162
- when /\A(--[^=]+)\z/
174
+ when /\A(--[^= ]+)\z/
163
175
  @long, @val = $1, nil
164
- when /\A(-[^=-]+)=(.+)\z/
176
+ when /\A(-[^= -])[= ]?(.+)\z/
165
177
  @short, @val = $1, $2 || @val
166
- when /\A(-[^=-]+)\z/
178
+ when /\A(-[^= -])\z/
167
179
  @short, @val = $1, nil
168
180
  else
169
181
  raise ArgumentError, "Unexpected format for option: #{opt.inspect}"
@@ -171,11 +183,12 @@ class DenCli::CMD
171
183
  end
172
184
  private :parse_opt_string
173
185
 
174
- def initialize cmd, name, opt, *alts, desc, default: NilClass, **os, &conv
175
- @name, @desc, @default, @os, @conv, @val =
176
- name.to_s.to_sym, desc, default, os, conv || lambda{|v|v}, nil
186
+ def initialize cmd, name, opt, *args, desc, default: NilClass, &conv
187
+ @name, @desc, @default, @conv, @val, @type =
188
+ name.to_s.to_sym, desc, default, conv || lambda{|v|v}, nil, nil
177
189
  parse_opt_string opt
178
- alts.each &method( :parse_opt_string)
190
+ @type = args.pop if OptionParser.top.atype.has_key? args.last
191
+ args.each &method( :parse_opt_string)
179
192
  @req =
180
193
  if NilClass != default
181
194
  false
@@ -188,30 +201,32 @@ class DenCli::CMD
188
201
 
189
202
  def on parser, store
190
203
  store[@name] = @default if default?
191
- parser.on "#{@short}#{@val ? ?= : ''}#{@val}", "#{@long}#{@val ? ?= : ''}#{@val}", **@os do |val|
204
+ short = "#{@short}#{@val ? ?= : ''}#{@val}"
205
+ long = "#{@long}#{@val ? ?= : ''}#{@val}"
206
+ parser.on short, long, *[@type].compact do |val|
192
207
  store[@name] = @conv[val]
193
208
  end
194
209
  end
195
210
 
196
211
  def inspect
197
- "#<%s:0x%016x %s %s %s %s (%p) %p os=%p conv=%s>" % [
212
+ "#<%s:0x%x %s %s %s %s (%p) %p conv=%s>" % [
198
213
  self.class.name, object_id, @req ? "<#{@name}>" : "[#{@name}]",
199
- @short, @long, @val, @default, @desc, @os,
214
+ @short, @long, @val, @default, @desc, @type ? " type=#{type}" : '',
200
215
  @exe ? "<#{@exe.lambda? ? :lambda: :proc} ##{@exe.arity}>" : "nil"
201
216
  ]
202
217
  end
203
218
  end
204
219
 
205
- def opt name, opt, *alts, desc, **os, &conv
206
- r = Opt.new( self, name, opt, *alts, desc, **os, &conv)
220
+ def opt name, opt, *args, desc, default: NilClass, &conv
221
+ r = Opt.new( self, name, opt, *args, desc, default: default, &conv)
207
222
  @options[r.name] = r
208
223
  self
209
224
  end
210
225
 
211
226
  def inspect
212
- "#<%s:0x%x %s @name=%p @description=%p @parent=<%s:0x%x %s> @exe=<arity=%d>>" % [
227
+ "#<%s:0x%x %s @name=%p @description=%p @options=%p @parent=<%s:0x%x %s> @exe=<arity=%d>>" % [
213
228
  self.class.name, self.object_id, self.full_cmd,
214
- @name, @description, @parent.class.name, @parent.class.object_id, @parent.full_cmd,
229
+ @name, @description, @options.values, @parent.class.name, @parent.class.object_id, @parent.full_cmd,
215
230
  @exe.arity
216
231
  ]
217
232
  end
@@ -17,12 +17,15 @@ class DenCli::Interactive
17
17
  end
18
18
  read_history if @histfile
19
19
 
20
- Readline.vi_editing_mode rescue NotImplementedError
20
+ begin
21
+ Readline.vi_editing_mode
22
+ rescue NotImplementedError
23
+ end
21
24
  Readline.completion_append_character = " "
22
25
  Readline.completion_proc = method :complete
23
26
 
24
27
  prepare_sub cl.subs
25
- cl.cmd :exit, "exit", min: 2 do
28
+ cl.cmd :exit, "exit", min: cl.has?(:ex) ? cl.has?(:exi) ? 4 : 3 : 2 do
26
29
  exit 0
27
30
  end
28
31
  cl.subs.aliases['?'] = cl.subs.subs['help']
@@ -90,14 +93,16 @@ class DenCli::Interactive
90
93
  c.subs.values.each do |n|
91
94
  case n
92
95
  when DenCli::Sub
93
- n.cmd :exit, "<- #{n.parent.full_cmd.join ' '} - #{n.parent.description[3..-1]}", min: 2 do
94
- @cur = n.parent
95
- end
96
+ n.cmd :exit,
97
+ "<- #{n.parent.full_cmd.join ' '} - #{n.parent.description[3..-1]}",
98
+ min: n.has?(:ex) ? n.has?( :exi) ? 4 : 3 : 2,
99
+ &lambda {|| @cur = n.parent }
100
+ n.aliases.delete nil
101
+ n.subs.delete nil
96
102
  n.cmd '', "", min: 2, aliases: [nil] do
97
103
  @cur = n
98
104
  end
99
- n.subs.delete ''
100
- n.aliases['?'] = n.subs['help']
105
+ n.aliases['?'] = n[:help] if n.has? :help and not n.has? '?'
101
106
  prepare_sub n
102
107
  when DenCli::CMD
103
108
  else raise "Unsupported sub-type: #{x}"
@@ -121,7 +126,7 @@ class DenCli::Interactive
121
126
  begin
122
127
  cur.call *line
123
128
  rescue ::DenCli::UsageError
124
- STDERR.puts "! #$!"
129
+ $stderr.puts "! #$!"
125
130
  end
126
131
  true
127
132
  end
data/lib/dencli/sub.rb CHANGED
@@ -1,21 +1,35 @@
1
1
  require_relative '../dencli'
2
2
 
3
3
  class DenCli::Sub
4
- attr_reader :parent, :name, :description, :subs, :aliases
4
+ attr_reader :parent, :name, :description, :subs, :aliases, :defined_in
5
5
 
6
- def initialize parent, name, description
6
+ def initialize parent, name, description, noshortaliases: nil, defined_in: nil
7
7
  #DenCli::assert_type self, __method__, :name, name, Symbol
8
8
  #DenCli::assert_type self, __method__, :parent, parent, DenCli, DenCli::Sub
9
9
  #DenCli::assert_type self, __method__, :description, description, String
10
10
  @parent, @name, @description, @subs, @aliases = parent, name, "-> #{description}", {}, {}
11
+ @noshortaliases, @defined_in = ! ! noshortaliases, defined_in || Kernel.caller
11
12
  end
12
13
 
13
14
  def _full_cmd( post) parent._full_cmd [@name]+post end
14
15
  def full_cmd() _full_cmd [] end
15
- def []( k) @aliases[k] end
16
+ def []( name) @aliases[name&.to_s] end
17
+ def has?( name) @aliases.has_key? name&.to_s end
16
18
 
17
- def usage
18
- "#{full_cmd.join ' '} ..."
19
+
20
+ def usage output: nil
21
+ output ||= ''
22
+ _usage output
23
+ output
24
+ end
25
+
26
+ def _usage output
27
+ output << full_cmd.join( ' ')
28
+ if @aliases.has_key? nil
29
+ output << " [<command> ...]"
30
+ else
31
+ output << " <command> [...]"
32
+ end
19
33
  end
20
34
 
21
35
  def help n = nil, *a, output: nil
@@ -28,8 +42,8 @@ class DenCli::Sub
28
42
  if n.nil?
29
43
  output << "#{full_cmd.join ' '}: #{description}\n\n"
30
44
  self.class._help_commands output, @subs
31
- elsif @aliases.has_key? n
32
- @aliases[n]._help output, *a
45
+ elsif has? n
46
+ self[n]._help output, *a
33
47
  else
34
48
  raise DenCli::UnknownCommand, "unknown command: #{_full_cmd( [n])[1..-1].join ' '}, available for #{full_cmd[1..-1].join' '}: #{@subs.keys.join ' '}"
35
49
  end
@@ -46,44 +60,46 @@ class DenCli::Sub
46
60
  output
47
61
  end
48
62
 
49
- def self._help_commands output, subs
50
- m = subs.map {|_name,c| x = c.usage.length; 25 < x ? 0 : x }.max
51
- subs.each do |_name, c|
52
- if 25 < c.usage.length
53
- output << "% -#{m}s\n#{' ' * m} " % [c.usage]
54
- else
55
- output << "% -#{m}s " % [c.usage]
56
- end
57
- n = m+2
58
- prefix = nil
59
- c.description.split /\n/ do |l|
60
- c = 0
61
- l.split %r< > do |w|
62
- if prefix
63
- output << prefix
64
- prefix = nil
65
- end
66
- wl = w.length
67
- if 75 < c+wl
68
- output << "\n#{' ' * n}#{w}"
69
- c = n+2+wl
70
- else
71
- output << " #{w}"
72
- c += 1 + wl
63
+ class <<self
64
+ def _help_commands output, subs
65
+ m = subs.map {|_name,c| x = c.usage.length; 25 < x ? 0 : x }.max
66
+ subs.each do |_name, c|
67
+ if 25 < c.usage.length
68
+ output << "% -#{m}s\n#{' ' * m} " % [c.usage]
69
+ else
70
+ output << "% -#{m}s " % [c.usage]
71
+ end
72
+ n = m+2
73
+ prefix = nil
74
+ c.description.split( /\n/).each do |l|
75
+ c = 0
76
+ l.split( %r< >).each do |w|
77
+ if prefix
78
+ output << prefix
79
+ prefix = nil
80
+ end
81
+ wl = w.length
82
+ if 75 < c+wl
83
+ output << "\n#{' ' * n}#{w}"
84
+ c = n+2+wl
85
+ else
86
+ output << " #{w}"
87
+ c += 1 + wl
88
+ end
73
89
  end
90
+ prefix = "\n#{' ' * n}"
74
91
  end
75
- prefix = "\n#{' ' * n}"
92
+ output << "\n"
76
93
  end
77
- output << "\n"
94
+ output
78
95
  end
79
- output
80
96
  end
81
97
 
82
98
  def goto *a
83
99
  return self if a.empty?
84
100
  n, *a = *a
85
- if @aliases.has_key? n
86
- @aliases[n].goto *a
101
+ if has? n
102
+ self[n].goto *a
87
103
  else
88
104
  raise DenCli::UnknownCommand, "unknown command: #{_full_cmd( [n])[1..-1].join ' '}, available for #{full_cmd[1..-1].join' '}: #{@subs.keys.join ' '}"
89
105
  end
@@ -91,35 +107,85 @@ class DenCli::Sub
91
107
 
92
108
  def call *a
93
109
  n, *a = *a
94
- if @aliases.has_key? n
95
- @aliases[n].call *a
110
+ if has? n
111
+ self[n].call *a
96
112
  else
97
113
  raise DenCli::UnknownCommand, "unknown command: #{_full_cmd( [n])[1..-1].join ' '}, available for #{full_cmd[1..-1].join' '}: #{@subs.keys.join ' '}"
98
114
  end
99
115
  end
100
116
 
101
117
  def _add name, min, obj, aliases
102
- #DenCli::assert_type self, __method__, :name, name, Symbol, NilClass
118
+ #DenCli::assert_type self, __method__, :name, name, Symbol, String
103
119
  #DenCli::assert_type self, __method__, :min, min, Integer, NilClass
104
120
  #DenCli::assert_type self, __method__, :obj, obj, DenCli::Sub, DenCli::CMD
105
121
  #DenCli::assert_type self, __method__, :aliases, aliases, Array, NilClass
106
- name = name.to_s unless name.nil?
122
+ name = name.to_s
107
123
  @subs[name] = obj
108
- DenCli.gen_aliases( name, min) {|a| @aliases[a] = obj }
124
+ if @noshortaliases
125
+ warn "Command/Alias for #{obj.full_cmd} defined in #{obj.defined_in} already exists: #{full_cmd.join ' '} #{name}. Used by #{@aliases[name].full_cmd} defined in #{@aliases[name].defined_in}" if @aliases.has_key? name
126
+ @aliases[name] = obj
127
+ else
128
+ DenCli.gen_aliases name, min do |a|
129
+ warn "Command/Alias for #{obj.full_cmd} defined in #{obj.defined_in} already exists: #{full_cmd.join ' '} #{a}. Used by #{@aliases[a].full_cmd} defined in #{@aliases[a].defined_in}" if @aliases.has_key? a
130
+ @aliases[a] ||= obj
131
+ end
132
+ end
109
133
  if aliases
110
- [*aliases].each {|a| @aliases[a] = obj }
134
+ [*aliases].each do |a|
135
+ a = a&.to_s
136
+ raise ArgumentError, "Alias for #{obj.full_cmd} defined in #{obj.defined_in} already exists: #{full_cmd.join ' '} #{a}. Used by #{@aliases[a].full_cmd} defined in #{@aliases[a].defined_in}" if @aliases.has_key? a
137
+ @aliases[a] = obj
138
+ end
111
139
  end
112
140
  obj
113
141
  end
114
142
  private :_add
115
143
 
116
- def sub name, description, min: nil, aliases: nil, &exe
117
- r = _add name, min, DenCli::Sub.new( self, name, description), aliases
144
+ # Define a new sub-menu:
145
+ #
146
+ # DenCli.new {|c|
147
+ # c.sub( 'sub-command') {|s|
148
+ # s.cmd( :hello, 'Greetings', &lambda {|| puts 'hello world' })
149
+ # }
150
+ # }
151
+ #
152
+ # # ./prog sub-command hello
153
+ #
154
+ # name should be a string/symbol. It will be converted to string.
155
+ # If provided, aliases must be a list of different aliases. It will be converted to string.
156
+ def sub name, description, min: nil, aliases: nil, noshortaliases: nil, defined_in: nil, &exe
157
+ r = _add name.to_s, min, DenCli::Sub.new( self, name, description, noshortaliases: noshortaliases, defined_in: defined_in || Kernel.caller.first), aliases
118
158
  block_given? ? yield( r) : r
119
159
  end
120
160
 
121
- def cmd name, description, min: nil, aliases: nil, &exe
122
- _add name, min, DenCli::CMD.new( self, name, description, exe), aliases
161
+ # Define a new command:
162
+ #
163
+ # DenCli.new {|c|
164
+ # c.cmd( :hello, 'Greetings', &lambda {|| puts 'hello world' })
165
+ # }
166
+ #
167
+ # # ./prog hello
168
+ # hello world
169
+ #
170
+ # name should be a string/symbol. It will be converted to string.
171
+ # If provided, aliases must be a list of different aliases. Except of nil, any alias will be converted to string.
172
+ # nil is an alias for a default command for sub-commands, but interactive shells.
173
+ #
174
+ # DenCli.new {|c|
175
+ # c.sub( :greetings, 'Hello, Welcome, ...') do |s|
176
+ # s.cmd( :hello, 'A simple Hello', aliases: %w[hello-world hello_world], &lambda {|| puts 'Hello World' })
177
+ # s.cmd( :welcome, 'More gracefull', aliases: [nil, 'welcome-world', :hello_world], &lambda {|| puts 'Welcome World' })
178
+ # }
179
+ # }
180
+ #
181
+ # # ./prog greetings
182
+ # Welcome World
183
+ # # ./prog greetings welcome
184
+ # Welcome World
185
+ # # ./prog greetings hello
186
+ # Hello World
187
+ def cmd name, description, min: nil, aliases: nil, defined_in: nil, &exe
188
+ _add name, min, DenCli::CMD.new( self, name, description, exe, defined_in || Kernel.caller.first), aliases
123
189
  end
124
190
 
125
191
  def complete *pre, str
@@ -128,7 +194,7 @@ class DenCli::Sub
128
194
  elsif sub = @subs[pre[0]]
129
195
  sub.complete *pre[1..-1], str
130
196
  else
131
- STDOUT.print "\a"
197
+ $stdout.print "\a"
132
198
  end
133
199
  end
134
200
 
@@ -1,3 +1,3 @@
1
1
  class DenCli
2
- VERSION = '0.5.2'
2
+ VERSION = '0.5.6'
3
3
  end
data/lib/dencli.rb CHANGED
@@ -51,11 +51,16 @@ class DenCli
51
51
  # `g(:abc)` => `["a", "ab", "abc"]`
52
52
  # `g(:abcdef, 4)` => `["abcd", "abcde", "abcdef"]`
53
53
  def gen_aliases cmd, min = nil
54
- r = ((min||1)-1).upto cmd.length-1
54
+ case min
55
+ when false then min = cmd.length
56
+ when nil then min = 1
57
+ end
58
+ r = ([min, 1].max - 1).upto cmd.length-2
55
59
  if block_given?
56
60
  r.each {|i| yield cmd[0..i] }
61
+ yield cmd
57
62
  else
58
- r.map {|i| cmd[0..i] }
63
+ r.map {|i| cmd[0..i] } + [cmd]
59
64
  end
60
65
  end
61
66
  alias g gen_aliases
@@ -75,6 +80,9 @@ class DenCli
75
80
  post
76
81
  end
77
82
 
83
+ def []( k) @subs[k] end
84
+ def has?( k) @subs.has? k end
85
+
78
86
  def sub *a, **o, &exe
79
87
  @subs.sub *a, **o, &exe
80
88
  end
@@ -96,7 +104,7 @@ class DenCli
96
104
  end
97
105
 
98
106
  def help_full *args, output: nil
99
- output ||= STDOUT
107
+ output ||= $stdout
100
108
  x = @subs.goto *args
101
109
  _help_full output, x
102
110
  end
@@ -105,10 +113,6 @@ class DenCli
105
113
  Sub._help_commands output, subs.to_enum( :commands)
106
114
  end
107
115
 
108
- def [] k
109
- @subs[k]
110
- end
111
-
112
116
  def interactive *args, **opts
113
117
  Interactive.new self, *args, **opts
114
118
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dencli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.2
4
+ version: 0.5.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Denis Knauf
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-12-07 00:00:00.000000000 Z
11
+ date: 2022-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec