cl 1.1.5 → 1.2.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
  SHA1:
3
- metadata.gz: ce812373badd5064879d2925c6d3cd24069c73f9
4
- data.tar.gz: 8bff50303b25018a6323a1a9fe6c549af3988261
3
+ metadata.gz: e0e03ee27c161efccb0e06355598ab58c0ab9ef1
4
+ data.tar.gz: f4a43fff809f58600ac878a49f25167445b2ab52
5
5
  SHA512:
6
- metadata.gz: a86caa06223d1198ac18261c7a603e8b4ac5acfbb6c66c76e8940219c2cfb6317388ebcfe7dfa90048d642894f47b69031090be01e2fe04a1db498040300e7ca
7
- data.tar.gz: 945f46c74950ce7932b5642e009e9726f0add1f1655e7b756b02cdcf1f7dd1dbebf201ad3f330b26eacd672cc313b89504213f3178afef788a8e6003a58a364d
6
+ metadata.gz: 64eb76490a49555dff4ad9af529d9dda88ada8f04bdf1667a66ed85753027891a4792efa9860c1d1c36d36a568b817c8282e2200f589fd7953019302e66ceb29
7
+ data.tar.gz: 5a967a02acef85622b2da4da3abcf012cece0b9c887e837cf50b9a4f64038d8d1565a3ff77f08c72560653d15bf4d62aad347298ab300f2aaf8e6ec20327cabf
data/Gemfile CHANGED
@@ -2,6 +2,8 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
+ # gem 'regstry', path: '../registry'
6
+
5
7
  group :test do
6
8
  gem 'memfs'
7
9
  gem 'rspec'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cl (1.1.4)
4
+ cl (1.1.5)
5
5
  regstry (~> 1.0.3)
6
6
 
7
7
  GEM
@@ -15,22 +15,22 @@ GEM
15
15
  tins (~> 1.6)
16
16
  diff-lcs (1.3)
17
17
  docile (1.3.2)
18
- json (2.2.0)
18
+ json (2.3.0)
19
19
  memfs (1.0.0)
20
- regstry (1.0.14)
21
- rspec (3.8.0)
22
- rspec-core (~> 3.8.0)
23
- rspec-expectations (~> 3.8.0)
24
- rspec-mocks (~> 3.8.0)
25
- rspec-core (3.8.0)
26
- rspec-support (~> 3.8.0)
27
- rspec-expectations (3.8.3)
20
+ regstry (1.0.15)
21
+ rspec (3.9.0)
22
+ rspec-core (~> 3.9.0)
23
+ rspec-expectations (~> 3.9.0)
24
+ rspec-mocks (~> 3.9.0)
25
+ rspec-core (3.9.1)
26
+ rspec-support (~> 3.9.1)
27
+ rspec-expectations (3.9.0)
28
28
  diff-lcs (>= 1.2.0, < 2.0)
29
- rspec-support (~> 3.8.0)
30
- rspec-mocks (3.8.0)
29
+ rspec-support (~> 3.9.0)
30
+ rspec-mocks (3.9.1)
31
31
  diff-lcs (>= 1.2.0, < 2.0)
32
- rspec-support (~> 3.8.0)
33
- rspec-support (3.8.0)
32
+ rspec-support (~> 3.9.0)
33
+ rspec-support (3.9.2)
34
34
  simplecov (0.16.1)
35
35
  docile (~> 1.1)
36
36
  json (>= 1.8, < 3)
@@ -38,8 +38,8 @@ GEM
38
38
  simplecov-html (0.10.2)
39
39
  term-ansicolor (1.7.1)
40
40
  tins (~> 1.0)
41
- thor (0.20.3)
42
- tins (1.21.1)
41
+ thor (1.0.1)
42
+ tins (1.22.2)
43
43
 
44
44
  PLATFORMS
45
45
  ruby
@@ -51,4 +51,4 @@ DEPENDENCIES
51
51
  rspec
52
52
 
53
53
  BUNDLED WITH
