dencli 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/example.rb +26 -4
- data/lib/dencli/cmd.rb +161 -7
- data/lib/dencli/interactive.rb +2 -2
- data/lib/dencli/sub.rb +18 -14
- data/lib/dencli/version.rb +1 -1
- data/lib/dencli.rb +8 -6
- 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: 127668bd8bd4de7dc572ce5c72be6403ca49e9da7690ceb7746c913a497495c3
|
4
|
+
data.tar.gz: b28f1e67d5c029df9917f6f9609a8d0c718c8a6cda35a0cf1a283cf37a925adb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e5fce26cb4eae5698536a1c1ffbae0571783432acef83451477daf259ee6aed1699542f4a93909c1bc3ac25095b3153a1381f32efa827f489cb3885cba557e7d
|
7
|
+
data.tar.gz: 8b22f6c8dfdd91fef4ce45819377ac5773e82a01ca168624dbff789d942cb32dec2677de396574198f7643f20f4401a32955239e67b621ecdebf5eb8847f854f
|
data/bin/example.rb
CHANGED
@@ -1,25 +1,47 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
require 'pathname'
|
4
|
+
$:.unshift Pathname.new(__FILE__).dirname.dirname.join('lib').to_s
|
3
5
|
require 'dencli'
|
4
6
|
|
5
7
|
cli = DenCli.new 'example', "This is an example for generate a DenCli-API"
|
6
8
|
cli.cmd( :example, "I have an example command") { STDERR.puts "This is an example" }
|
7
9
|
cli.cmd( :help, "", aliases: [nil, '-h', '--help']) {|*args| STDERR.puts cli.help(*args) }
|
8
10
|
|
11
|
+
cli.cmd( :args, "Expects and prints given arguments", &lambda {|a, b, c:, d:, e:|
|
12
|
+
p a: a, b: b, c: c, d: d, e: e
|
13
|
+
}).
|
14
|
+
opt( :c, '-c=ForC', "Option c").
|
15
|
+
opt( :d, '-d=ForD', "Option d", default: "something").
|
16
|
+
opt( :e, '-e', "Toggle e", default: false)
|
17
|
+
|
9
18
|
cli.sub( :more, "Sub-Commands are also possible with a new cli") do |sub|
|
19
|
+
sub.cmd( :help, "", aliases: [nil, '-h', '--help']) {|*args| STDERR.puts sub.help(*args) }
|
10
20
|
sub.cmd( :help, "") {|*args| STDERR.puts cli.help( 'more', *args) }
|
11
21
|
sub.cmd( :example, "Here is an example, too") { STDERR.puts "This is an other example" }
|
12
22
|
sub.cmd( :foo, "BAR") { STDERR.puts "FOO bar"}
|
13
23
|
|
24
|
+
sub.cmd( :args, "Expects and prints given arguments", &lambda {|a, b=1, c:, d: 5, e:|
|
25
|
+
p a: a, b: b, c: c, d: d, e: e
|
26
|
+
}).
|
27
|
+
opt( :c, '-c=ForC', "Option c").
|
28
|
+
opt( :d, '-d=ForD', "Option d (implicit default)").
|
29
|
+
opt( :e, '-e', "Toggle e")
|
30
|
+
|
14
31
|
sub.sub( :deeper, "You want to have Sub-Sub-Commands?") do |sub2|
|
15
|
-
sub2.cmd( :help, ""
|
16
|
-
sub2.cmd( :last, "The last example"
|
32
|
+
sub2.cmd( :help, "", aliases: [nil, '-h', '--help'], &lambda {|*args| STDERR.puts sub2.help(*args) })
|
33
|
+
sub2.cmd( :last, "The last example", &lambda { STDERR.puts "The last example" })
|
17
34
|
|
18
35
|
sub2.sub( :'sub-commands', "Endless Sub-Sub- ...") do |sub3|
|
19
|
-
|
36
|
+
sub3.cmd( :help, "") {|*args| STDERR.puts sub3.help( sub3, *args) }
|
20
37
|
sub3.cmd( :hehe, "The real last example", min: 2) { STDERR.puts "Trust me!" }
|
21
38
|
end
|
22
39
|
end
|
23
40
|
end
|
24
41
|
|
25
|
-
|
42
|
+
begin
|
43
|
+
cli.call *ARGV
|
44
|
+
rescue DenCli::UsageError
|
45
|
+
STDERR.puts $!
|
46
|
+
exit 1
|
47
|
+
end
|
data/lib/dencli/cmd.rb
CHANGED
@@ -1,30 +1,184 @@
|
|
1
1
|
require_relative '../dencli'
|
2
2
|
|
3
|
+
|
3
4
|
class DenCli::CMD
|
4
|
-
attr_reader :parent, :name, :
|
5
|
+
attr_reader :parent, :name, :description, :exe, :completion, :options
|
5
6
|
|
6
|
-
def initialize parent, name,
|
7
|
+
def initialize parent, name, description, exe
|
7
8
|
raise "Proc expected, instead of: #{exe.inspect}" unless Proc === exe
|
8
|
-
@parent, @name, @
|
9
|
+
@parent, @name, @description, @exe = parent, name, description, lambda( &exe)
|
10
|
+
@options = {}
|
9
11
|
completion {|*a| [] }
|
10
12
|
end
|
11
13
|
|
12
14
|
def _full_cmd( post) parent._full_cmd [@name]+post end
|
13
15
|
def full_cmd() _full_cmd [] end
|
14
|
-
def call( *a) @exe.call *a end
|
15
|
-
def help() "#{parent.full_cmd.join ' '} #{name}\n#{ desc}" end
|
16
16
|
|
17
|
+
def parameters() @exe.parameters end
|
18
|
+
def arguments_required() @exe.parameters.select {|e| :req == e[0] }.map {|e| e[1] } end
|
19
|
+
alias required arguments_required
|
20
|
+
def arguments_additional() @exe.parameters.select {|e| :opt == e[0] }.map {|e| e[1] } end
|
21
|
+
alias additional arguments_additional
|
22
|
+
def arguments() @exe.parameters.select {|e| :req == e[0] or :opt == e[0] }.map {|e| e[1] } end
|
23
|
+
def options_required() @exe.parameters.select {|e| :keyreq == e[0] }.map {|e| e[1] } end
|
24
|
+
def options_additional() @exe.parameters.select {|e| :key == e[0] }.map {|e| e[1] } end
|
25
|
+
|
26
|
+
def help() "Usage: #{usage}\n#{description}\n#{options_help}" end
|
17
27
|
def complete( *pre, str) @completion.call *pre, str end
|
18
28
|
|
29
|
+
def call( *as)
|
30
|
+
os = {}
|
31
|
+
unless @options.empty?
|
32
|
+
# options like --abc | -x will be provided in os
|
33
|
+
options = OptionParser.new
|
34
|
+
options.banner = "#{full_cmd.join ' '}"
|
35
|
+
# see also @options-array
|
36
|
+
@options.each {|_, opt| opt.on options, os }
|
37
|
+
as = options.parse! as
|
38
|
+
end
|
39
|
+
if @exe.lambda?
|
40
|
+
# The difference between a lambda and a Proc is, that Proc has anytime arity=-1.
|
41
|
+
# There will be no check if all arguments are given or some were missing or more than expected.
|
42
|
+
# lambda checks these arguments and has a arity.
|
43
|
+
# We will check it to provide useful errors.
|
44
|
+
pars = required
|
45
|
+
if as.length < pars.length
|
46
|
+
raise DenCli::UsageError, "Missing parameter(s): #{pars[as.length..-1].join " "}"
|
47
|
+
end
|
48
|
+
if parameters.select {|e| :rest == e[0] }.empty?
|
49
|
+
pars = pars + additional
|
50
|
+
if as.length > pars.length
|
51
|
+
raise DenCli::UsageError, "Unused parameter(s): #{as[-pars.length..-1].shelljoin}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
kr = @options.select {|_, o| o.required? and not os.has_key? o.name }
|
55
|
+
unless kr.empty?
|
56
|
+
raise DenCli::UsageError, "Missing argument(s): #{kr.map {|o| o.as.first }.join ', '}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
@exe.call *as, **os
|
60
|
+
end
|
61
|
+
|
62
|
+
def usage
|
63
|
+
args =
|
64
|
+
@options.map do |_, o|
|
65
|
+
s = "#{o.short}#{o.val ? ?= : ''}#{o.val}"; o.required? ? "#{s} " : "[#{s}] "
|
66
|
+
end
|
67
|
+
"#{full_cmd.join ' '} #{args.join ''}"+
|
68
|
+
(@exe.lambda? ? (
|
69
|
+
required.map{|s|"<#{s}>"}.join( " ")+
|
70
|
+
(additional.empty? ? "" : " [#{additional.map{|s|"<#{s}>"}.join " "}]")
|
71
|
+
) : '...')
|
72
|
+
end
|
73
|
+
|
74
|
+
def options_help
|
75
|
+
sc, lc, dc = 0, 0, 0
|
76
|
+
@options.each do |_, o|
|
77
|
+
s = o.short&.length || 0
|
78
|
+
l = o.long&.length || 0
|
79
|
+
v = o.val&.length || 0
|
80
|
+
d = o.desc&.to_s&.length || 0
|
81
|
+
d += 3 + o.default.to_s.length if o.default?
|
82
|
+
if 0 == l
|
83
|
+
x = s + (0==v ? 0 : 1+v)
|
84
|
+
sc = x if sc < x
|
85
|
+
else
|
86
|
+
sc = s if sc < s
|
87
|
+
x = l + (0==v ? 0 : 1+v)
|
88
|
+
lc = x if lc < x
|
89
|
+
end
|
90
|
+
dc = d if dc < d
|
91
|
+
end
|
92
|
+
format = " %-#{sc}s%s %-#{lc}s %s"
|
93
|
+
@options.map {|_, o|
|
94
|
+
s, l, v, y = o.short, o.long, o.val, ','
|
95
|
+
if l.nil?
|
96
|
+
s += "=#{v}" if v
|
97
|
+
y = ' '
|
98
|
+
elsif s.nil?
|
99
|
+
l += "=#{v}" if v
|
100
|
+
y = ' '
|
101
|
+
end
|
102
|
+
d = o.desc || ''
|
103
|
+
d += " (#{o.default})" if o.default?
|
104
|
+
format % [ s, y, l, d ]
|
105
|
+
}.join "\n"
|
106
|
+
end
|
107
|
+
|
19
108
|
def completion &exe
|
20
109
|
@completion = exe
|
21
110
|
self
|
22
111
|
end
|
23
112
|
|
113
|
+
class Opt
|
114
|
+
attr_reader :name, :long, :short, :val, :desc, :os, :conv, :req
|
115
|
+
def required?() @req end
|
116
|
+
def default?() NilClass != @default end
|
117
|
+
def default() NilClass == @default ? nil : @default end
|
118
|
+
|
119
|
+
def initialize cmd, name, opt, *alts, desc, default: NilClass, **os, &conv
|
120
|
+
long = short = val = nil
|
121
|
+
case opt
|
122
|
+
when /\A(--[^=-]+)=(.+)\z/
|
123
|
+
long, val = $1, $2
|
124
|
+
when /\A(--[^=-]+)\z/
|
125
|
+
long, val = $1, nil
|
126
|
+
when /\A(-[^=-]+)=(.+)\z/
|
127
|
+
short, val = $1, $2
|
128
|
+
when /\A(-[^=-]+)\z/
|
129
|
+
short, val = $1, nil
|
130
|
+
else raise ArgumentError, "Unexpected format for option: #{opt.inspect}"
|
131
|
+
end
|
132
|
+
alts.each do |alt|
|
133
|
+
case alt
|
134
|
+
when /\A(--[^=-]+)=(.+)\z/
|
135
|
+
long, val = $1, val || $2
|
136
|
+
when /\A(--[^=-]+)\z/
|
137
|
+
long, val = $1, nil
|
138
|
+
when /\A(-[^=-]+)=(.+)\z/
|
139
|
+
short, val = $1, val || $2
|
140
|
+
when /\A(-[^=-]+)\z/
|
141
|
+
short, val = $1, nil
|
142
|
+
else raise ArgumentError, "Unexpected format for option: #{alt.inspect}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
@name, @short, @long, @val, @desc, @default, @os, @conv =
|
146
|
+
name.to_s.to_sym, short, long, val, desc, default, os, conv || lambda{|v|v}
|
147
|
+
@req =
|
148
|
+
if NilClass != default
|
149
|
+
false
|
150
|
+
elsif cmd.exe.lambda?
|
151
|
+
! cmd.exe.parameters.select {|e| [:keyreq, @name] == e[0..1] }.empty?
|
152
|
+
else
|
153
|
+
nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def on parser, store
|
158
|
+
parser.on "#{@short}#{@val ? ?= : ''}#{@val}", "#{@long}#{@val ? ?= : ''}#{@val}", **@os do |val|
|
159
|
+
store[@name] = @conv[val]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def inspect
|
164
|
+
"#<%s:0x%016x %s %s %s %s (%p) %p os=%p conv=%s>" % [
|
165
|
+
self.class.name, object_id, @req ? "<#{@name}>" : "[#{@name}]",
|
166
|
+
@short, @long, @val, @default, @desc, @os,
|
167
|
+
@exe ? "<#{@exe.lambda? ? :lambda: :proc} ##{@exe.arity}>" : "nil"
|
168
|
+
]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def opt name, opt, *alts, desc, **os, &conv
|
173
|
+
r = Opt.new( self, name, opt, *alts, desc, **os, &conv)
|
174
|
+
@options[r.name] = r
|
175
|
+
self
|
176
|
+
end
|
177
|
+
|
24
178
|
def inspect
|
25
|
-
"#<%s:0x%x %s @name=%p @
|
179
|
+
"#<%s:0x%x %s @name=%p @description=%p @parent=<%s:0x%x %s> @exe=<arity=%d>>" % [
|
26
180
|
self.class.name, self.object_id, self.full_cmd,
|
27
|
-
@name, @
|
181
|
+
@name, @description, @parent.class.name, @parent.class.object_id, @parent.full_cmd,
|
28
182
|
@exe.arity
|
29
183
|
]
|
30
184
|
end
|
data/lib/dencli/interactive.rb
CHANGED
@@ -90,7 +90,7 @@ class DenCli::Interactive
|
|
90
90
|
c.subs.values.each do |n|
|
91
91
|
case n
|
92
92
|
when DenCli::Sub
|
93
|
-
n.cmd :exit, "<- #{n.parent.full_cmd.join ' '} - #{n.parent.
|
93
|
+
n.cmd :exit, "<- #{n.parent.full_cmd.join ' '} - #{n.parent.description[3..-1]}", min: 2 do
|
94
94
|
@cur = n.parent
|
95
95
|
end
|
96
96
|
n.cmd '', "", min: 2, aliases: [nil] do
|
@@ -120,7 +120,7 @@ class DenCli::Interactive
|
|
120
120
|
return nil if line.nil?
|
121
121
|
begin
|
122
122
|
cur.call *line
|
123
|
-
rescue ::UsageError
|
123
|
+
rescue ::DenCli::UsageError
|
124
124
|
STDERR.puts "! #$!"
|
125
125
|
end
|
126
126
|
true
|
data/lib/dencli/sub.rb
CHANGED
@@ -1,28 +1,32 @@
|
|
1
1
|
require_relative '../dencli'
|
2
2
|
|
3
3
|
class DenCli::Sub
|
4
|
-
attr_reader :parent, :name, :
|
4
|
+
attr_reader :parent, :name, :description, :subs, :aliases
|
5
5
|
|
6
|
-
def initialize parent, name,
|
7
|
-
@parent, @name, @
|
6
|
+
def initialize parent, name, description
|
7
|
+
@parent, @name, @description, @subs, @aliases = parent, name, "-> #{description}", {}, {}
|
8
8
|
end
|
9
9
|
|
10
10
|
def _full_cmd( post) parent._full_cmd [@name]+post end
|
11
11
|
def full_cmd() _full_cmd [] end
|
12
12
|
def []( k) @aliases[k] end
|
13
13
|
|
14
|
+
def usage
|
15
|
+
"#{full_cmd.join ' '} ..."
|
16
|
+
end
|
17
|
+
|
14
18
|
def help n = nil, *a
|
15
19
|
if n.nil?
|
16
|
-
r = "#{full_cmd.join ' '}: #{
|
17
|
-
m = @subs.map {|k,
|
20
|
+
r = "#{full_cmd.join ' '}: #{description}\n\n"
|
21
|
+
m = @subs.map {|k,c| k.nil? ? 0 : c.usage.length }.max
|
18
22
|
@subs.each do |k, c|
|
19
|
-
r += " % -#{m}s %s\n" % [
|
23
|
+
r += " % -#{m}s %s\n" % [c.usage, c.description] unless k.nil?
|
20
24
|
end
|
21
25
|
r
|
22
26
|
elsif @aliases.has_key? n
|
23
27
|
@aliases[n].help *a
|
24
28
|
else
|
25
|
-
raise UnknownCommand, "unknown command: #{_full_cmd( [n])[1..-1].join ' '}, available for #{full_cmd[1..-1].join' '}: #{@subs.keys.join ' '}"
|
29
|
+
raise DenCli::UnknownCommand, "unknown command: #{_full_cmd( [n])[1..-1].join ' '}, available for #{full_cmd[1..-1].join' '}: #{@subs.keys.join ' '}"
|
26
30
|
end
|
27
31
|
end
|
28
32
|
|
@@ -31,7 +35,7 @@ class DenCli::Sub
|
|
31
35
|
if @aliases.has_key? n
|
32
36
|
@aliases[n].call *a
|
33
37
|
else
|
34
|
-
raise UnknownCommand, "unknown command: #{_full_cmd( [n])[1..-1].join ' '}, available for #{full_cmd[1..-1].join' '}: #{@subs.keys.join ' '}"
|
38
|
+
raise DenCli::UnknownCommand, "unknown command: #{_full_cmd( [n])[1..-1].join ' '}, available for #{full_cmd[1..-1].join' '}: #{@subs.keys.join ' '}"
|
35
39
|
end
|
36
40
|
end
|
37
41
|
|
@@ -46,13 +50,13 @@ class DenCli::Sub
|
|
46
50
|
end
|
47
51
|
private :_add
|
48
52
|
|
49
|
-
def sub name,
|
50
|
-
r = _add name, min, DenCli::Sub.new( self, name,
|
53
|
+
def sub name, description, min: nil, aliases: nil, &exe
|
54
|
+
r = _add name, min, DenCli::Sub.new( self, name, description), aliases
|
51
55
|
block_given? ? yield( r) : r
|
52
56
|
end
|
53
57
|
|
54
|
-
def cmd name,
|
55
|
-
_add name, min, DenCli::CMD.new( self, name,
|
58
|
+
def cmd name, description, min: nil, aliases: nil, &exe
|
59
|
+
_add name, min, DenCli::CMD.new( self, name, description, exe), aliases
|
56
60
|
end
|
57
61
|
|
58
62
|
def complete *pre, str
|
@@ -66,9 +70,9 @@ class DenCli::Sub
|
|
66
70
|
end
|
67
71
|
|
68
72
|
def inspect
|
69
|
-
"#<%s:0x%x %s @name=%p @
|
73
|
+
"#<%s:0x%x %s @name=%p @description=%p @subs={%s} @aliases={%s} @parent=<%s:0x%x %s>>" % [
|
70
74
|
self.class.name, self.object_id, self.full_cmd,
|
71
|
-
@name, @
|
75
|
+
@name, @description, @subs.keys.join(', '), @aliases.keys.join(', '), @parent.class.name, @parent.class.object_id, @parent.full_cmd
|
72
76
|
]
|
73
77
|
end
|
74
78
|
end
|
data/lib/dencli/version.rb
CHANGED
data/lib/dencli.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
1
3
|
class DenCli
|
2
4
|
class UsageError < ::RuntimeError
|
3
5
|
end
|
@@ -54,8 +56,8 @@ class DenCli
|
|
54
56
|
|
55
57
|
attr_reader :subs
|
56
58
|
|
57
|
-
def initialize progname,
|
58
|
-
@subs = Sub.new self, progname,
|
59
|
+
def initialize progname, description
|
60
|
+
@subs = Sub.new self, progname, description
|
59
61
|
end
|
60
62
|
|
61
63
|
def full_cmd
|
@@ -66,12 +68,12 @@ class DenCli
|
|
66
68
|
post
|
67
69
|
end
|
68
70
|
|
69
|
-
def sub *a, &exe
|
70
|
-
@subs.sub *a, &exe
|
71
|
+
def sub *a, **o, &exe
|
72
|
+
@subs.sub *a, **o, &exe
|
71
73
|
end
|
72
74
|
|
73
|
-
def cmd *a, &exe
|
74
|
-
@subs.cmd *a, &exe
|
75
|
+
def cmd *a, **o, &exe
|
76
|
+
@subs.cmd *a, **o, &exe
|
75
77
|
end
|
76
78
|
|
77
79
|
def call *a
|
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.
|
4
|
+
version: 0.4.0
|
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: 2021-11-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|