cl 1.1.5 → 1.2.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
  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