54
- 2.0.1
54
+ 2.0.2
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ $:.unshift File.expand_path('../lib', __FILE__)
4
+ require 'cl/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'cl'
8
+ s.version = Cl::VERSION
9
+ s.authors = ['Sven Fuchs']
10
+ s.homepage = 'https://github.com/svenfuchs/cl'
11
+ s.licenses = ['MIT']
12
+ s.summary = 'Object-oriented OptionParser based CLI support'
13
+ s.description = <<-str.strip.gsub(/^ +/, '')
14
+ OptionParser based CLI support for rapid CLI development in an object-oriented
15
+ context.
16
+
17
+ This library wraps Ruby's OptionParser for parsing your options under the hood,
18
+ so you get all the goodness that the Ruby standard library provides.
19
+
20
+ On top of that it adds a rich and powerful DSL for defining, validating, and
21
+ normalizing options, as well as automatic and gorgeous help output (modeled
22
+ after `gem --help`).
23
+ str
24
+
25
+ s.files = Dir.glob('{examples/**/*,lib/**/*,[A-Z]*}')
26
+ s.platform = Gem::Platform::RUBY
27
+ s.require_path = 'lib'
28
+
29
+ s.add_dependency 'regstry', '~> 1.0.3'
30
+ end
data/lib/cl.rb CHANGED
@@ -4,6 +4,8 @@ require 'cl/runner'
4
4
  require 'cl/errors'
5
5
 
6
6
  class Cl
7
+ singleton_class.send(:attr_accessor, :flag_values) # remove unless Dpl needs this
8
+
7
9
  attr_reader :ctx, :name, :opts
8
10
 
9
11
  # @overload initialize(ctx, name, opts)
@@ -5,21 +5,50 @@ class Cl
5
5
  include Cast
6
6
 
7
7
  def define(const)
8
- const.send(:attr_accessor, name)
8
+ mod = Module.new
9
+ mod.send(:attr_accessor, name)
10
+ mod.class_eval "def #{name}?; #{name}.is_a?(Array) ? !#{name}.empty? : !!#{name} end"
11
+ const.include(mod)
9
12
  end
10
13
 
11
14
  def set(cmd, value)
12
- cmd.send(:"#{name}=", cast(value))
15
+ value = cast(value)
16
+ unknown(value) if enum? && !known?(value)
17
+ cmd.send(:"#{name}=", value)
13
18
  end
14
19
 
15
20
  def type
16
21
  opts[:type] || :string
17
22
  end
18
23
 
24
+ def array?
25
+ type == :array
26
+ end
27
+
19
28
  def description
20
29
  opts[:description]
21
30
  end
22
31
 
32
+ def enum
33
+ Array(opts[:enum])
34
+ end
35
+
36
+ def enum?
37
+ opts.key?(:enum)
38
+ end
39
+
40
+ def default
41
+ opts[:default]
42
+ end
43
+
44
+ def default?
45
+ opts.key?(:default)
46
+ end
47
+
48
+ def known?(value)
49
+ enum.include?(value)
50
+ end
51
+
23
52
  def required?
24
53
  !!opts[:required]
25
54
  end
@@ -29,7 +58,11 @@ class Cl
29
58
  end
30
59
 
31
60
  def splat?
32
- opts[:splat] && type == :array
61
+ !!opts[:splat] && array?
62
+ end
63
+
64
+ def unknown(value)
65
+ raise UnknownArgumentValue.new(value, enum.join(', '))
33
66
  end
34
67
 
35
68
  def to_s
@@ -14,10 +14,12 @@ class Cl
14
14
  end
15
15
 
16
16
  def apply(cmd, values, opts)
17
- return values if args.empty? || opts[:help]
18
17
  values = splat(values) if splat?
18
+ values = default(values) if default?
19
19
  validate(values)
20
- args.zip(values).map { |(arg, value)| arg.set(cmd, value) }.flatten(1).compact
20
+ return values if args.empty?
21
+ values = args.zip(values).map { |(arg, value)| arg.set(cmd, value) }.flatten(1) #.compact
22
+ compact_args(values)
21
23
  end
22
24
 
23
25
  def each(&block)
@@ -28,15 +30,28 @@ class Cl
28
30
  self.args.index(*args, &block)
29
31
  end
30
32
 
33
+ attr_writer :args
34
+
31
35
  def args
32
36
  @args ||= []
33
37
  end
34
38
 
39
+ def clear
40
+ args.clear
41
+ end
42
+
43
+ def dup
44
+ args = super
45
+ args.args = args.args.dup
46
+ args
47
+ end
48
+
35
49
  private
36
50
 
37
51
  def validate(args)
52
+ # raise ArgumentError.new(:unknown_arg, arg) if unknown?(arg)
38
53
  raise ArgumentError.new(:missing_args, args.size, required) if args.size < required
