dencli 0.3.1 → 0.4.0

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: 291671c467c0e0cd0b75a21d36e1f5b8e2ce03e50b8f6ffe42ffd29a8b6b2bf4
4
- data.tar.gz: 78020b860dea0a56909a90b8e421201e52c7ddc8b768b21c073cebdbd273f15f
3
+ metadata.gz: 127668bd8bd4de7dc572ce5c72be6403ca49e9da7690ceb7746c913a497495c3
4
+ data.tar.gz: b28f1e67d5c029df9917f6f9609a8d0c718c8a6cda35a0cf1a283cf37a925adb
5
5
  SHA512:
6
- metadata.gz: 2a9eb16a990f3f7570e3c714e61c613b7fd76b3b3f6d635f6681e7fe165318d8e46b8e527439ea6c5203d215b529d66b48f6674f4eb795352fd2acc623658bb7
7
- data.tar.gz: e3802537b891dbb74221a723867b5123b74d25c4da3f258536d15cb2723b3f56f10fa9b8dcc6fe992f311dd86dae3ba1f59bcf57bb63ea14a3ddee84340db3db
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, "") {|*args| STDERR.puts cli.help( 'more', 'deeper', *args) }
16
- sub2.cmd( :last, "The last example") { STDERR.puts "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
- sub2.cmd( :help, "") {|*args| STDERR.puts cli.help( 'more', 'deeper', 'sub-commands', *args) }
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
- cli.call *ARGV
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,12 +1,13 @@
1
1
  require_relative '../dencli'
2
2
 
3
+
3
4
  class DenCli::CMD
4
5
  attr_reader :parent, :name, :description, :exe, :completion, :options
5
6
 
6
7
  def initialize parent, name, description, exe
7
8
  raise "Proc expected, instead of: #{exe.inspect}" unless Proc === exe
8
9
  @parent, @name, @description, @exe = parent, name, description, lambda( &exe)
9
- @options = []
10
+ @options = {}
10
11
  completion {|*a| [] }
11
12
  end
12
13
 
@@ -14,59 +15,163 @@ class DenCli::CMD
14
15
  def full_cmd() _full_cmd [] end
15
16
 
16
17
  def parameters() @exe.parameters end
17
- def required() @exe.parameters.select{|e|:req == e[0]}.map{|e|e[1]} end
18
- def additional() @exe.parameters.select{|e|:opt == e[0]}.map{|e|e[1]} 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
27
+ def complete( *pre, str) @completion.call *pre, str end
19
28
 
20
29
  def call( *as)
21
- if @options.empty?
22
- @exe.call *as
23
- else
24
- os = {}
30
+ os = {}
31
+ unless @options.empty?
32
+ # options like --abc | -x will be provided in os
25
33
  options = OptionParser.new
26
34
  options.banner = "#{full_cmd.join ' '}"
27
- @options.each do |(aname, aas, aos, aexe)|
28
- os[aname] = aos[aname] if aos.has_key? :default
29
- options.on( *aas) {|val| os[aname] = aexe[val] }
30
- end
35
+ # see also @options-array
36
+ @options.each {|_, opt| opt.on options, os }
31
37
  as = options.parse! as
32
- if @exe.lambda?
33
- pars = required
34
- if as.length < pars.length
35
- raise DenCli::UsageError, "Missing parameter(s): #{pars[as.length..-1].join " "}"
36
- end
37
- if parameters.select{|e|:rest == e[0]}.empty?
38
- pars = pars + additional
39
- if as.length > pars.length
40
- raise DenCli::UsageError, "Unused parameter(s): #{as[-pars.length..-1].shelljoin}"
41
- end
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}"
42
52
  end
43
53
  end
44
- @exe.call *as, **os
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
45
58
  end
59
+ @exe.call *as, **os
46
60
  end
47
61
 
48
62
  def usage
49
- "#{parent.full_cmd.join ' '} #{name} "+
50
- @options.map{|(_,(o,_,_,_),_)|"[#{o}] "}.join( '')+
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 ''}"+
51
68
  (@exe.lambda? ? (
52
- required.join( " ")+
53
- (additional.empty? ? "" : " [#{additional.join " "}]")
69
+ required.map{|s|"<#{s}>"}.join( " ")+
70
+ (additional.empty? ? "" : " [#{additional.map{|s|"<#{s}>"}.join " "}]")
54
71
  ) : '...')
55
72
  end
56
73
 
57
- def help
58
- "#{usage}\n#{description}"
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"
59
106
  end
60
107
 
61
- def complete( *pre, str) @completion.call *pre, str end
62
-
63
108
  def completion &exe
64
109
  @completion = exe
65
110
  self
66
111
  end
67
112
 
68
- def opt name, *as, **os, &exe
69
- @options.push [name.to_s.to_sym, as, os, exe || lambda{|val|val} ]
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
70
175
  self
71
176
  end
72
177
 
@@ -1,3 +1,3 @@
1
1
  class DenCli
2
- VERSION = '0.3.1'
2
+ VERSION = '0.4.0'
3
3
  end
data/lib/dencli.rb CHANGED
@@ -68,12 +68,12 @@ class DenCli
68
68
  post
69
69
  end
70
70
 
71
- def sub *a, &exe
72
- @subs.sub *a, &exe
71
+ def sub *a, **o, &exe
72
+ @subs.sub *a, **o, &exe
73
73
  end
74
74
 
75
- def cmd *a, &exe
76
- @subs.cmd *a, &exe
75
+ def cmd *a, **o, &exe
76
+ @subs.cmd *a, **o, &exe
77
77
  end
78
78
 
79
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.3.1
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: 2021-01-03 00:00:00.000000000 Z
11
+ date: 2021-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec