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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -3
- data/Gemfile.lock +2 -2
- data/NOTES.md +12 -8
- data/README.md +897 -13
- data/examples/readme/alias +22 -0
- data/examples/readme/arg +16 -0
- data/examples/readme/arg_array +16 -0
- data/examples/readme/arg_type +18 -0
- data/examples/readme/args_splat +43 -0
- data/examples/readme/array +18 -0
- data/examples/readme/default +18 -0
- data/examples/readme/deprecated +21 -0
- data/examples/readme/deprecated_alias +20 -0
- data/examples/readme/downcase +18 -0
- data/examples/readme/enum +26 -0
- data/examples/readme/example +17 -0
- data/examples/readme/format +29 -0
- data/examples/readme/internal +18 -0
- data/examples/readme/opts +29 -0
- data/examples/readme/opts_block +26 -0
- data/examples/readme/range +29 -0
- data/examples/readme/required +29 -0
- data/examples/readme/requireds +36 -0
- data/examples/readme/requires +32 -0
- data/examples/readme/see +21 -0
- data/examples/readme/type +22 -0
- data/lib/cl.rb +26 -66
- data/lib/cl/arg.rb +9 -5
- data/lib/cl/cast.rb +5 -1
- data/lib/cl/cmd.rb +11 -49
- data/lib/cl/ctx.rb +3 -5
- data/lib/cl/dsl.rb +176 -0
- data/lib/cl/errors.rb +73 -0
- data/lib/cl/help/cmd.rb +8 -3
- data/lib/cl/helper.rb +8 -0
- data/lib/cl/opt.rb +13 -0
- data/lib/cl/opts.rb +27 -8
- data/lib/cl/runner.rb +10 -0
- data/lib/cl/runner/default.rb +22 -2
- data/lib/cl/runner/multi.rb +6 -4
- data/lib/cl/ui.rb +11 -2
- data/lib/cl/version.rb +1 -1
- metadata +27 -2
data/lib/cl/errors.rb
ADDED
@@ -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
|
data/lib/cl/help/cmd.rb
CHANGED
@@ -13,7 +13,7 @@ class Cl
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def format
|
16
|
-
[usage, arguments, options, common,
|
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?
|
data/lib/cl/helper.rb
CHANGED
data/lib/cl/opt.rb
CHANGED
@@ -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)
|
data/lib/cl/opts.rb
CHANGED
@@ -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
|
-
|
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
|
85
|
-
opts =
|
86
|
-
raise
|
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
|
118
|
-
|
119
|
-
value = opts[opt.name]
|
120
|
-
|
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
|
data/lib/cl/runner.rb
ADDED
data/lib/cl/runner/default.rb
CHANGED
@@ -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
|
-
|
34
|
+
private
|
33
35
|
|
34
|
-
#
|
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
|
data/lib/cl/runner/multi.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
class Cl
|
2
2
|
module Runner
|
3
3
|
class Multi
|
4
|
-
|
4
|
+
Runner.register :multi, self
|
5
5
|
|
6
|
-
|
7
|
-
|
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(
|
29
|
+
cmd.new(ctx, args)
|
28
30
|
end
|
29
31
|
end
|
30
32
|
end
|
data/lib/cl/ui.rb
CHANGED
@@ -17,8 +17,13 @@ class Cl
|
|
17
17
|
@stdout ||= opts[:stdout] || $stdout
|
18
18
|
end
|
19
19
|
|
20
|
-
def puts(*
|
21
|
-
stdout.puts(*
|
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
|
data/lib/cl/version.rb
CHANGED
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.
|
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-
|
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
|