39
- raise ArgumentError.new(:too_many_args, args.size, allowed) if args.size > allowed && !splat?
54
+ raise ArgumentError.new(:too_many_args, args.join(' '), args.size, allowed) if args.size > allowed && !splat?
40
55
  end
41
56
 
42
57
  def allowed
@@ -47,6 +62,10 @@ class Cl
47
62
  any?(&:splat?)
48
63
  end
49
64
 
65
+ def default?
66
+ any?(&:default?)
67
+ end
68
+
50
69
  def required
51
70
  select(&:required?).size
52
71
  end
@@ -54,8 +73,20 @@ class Cl
54
73
  def splat(values)
55
74
  args.each.with_index.inject([]) do |group, (arg, ix)|
56
75
  count = arg && arg.splat? ? [values.size - args.size + ix + 1] : []
76
+ count = 0 if count.first.to_i < 0
57
77
  group << values.shift(*count)
58
78
  end
59
79
  end
80
+
81
+ def default(values)
82
+ args.each.with_index.inject([]) do |args, (arg, ix)|
83
+ args << (values[ix] || arg.default)
84
+ end
85
+ end
86
+
87
+ def compact_args(args)
88
+ args = compact_args(args[0..-2]) while args.last.nil? && args.size > 0
89
+ args
90
+ end
60
91
  end
61
92
  end
@@ -31,12 +31,12 @@ class Cl
31
31
  alias flag boolean
32
32
 
33
33
  def int
34
- Integer(value)
34
+ Integer(value) if value
35
35
  end
36
36
  alias integer int
37
37
 
38
38
  def float
39
- Float(value)
39
+ Float(value) if value
40
40
  end
41
41
 
42
42
  def split(value)
@@ -23,14 +23,14 @@ class Cl
23
23
  if const.name
24
24
  key = underscore(const.name.split('::').last)
25
25
  key = [registry_key, key].compact.join(':') unless abstract?
26
+ const.register(key)
26
27
  end
27
- const.register key
28
28
  const.define_singleton_method(:inherited, &inherited)
29
29
  end
30
30
  define_method(:inherited, &inherited)
31
31
 
32
32
  def cmds
33
- registry.values
33
+ registry.values.uniq
34
34
  end
35
35
 
36
36
  def parse(ctx, cmd, args)
@@ -55,7 +55,7 @@ class Cl
55
55
  @ctx = ctx
56
56
  args, opts = self.class.parse(ctx, self, args)
57
57
  @opts = self.class.opts.apply(self, self.opts.merge(opts))
58
- @args = self.class.args.apply(self, args, opts)
58
+ @args = self.class.args.apply(self, args, opts) unless help? && !is_a?(Help)
59
59
  end
60
60
 
61
61
  def opts
@@ -18,7 +18,7 @@ class Cl
18
18
  #
19
19
  # See {Cl::Cmd::Dsl#arg} for more details.
20
20
  def args(*args)
21
- return @args ||= Args.new unless args.any?
21
+ return @args ||= superclass.respond_to?(:args) ? superclass.args.dup : Args.new unless args.any?
22
22
  opts = args.last.is_a?(Hash) ? args.pop : {}
23
23
  args.each { |arg| arg(arg, opts) }
24
24
  end
