dencli 0.2.0 → 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: 81af9b5ecb0bfa6a5c5d0546bef0424ab3301e5306222a9a0db5050039f20049
4
- data.tar.gz: cdb5ce86ac9ba4e8c574c5c3b688b3e82874c35a9b6873cf9f294800925c9d31
3
+ metadata.gz: 127668bd8bd4de7dc572ce5c72be6403ca49e9da7690ceb7746c913a497495c3
4
+ data.tar.gz: b28f1e67d5c029df9917f6f9609a8d0c718c8a6cda35a0cf1a283cf37a925adb
5
5
  SHA512:
6
- metadata.gz: b82a4b1d8f179b3b12d481f6bd63b891d70f695987d9b0efc81eb0c40f34d984b14b8714c25b0a1b6ef090b6be97055040e5cc0d967624e1c821112a1e0b8bd5
7
- data.tar.gz: 64eb45044b043717ad869b3cab606e5b2283c2666fa82df2ffc3b5f13e30de3d3b9672ab72ab64424688fda8c5535542217511b72d13da509cc88dd91f546869
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,30 +1,184 @@
1
1
  require_relative '../dencli'
2
2
 
3
+
3
4
  class DenCli::CMD
4
- attr_reader :parent, :name, :desc, :exe, :completion
5
+ attr_reader :parent, :name, :description, :exe, :completion, :options
5
6
 
6
- def initialize parent, name, desc, exe
7
+ def initialize parent, name, description, exe
7
8
  raise "Proc expected, instead of: #{exe.inspect}" unless Proc === exe
8
- @parent, @name, @desc, @exe = parent, name, desc, exe
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 @desc=%p @parent=<%s:0x%x %s> @exe=<arity=%d>>" % [
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, @desc, @parent.class.name, @parent.class.object_id, @parent.full_cmd,
181
+ @name, @description, @parent.class.name, @parent.class.object_id, @parent.full_cmd,
28
182
  @exe.arity
29
183
  ]
30
184
  end
@@ -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.desc[3..-1]}", min: 2 do
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, :desc, :subs, :aliases
4
+ attr_reader :parent, :name, :description, :subs, :aliases
5
5
 
6
- def initialize parent, name, desc
7
- @parent, @name, @desc, @subs, @aliases = parent, name, "-> #{desc}", {}, {}
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 ' '}: #{desc}\n\n"
17
- m = @subs.map {|k,_| k.length }.max
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" % [k, c.desc] unless k.nil?
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, desc, min: nil, aliases: nil, &exe
50
- r = _add name, min, DenCli::Sub.new( self, name, desc), aliases
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, desc, min: nil, aliases: nil, &exe
55
- _add name, min, DenCli::CMD.new( self, name, desc, exe), aliases
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 @desc=%p @subs={%s} @aliases={%s} @parent=<%s:0x%x %s>>" % [
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, @desc, @subs.keys.join(', '), @aliases.keys.join(', '), @parent.class.name, @parent.class.object_id, @parent.full_cmd
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
@@ -1,3 +1,3 @@
1
1
  class DenCli
2
- VERSION = '0.2.0'
2
+ VERSION = '0.4.0'
3
3
  end
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, desc
58
- @subs = Sub.new self, progname, desc
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.2.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: 2020-12-27 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