dencli 0.5.2 → 0.5.6

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
  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