@@ -5,8 +5,9 @@ class Cl
5
5
  MSGS = {
6
6
  unknown_cmd: 'Unknown command: %s',
7
7
  unknown_option: 'Unknown option: %s',
8
+ unknown_arg: 'Unknown argument value: %s (known: %s)',
8
9
  missing_args: 'Missing arguments (given: %s, required: %s)',
9
- too_many_args: 'Too many arguments (given: %s, allowed: %s)',
10
+ too_many_args: 'Too many arguments: %s (given: %s, allowed: %s)',
10
11
  wrong_type: 'Wrong argument type (given: %s, expected: %s)',
11
12
  out_of_range: 'Out of range: %s',
12
13
  invalid_format: 'Invalid format: %s',
@@ -91,6 +92,12 @@ class Cl
91
92
  end
92
93
  end
93
94
 
95
+ class UnknownArgumentValue < OptionError
96
+ def initialize(value, known)
97
+ super(:unknown_arg, value, known)
98
+ end
99
+ end
100
+
94
101
  class UnknownValues < OptionError
95
102
  include Suggest
96
103
 
@@ -2,12 +2,18 @@ class Cl
2
2
  class Help < Cl::Cmd
3
3
  register :help
4
4
 
5
+ arg :args, splat: true
6
+
5
7
  def run
6
8
  ctx.puts help
7
9
  end
8
10
 
9
11
  def help
10
- args.any? ? Cmd.new(ctx, cmd).format : Cmds.new(ctx, cmds).format
12
+ Array(args).any? ? Cmd.new(ctx, cmd).format : Cmds.new(ctx, cmds).format
13
+ end
14
+
15
+ def help?
16
+ true
11
17
  end
12
18
 
13
19
  private
@@ -12,7 +12,7 @@ class Cl
12
12
  end
13
13
 
14
14
  def usage
15
- "Usage: #{Usage.new(ctx, cmd).format}"
15
+ "Usage: #{Usage.new(ctx, cmd).format.join("\n or: ")}"
16
16
  end
17
17
 
18
18
  def summary
@@ -19,7 +19,7 @@ class Cl
19
19
  end
20
20
 
21
21
  def format_cmd(cmd)
22
- ["#{Usage.new(ctx, cmd).format}", cmd.summary]
22
+ ["#{Usage.new(ctx, cmd).format.first}", cmd.summary]
23
23
  end
24
24
  end
25
25
  end
@@ -9,7 +9,7 @@ class Cl
9
9
  def format
10
10
  opts = []
11
11
  opts << "type: #{type(obj)}" unless obj.type == :flag
12
- opts << 'required: true' if obj.required?
12
+ opts << 'required' if obj.required?
13
13
  opts += Opt.new(obj).format if obj.is_a?(Cl::Opt)
14
14
  opts = opts.join(', ')
15
15
  opts = "(#{opts})" if obj.description && !opts.empty?
@@ -33,8 +33,8 @@ class Cl
33
33
  opts << "default: #{format_default(opt)}" if opt.default?
34
34
  opts << "known values: #{format_enum(opt)}" if opt.enum?
35
35
  opts << "format: #{opt.format}" if opt.format?
36
- opts << "downcase: true" if opt.downcase?
37
- opts << "upcase: true" if opt.upcase?
36
+ opts << "downcases" if opt.downcase?
37
+ opts << "upcases" if opt.upcase?
38
38
  opts << "min: #{opt.min}" if opt.min?
39
39
  opts << "max: #{opt.max}" if opt.max?
40
40
  opts << "e.g.: #{opt.example}" if opt.example?
@@ -2,7 +2,13 @@ class Cl
2
2
  class Help
3
3
  class Usage < Struct.new(:ctx, :cmd)
4
4
  def format
5
- usage = [executable, name]
5
+ cmd.registry_keys.map do |key|
6
+ line(key)
7
+ end
8
+ end
9
+
10
+ def line(key)
11
+ usage = [executable, key.to_s.gsub(':', ' ')]
6
12
  usage += cmd.args.map(&:to_s) # { |arg| "[#{arg}]" }
7
13
  usage << '[options]' if opts?
8
14
  usage.join(' ')
@@ -12,10 +18,6 @@ class Cl
12
18
  ctx.name
13
19
  end
14
20
 
15
- def name
16
- cmd.registry_key.to_s.gsub(':', ' ')
17
- end
18
-
19
21
  def opts?
20
22
  cmd.opts.any?
21
23
  end
@@ -231,7 +231,7 @@ class Cl
231
231
 
232
232
  class Validator < Struct.new(:strs, :opts)
233
233
  SHORT = /^-\w( \w+)?$/
234
- LONG = /^--[\w\-\[\]]+( \w+)?$/
234
+ LONG = /^--[\w\-\[\]]+( \[?\w+\]?)?$/
235
235
 
236
236
  MSGS = {
237
237
  missing_strs: 'No option strings given. Pass one short -s and/or one --long option string.',
@@ -12,7 +12,7 @@ class Cl
12
12
 
13
13
  opt = Opt.new(strs, opts, block)
14
14
  opt.define(const)
15
- self << opt
15
+ insert(opt, const)
16
16
  end
17
17
 
18
18
  def apply(cmd, opts)
@@ -27,10 +27,11 @@ class Cl
27
27
  opts
28
28
  end
29
29
 
30
- def <<(opt)
30
+ def insert(opt, const)
31
31
  delete(opt)
32
- # keep the --help option at the end for help output
33
- opts.empty? ? opts << opt : opts.insert(-2, opt)
32
+ return opts << opt if const == Cmd
33
+ ix = opts.index(const.superclass.opts.first)
34
+ opts.empty? ? opts << opt : opts.insert(ix.to_i, opt)
34
35
  end
35
36
 
36
37
  def [](key)
@@ -45,6 +46,10 @@ class Cl
45
46
  opts.delete(opts.detect { |o| o.strs == opt.strs })
46
47
  end
47
48
 
49
+ def first
50
+ opts.first
51
+ end
52
+
48
53
  def to_a
49
54
  opts
50
55
  end
@@ -59,6 +64,10 @@ class Cl
59
64
  map(&:deprecated).flatten.compact
60
65
  end
61
66
 
67
+ def ==(other)
68
+ strs == other.strs
69
+ end
70
+
62
71
  def dup
63
72
  super.tap { |obj| obj.opts = opts.dup }
64
73
  end
@@ -33,7 +33,8 @@ class Cl
33
33
 
34
34
  def normalize(opts, args)
35
35
  args = noize(opts, args)
36
- dasherize(args)
36
+ # dasherize(args)
37
+ args
37
38
  end
38
39
 
39
40
  def noize(opts, args)
@@ -50,13 +51,13 @@ class Cl
50
51
  end
51
52
  end
52
53
 
53
- DASHERIZE = /^--([^= ])*/
54
-
55
- def dasherize(strs)
56
- strs.map do |str|
57
- str.is_a?(String) ? str.gsub(DASHERIZE) { |opt| opt.gsub('_', '-') } : str
58
- end
59
- end
54
+ # DASHERIZE = /^--([^= ])*/
55
+ #
56
+ # def dasherize(strs)
57
+ # strs.map do |str|
58
+ # str.is_a?(String) ? str.gsub(DASHERIZE) { |opt| opt.gsub('_', '-') } : str
59
+ # end
60
+ # end
60
61
 
61
62
  def long?(str)
62
63
  str.start_with?('--')
@@ -13,7 +13,7 @@ class Cl
13
13
  strs = strs.map { |str| negated(str) }.flatten if flag?
14
14
  strs = collect(strs, :dashed)
15
15
  strs = collect(strs, :underscored)
16
- strs = collect(strs, :valued) if flag?
16
+ strs = collect(strs, :valued) if flag? && Cl.flag_values
17
17
  strs.uniq
18
18
  end
19
19
 
@@ -7,6 +7,9 @@ class Cl
7
7
  class Default
8
8
  Runner.register :default, self
9
9
 
10
+ singleton_class.send(:attr_accessor, :run_method)
11
+ self.run_method = :run
12
+
10
13
  extend Forwardable
11
14
  include Merge, Suggest
12
15
 
@@ -20,7 +23,7 @@ class Cl
20
23
  end
21
24
 
22
25
  def run
23
- cmd.help? ? help.run : cmd.run
26
+ cmd.help? ? help.run : cmd.send(self.class.run_method)
24
27
  rescue OptionParser::InvalidOption => e
25
28
  raise UnknownOption.new(const, e.message)
26
29
  end
@@ -30,7 +33,7 @@ class Cl
30
33
  end
31
34
 
32
35
  def help
33
- Help.new(ctx, [cmd.registry_key])
36
+ cmd.is_a?(Help) ? cmd : Help.new(ctx, [cmd.registry_key])
34
37
  end
35
38
 
36
39
  def suggestions(args)
@@ -1,3 +1,3 @@
1
1
  class Cl
2
- VERSION = '1.1.5'
2
+ VERSION = '1.2.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cl
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.5
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Fuchs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-10 00:00:00.000000000 Z
11
+ date: 2020-01-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: regstry
@@ -45,6 +45,7 @@ files:
45
45
  - MIT_LICENSE.md
46
46
  - NOTES.md
47
47
  - README.md
48
+ - cl.gemspec
48
49
  - examples/README.md
49
50
  - examples/_src/args/cast.erb.rb
50
51
  - examples/_src/args/opts.erb.rb
@@ -171,7 +172,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
171
172
  version: '0'
172
173
  requirements: []
173
174
  rubyforge_project:
174
- rubygems_version: 2.6.13
175
+ rubygems_version: 2.6.11
175
176
  signing_key:
176
177
  specification_version: 4
177
178
  summary: Object-oriented OptionParser based CLI support