cl 0.1.12 → 0.1.13

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.
@@ -0,0 +1,73 @@
1
+ class Cl
2
+ class Error < StandardError
3
+ MSGS = {
4
+ unknown_cmd: 'Unknown command: %s',
5
+ missing_args: 'Missing arguments (given: %s, required: %s)',
6
+ too_many_args: 'Too many arguments (given: %s, allowed: %s)',
7
+ wrong_type: 'Wrong argument type (given: %s, expected: %s)',
8
+ out_of_range: 'Out of range: %s',
9
+ invalid_format: 'Invalid format: %s',
10
+ unknown_values: 'Unknown value: %s',
11
+ required_opt: 'Missing required option: %s',
12
+ required_opts: 'Missing required options: %s',
13
+ requires_opt: 'Missing option: %s',
14
+ requires_opts: 'Missing options: %s',
15
+ }
16
+
17
+ def initialize(msg, *args)
18
+ super(MSGS[msg] ? MSGS[msg] % args : msg)
19
+ end
20
+ end
21
+
22
+ ArgumentError = Class.new(Error)
23
+ OptionError = Class.new(Error)
24
+
25
+ class UnknownCmd < Error
26
+ def initialize(args)
27
+ super(:unknown_cmd, args.join(' '))
28
+ end
29
+ end
30
+
31
+ class RequiredOpts < OptionError
32
+ def initialize(opts)
33
+ msg = opts.size == 1 ? :required_opt : :required_opts
34
+ super(msg, opts.join(', '))
35
+ end
36
+ end
37
+
38
+ class RequiredsOpts < OptionError
39
+ def initialize(opts)
40
+ opts = opts.map { |alts| alts.map { |alt| Array(alt).join(' and ') }.join(', or ' ) }
41
+ super(:requires_opts, opts.join('; '))
42
+ end
43
+ end
44
+
45
+ class RequiresOpts < OptionError
46
+ def initialize(opts)
47
+ msg = opts.size == 1 ? :requires_opt : :requires_opts
48
+ opts = opts.map { |one, other| "#{one} (required by #{other})" }.join(', ')
49
+ super(msg, opts)
50
+ end
51
+ end
52
+
53
+ class OutOfRange < OptionError
54
+ def initialize(opts)
55
+ opts = opts.map { |opt, opts| "#{opt} (#{opts.map { |pair| pair.join(': ') }.join(', ')})" }.join(', ')
56
+ super(:out_of_range, opts)
57
+ end
58
+ end
59
+
60
+ class InvalidFormat < OptionError
61
+ def initialize(opts)
62
+ opts = opts.map { |opt, format| "#{opt} (format: #{format})" }.join(', ')
63
+ super(:invalid_format, opts)
64
+ end
65
+ end
66
+
67
+ class UnknownValues < OptionError
68
+ def initialize(opts)
69
+ opts = opts.map { |(key, value, known)| "#{key}=#{value} (known values: #{known.join(', ')})" }.join(', ')
70
+ super(:unknown_values, opts)
71
+ end
72
+ end
73
+ end
@@ -13,7 +13,7 @@ class Cl
13
13
  end
14
14
 
15
15
  def format
16
- [usage, arguments, options, common, summary, description].compact.join("\n\n")
16
+ [usage, summary, description, arguments, options, common, examples].compact.join("\n\n")
17
17
  end
18
18
 
19
19
  def usage
@@ -40,6 +40,10 @@ class Cl
40
40
  ['Common Options:', table(:cmmn)] if common?
41
41
  end
42
42
 
43
+ def examples
44
+ ['Examples:', indent(cmd.examples)] if cmd.examples
45
+ end
46
+
43
47
  def table(name)
44
48
  table = send(name)
45
49
  indent(table.to_s(width - table.width + 5))
@@ -77,7 +81,7 @@ class Cl
77
81
  opts = cmd.required
78
82
  strs = opts.map { |alts| alts.map { |alt| Array(alt).join(' and ') }.join(', or ' ) }
79
83
  strs = strs.map { |str| "Either #{str} are required." }.join("\n")
80
- indent(strs)
84
+ indent(strs) unless strs.empty?
81
85
  end
82
86
 
83
87
  def common?
@@ -90,7 +94,7 @@ class Cl
90
94
 
91
95
  def format_obj(obj)
92
96
  opts = []
93
- opts << "type: #{format_type(obj)}"
97
+ opts << "type: #{format_type(obj)}" unless obj.type == :flag
94
98
  opts << 'required: true' if obj.required?
95
99
  opts += format_opt(obj) if obj.is_a?(Opt)
96
100
  opts = opts.join(', ')
@@ -107,6 +111,7 @@ class Cl
107
111
  opts << "known values: #{format_enum(opt)}" if opt.enum?
108
112
  opts << "format: #{opt.format}" if opt.format?
109
113
  opts << "downcase: true" if opt.downcase?
114
+ opts << "min: #{opt.min}" if opt.min?
110
115
  opts << "max: #{opt.max}" if opt.max?
