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 +4 -4
- data/bin/example.rb +228 -19
- data/lib/dencli/cmd.rb +41 -26
- data/lib/dencli/interactive.rb +13 -8
- data/lib/dencli/sub.rb +113 -47
- data/lib/dencli/version.rb +1 -1
- data/lib/dencli.rb +11 -7
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 567eaa6ae13836be53cfc492496ae089be944c0a352f55023ac413e4f051c8d4
|
4
|
+
data.tar.gz: 7c3cb0ff03f0a738b56e785e8d2f9da76db16d0afab5c29fd5a53881476968cd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
5
|
-
require '
|
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, '-
|
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") {
|
18
|
-
cli.cmd( :help, "", aliases: [nil, '-h', '--help'], &lambda {|*
|
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 *
|
112
|
+
cli.help_full *commands, output: $stderr
|
21
113
|
else
|
22
|
-
cli.help *
|
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|
|
29
|
-
sub.cmd( :
|
30
|
-
sub.cmd( :
|
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 {|*
|
42
|
-
sub2.cmd( :last, "The last example", &lambda {
|
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
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
80
|
-
|
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 += "
|
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, :
|
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(-[
|
176
|
+
when /\A(-[^= -])[= ]?(.+)\z/
|
165
177
|
@short, @val = $1, $2 || @val
|
166
|
-
when /\A(-[
|
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, *
|
175
|
-
@name, @desc, @default, @
|
176
|
-
name.to_s.to_sym, desc, default,
|
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
|
-
|
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
|
-
|
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%
|
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, @
|
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, *
|
206
|
-
r = Opt.new( self, name, opt, *
|
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
|
data/lib/dencli/interactive.rb
CHANGED
@@ -17,12 +17,15 @@ class DenCli::Interactive
|
|
17
17
|
end
|
18
18
|
read_history if @histfile
|
19
19
|
|
20
|
-
|
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,
|
94
|
-
|
95
|
-
|
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.
|
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
|
-
|
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 [](
|
16
|
+
def []( name) @aliases[name&.to_s] end
|
17
|
+
def has?( name) @aliases.has_key? name&.to_s end
|
16
18
|
|
17
|
-
|
18
|
-
|
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
|
32
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
c
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
92
|
+
output << "\n"
|
76
93
|
end
|
77
|
-
output
|
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
|
86
|
-
|
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
|
95
|
-
|
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,
|
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
|
122
|
+
name = name.to_s
|
107
123
|
@subs[name] = obj
|
108
|
-
|
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
|
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
|
-
|
117
|
-
|
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
|
-
|
122
|
-
|
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
|
-
|
197
|
+
$stdout.print "\a"
|
132
198
|
end
|
133
199
|
end
|
134
200
|
|
data/lib/dencli/version.rb
CHANGED
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
|
-
|
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 ||=
|
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.
|
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:
|
11
|
+
date: 2022-02-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|