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