111
116
  opts << "e.g.: #{opt.example}" if opt.example?
112
117
  opts << "see: #{opt.see}" if opt.see?
@@ -25,6 +25,14 @@ class Cl
25
25
  end
26
26
  end
27
27
 
28
+ module Underscore
29
+ def underscore(string)
30
+ string.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
31
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
32
+ downcase
33
+ end
34
+ end
35
+
28
36
  extend Merge, Regex, Wrap
29
37
  end
30
38
 
@@ -64,6 +64,7 @@ class Cl
64
64
  def deprecated
65
65
  return [name, opts[:deprecated]] unless opts[:deprecated].is_a?(Symbol)
66
66
  [opts[:deprecated], name] if opts[:deprecated]
67
+ # [name, opts[:deprecated]] if opts[:deprecated]
67
68
  end
68
69
 
69
70
  def downcase?
@@ -119,6 +120,14 @@ class Cl
119
120
  !!opts[:internal]
120
121
  end
121
122
 
123
+ def min?
124
+ int? && !!opts[:min]
125
+ end
126
+
127
+ def min
128
+ opts[:min]
129
+ end
130
+
122
131
  def max?
123
132
  int? && !!opts[:max]
124
133
  end
@@ -147,6 +156,10 @@ class Cl
147
156
  opts[:see]
148
157
  end
149
158
 
159
+ def separator
160
+ opts[:sep]
161
+ end
162
+
150
163
  def block
151
164
  # raise if no block was given, and the option's name cannot be inferred
152
165
  super || method(:assign)
@@ -24,6 +24,7 @@ class Cl
24
24
  end
25
25
 
26
26
  def <<(opt)
27
+ delete(opt)
27
28
  # keep the --help option at the end for help output
28
29
  opts.empty? ? opts << opt : opts.insert(-2, opt)
29
30
  end
@@ -36,6 +37,10 @@ class Cl
36
37
  opts.each(&block)
37
38
  end
38
39
 
40
+ def delete(opt)
41
+ opts.delete(opts.detect { |o| o.strs == opt.strs })
42
+ end
43
+
39
44
  def to_a
40
45
  opts
41
46
  end
@@ -60,7 +65,7 @@ class Cl
60
65
  validate_requireds(cmd, opts)
61
66
  validate_required(opts)
62
67
  validate_requires(opts)
63
- validate_max(opts)
68
+ validate_range(opts)
64
69
  validate_format(opts)
65
70
  validate_enum(opts)
66
71
  end
@@ -81,9 +86,9 @@ class Cl
81
86
  raise RequiresOpts.new(invert(opts)) if opts.any?
82
87
  end
83
88
 
84
- def validate_max(opts)
85
- opts = exceeding_max(opts)
86
- raise ExceedingMax.new(opts) if opts.any?
89
+ def validate_range(opts)
90
+ opts = out_of_range(opts)
91
+ raise OutOfRange.new(opts) if opts.any?
87
92
  end
88
93
 
89
94
  def validate_format(opts)
@@ -114,13 +119,19 @@ class Cl
114
119
  end.compact
115
120
  end
116
121
 
117
- def exceeding_max(opts)
118
- select(&:max?).map do |opt|
119
- value = opts[opt.name]
120
- [opt.name, opt.max] if value && value > opt.max
122
+ def out_of_range(opts)
123
+ self.opts.map do |opt|
124
+ next unless value = opts[opt.name]
125
+ range = only(opt.opts, :min, :max)
126
+ [opt.name, compact(range)] if out_of_range?(range, value)
121
127
  end.compact
122
128
  end
123
129
 
130
+ def out_of_range?(range, value)
131
+ min, max = range.values_at(:min, :max)
132
+ min && value < min || max && value > max
133
+ end
134
+
124
135
  def invalid_format(opts)
125
136
  select(&:format?).map do |opt|
126
137
  value = opts[opt.name]
@@ -163,6 +174,14 @@ class Cl
163
174
  end.to_h
164
175
  end
165
176
 
177
+ def compact(hash, *keys)
178
+ hash.reject { |_, value| value.nil? }.to_h
179
+ end
180
+
181
+ def only(hash, *keys)
182
+ hash.select { |key, _| keys.include?(key) }.to_h
183
+ end
184
+
166
185
  def invert(hash)
167
186
  hash.map { |key, obj| Array(obj).map { |obj| [obj, key] } }.flatten(1).to_h
168
187
  end
@@ -0,0 +1,10 @@
1
+ require 'registry'
2
+
3
+ class Cl
4
+ module Runner
5
+ include Registry
6
+ end
7
+ end
8
+
9
+ require 'cl/runner/default'
10
+ require 'cl/runner/multi'
@@ -5,6 +5,8 @@ require 'cl/helper'
5
5
  class Cl
6
6
  module Runner
7
7
  class Default
8
+ Runner.register :default, self
9
+
8
10
  extend Forwardable
9
11
  include Merge
10
12
 
@@ -29,11 +31,28 @@ class Cl
29
31
  Help.new(ctx, [cmd.registry_key])
30
32
  end
31
33
 
32
- private
34
+ private
33
35
 
34
- # stopping at any arg that starts with a dash, find the command
36
+ # Finds a command class to run for the given arguments.
37
+ #
38
+ # Stopping at any arg that starts with a dash, find the command
35
39
  # with the key matching the most args when joined with ":", and
36
40
  # remove these used args from the array
41
+ #
42
+ # For example, if there are commands registered with the keys
43
+ #
44
+ # git:pull
45
+ # git:push
46
+ #
47
+ # then for the arguments:
48
+ #
49
+ # git push master
50
+ #
51
+ # the method `lookup` will find the constant registered as `git:push`,
52
+ # remove these from the `args` array, and return both the constant, and
53
+ # the remaining args.
54
+ #
55
+ # @param args [Array<String>] arguments to run (usually ARGV)
37
56
  def lookup(args)
38
57
  keys = args.take_while { |key| !key.start_with?('-') }
39
58
 
@@ -44,6 +63,7 @@ class Cl
44
63
  end
45
64
 
46
65
  cmd, keys = keys[0].last
66
+ cmd || raise(UnknownCmd.new(args))
47
67
  keys.each { |key| args.delete_at(args.index(key)) }
48
68
  [cmd, args]
49
69
  end
@@ -1,10 +1,12 @@
1
1
  class Cl
2
2
  module Runner
3
3
  class Multi
4
- attr_reader :name, :cmds
4
+ Runner.register :multi, self
5
5
 
6
- def initialize(name, *args)
7
- @name = name
6
+ attr_reader :ctx, :cmds
7
+
8
+ def initialize(ctx, *args)
9
+ @ctx = ctx
8
10
  @cmds = build(group(args))
9
11
  end
10
12
 
@@ -24,7 +26,7 @@ class Cl
24
26
 
25
27
  def build(cmds)
26
28
  cmds.map do |(cmd, *args)|
27
- cmd.new(name, args)
29
+ cmd.new(ctx, args)
28
30
  end
29
31
  end
30
32
  end
@@ -17,8 +17,13 @@ class Cl
17
17
  @stdout ||= opts[:stdout] || $stdout
18
18
  end
19
19
 
20
- def puts(*str)
21
- stdout.puts(*str)
20
+ def puts(*strs)
21
+ stdout.puts(*strs)
22
+ end
23
+
24
+ def abort(error, *strs)
25
+ self.error [error.message, *strs].join("\n\n")
26
+ exit 1
22
27
  end
23
28
  end
24
29
 
@@ -38,6 +43,10 @@ class Cl
38
43
  def stdout
39
44
  @stdout ||= StringIO.new
40
45
  end
46
+
47
+ def abort(error, *strs)
48
+ raise error
49
+ end
41
50
  end
42
51
 
43
52
  class Pipe < Base
@@ -1,3 +1,3 @@
1
1
  class Cl
2
- VERSION = '0.1.12'
2
+ VERSION = '0.1.13'
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: 0.1.12
4
+ version: 0.1.13
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-05-17 00:00:00.000000000 Z
11
+ date: 2019-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: regstry
@@ -43,6 +43,28 @@ files:
43
43
  - examples/gem
44
44
  - examples/heroku
45
45
  - examples/rakeish
46
+ - examples/readme/alias
47
+ - examples/readme/arg
48
+ - examples/readme/arg_array
49
+ - examples/readme/arg_type
50
+ - examples/readme/args_splat
51
+ - examples/readme/array
52
+ - examples/readme/default
53
+ - examples/readme/deprecated
54
+ - examples/readme/deprecated_alias
55
+ - examples/readme/downcase
56
+ - examples/readme/enum
57
+ - examples/readme/example
58
+ - examples/readme/format
59
+ - examples/readme/internal
60
+ - examples/readme/opts
61
+ - examples/readme/opts_block
62
+ - examples/readme/range
63
+ - examples/readme/required
64
+ - examples/readme/requireds
65
+ - examples/readme/requires
66
+ - examples/readme/see
67
+ - examples/readme/type
46
68
  - lib/cl.rb
47
69
  - lib/cl/arg.rb
48
70
  - lib/cl/args.rb
@@ -52,6 +74,8 @@ files:
52
74
  - lib/cl/config/env.rb
53
75
  - lib/cl/config/files.rb
54
76
  - lib/cl/ctx.rb
77
+ - lib/cl/dsl.rb
78
+ - lib/cl/errors.rb
55
79
  - lib/cl/help.rb
56
80
  - lib/cl/help/cmd.rb
57
81
  - lib/cl/help/cmds.rb
@@ -61,6 +85,7 @@ files:
61
85
  - lib/cl/opt.rb
62
86
  - lib/cl/opts.rb
63
87
  - lib/cl/parser.rb
88
+ - lib/cl/runner.rb
64
89
  - lib/cl/runner/default.rb
65
90
  - lib/cl/runner/multi.rb
66
91
  - lib/cl/ui.rb