cl 1.0.0 → 1.0.1
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 +5 -5
- data/CHANGELOG.md +1 -0
- data/Gemfile.lock +1 -1
- data/NOTES.md +4 -0
- data/README.md +131 -74
- data/examples/README.md +22 -0
- data/examples/{src → _src}/args/cast.erb.rb +0 -0
- data/examples/{src → _src}/args/opts.erb.rb +0 -0
- data/examples/{src → _src}/args/required.erb.rb +0 -0
- data/examples/{src → _src}/args/splat.erb.rb +0 -0
- data/examples/{src → _src}/gem.erb.rb +0 -0
- data/examples/{src → _src}/heroku.erb.rb +0 -0
- data/examples/{src → _src}/rakeish.erb.rb +0 -0
- data/examples/{src → _src}/readme/abstract.erb.rb +0 -0
- data/examples/{src → _src}/readme/alias.erb.rb +0 -0
- data/examples/{src → _src}/readme/arg.erb.rb +0 -0
- data/examples/{src → _src}/readme/arg_array.erb.rb +0 -0
- data/examples/{src → _src}/readme/arg_type.erb.rb +0 -0
- data/examples/{src → _src}/readme/args_splat.erb.rb +0 -0
- data/examples/{src → _src}/readme/array.erb.rb +0 -0
- data/examples/_src/readme/basic.erb.rb +70 -0
- data/examples/{src → _src}/readme/default.erb.rb +0 -0
- data/examples/{src → _src}/readme/deprecated.erb.rb +0 -0
- data/examples/{src → _src}/readme/deprecated_alias.erb.rb +0 -0
- data/examples/_src/readme/description.erb.rb +58 -0
- data/examples/{src → _src}/readme/downcase.erb.rb +0 -0
- data/examples/{src → _src}/readme/enum.erb.rb +0 -0
- data/examples/{src → _src}/readme/example.erb.rb +0 -0
- data/examples/{src → _src}/readme/format.erb.rb +0 -0
- data/examples/{src → _src}/readme/internal.erb.rb +0 -0
- data/examples/{src → _src}/readme/negate.erb.rb +0 -0
- data/examples/{src/readme/node.erb.rb → _src/readme/note.erb.rb} +0 -0
- data/examples/{src → _src}/readme/opts.erb.rb +0 -0
- data/examples/{src → _src}/readme/opts_block.erb.rb +0 -0
- data/examples/{src → _src}/readme/range.erb.rb +0 -0
- data/examples/_src/readme/registry.erb.rb +16 -0
- data/examples/{src → _src}/readme/required.erb.rb +0 -0
- data/examples/{src → _src}/readme/requireds.erb.rb +0 -0
- data/examples/{src → _src}/readme/requires.erb.rb +0 -0
- data/examples/_src/readme/runner.erb.rb +27 -0
- data/examples/_src/readme/runner_custom.erb.rb +25 -0
- data/examples/{src → _src}/readme/secret.erb.rb +0 -0
- data/examples/{src → _src}/readme/see.erb.rb +0 -0
- data/examples/{src → _src}/readme/type.erb.rb +0 -0
- data/examples/readme/basic +65 -0
- data/examples/readme/description +54 -0
- data/examples/readme/note +6 -4
- data/examples/readme/registry +13 -0
- data/examples/readme/runner +28 -0
- data/examples/readme/runner_custom +22 -0
- data/lib/cl/args.rb +10 -18
- data/lib/cl/cast.rb +46 -24
- data/lib/cl/help/cmd.rb +3 -55
- data/lib/cl/help/format.rb +69 -0
- data/lib/cl/help/table.rb +5 -2
- data/lib/cl/opt.rb +4 -0
- data/lib/cl/opts/validate.rb +117 -0
- data/lib/cl/opts.rb +11 -101
- data/lib/cl/version.rb +1 -1
- metadata +49 -36
- data/examples/readme/node +0 -19
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'cl'
|
2
|
+
|
3
|
+
# e.g. lib/owners/add.rb
|
4
|
+
module Owners
|
5
|
+
class Add < Cl::Cmd
|
6
|
+
summary 'Add one or more owners to an existing owner group'
|
7
|
+
|
8
|
+
description <<~str
|
9
|
+
Use this command to add one or more owners to an existing
|
10
|
+
owner group.
|
11
|
+
|
12
|
+
[...]
|
13
|
+
str
|
14
|
+
|
15
|
+
args :owner
|
16
|
+
|
17
|
+
opt '-t', '--to TO', 'An existing owner group'
|
18
|
+
|
19
|
+
def run
|
20
|
+
# implement adding the owner as given in `owner` (as well as `args`)
|
21
|
+
# to the group given in `to` (as well as `opts[:to]`).
|
22
|
+
p owner: owner, to: to, to?: to?, args: args, opts: opts
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Running this, e.g. using `bin/owners add one,two --to group` will instantiate the
|
28
|
+
# class `Owners::Add`, and call the method `run` on it.
|
29
|
+
|
30
|
+
# e.g. bin/owners
|
31
|
+
#
|
32
|
+
# args normally would be ARGV
|
33
|
+
args = %w(add one --to group)
|
34
|
+
|
35
|
+
Cl.new('owners').run(args)
|
36
|
+
|
37
|
+
# Output:
|
38
|
+
#
|
39
|
+
# {:owner=>"one", :to=>"group", :to?=>true, :args=>["one"], :opts=>{:to=>"group"}}
|
40
|
+
|
41
|
+
Cl.new('owners').run(%w(add --help))
|
42
|
+
|
43
|
+
# Output:
|
44
|
+
#
|
45
|
+
# Usage: owners add [owner] [options]
|
46
|
+
#
|
47
|
+
# Summary:
|
48
|
+
#
|
49
|
+
# Add one or more owners to an existing owner group
|
50
|
+
#
|
51
|
+
# Description:
|
52
|
+
#
|
53
|
+
# Use this command to add one or more owners to an existing
|
54
|
+
# owner group.
|
55
|
+
#
|
56
|
+
# [...]
|
57
|
+
#
|
58
|
+
# Arguments:
|
59
|
+
#
|
60
|
+
# owner type: string
|
61
|
+
#
|
62
|
+
# Options:
|
63
|
+
#
|
64
|
+
# -t --to TO An existing owner group (type: string)
|
65
|
+
# --help Get help on this command
|
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$: << File.expand_path('lib')
|
3
|
+
|
4
|
+
require 'cl'
|
5
|
+
|
6
|
+
module Owners
|
7
|
+
class Add < Cl::Cmd
|
8
|
+
summary 'Add one or more owners to an existing owner group'
|
9
|
+
|
10
|
+
description <<~str
|
11
|
+
Use this command to add one or more owners to an existing
|
12
|
+
owner group.
|
13
|
+
str
|
14
|
+
|
15
|
+
examples <<~str
|
16
|
+
Adding a single user to the group admins:
|
17
|
+
|
18
|
+
owners add user --to admins
|
19
|
+
|
20
|
+
Adding a several users at once:
|
21
|
+
|
22
|
+
owners add one two three --to admins
|
23
|
+
str
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
Cl.new('owners').run(%w(add --help))
|
28
|
+
|
29
|
+
# Output:
|
30
|
+
#
|
31
|
+
# Usage: owners add [options]
|
32
|
+
#
|
33
|
+
# Summary:
|
34
|
+
#
|
35
|
+
# Add one or more owners to an existing owner group
|
36
|
+
#
|
37
|
+
# Description:
|
38
|
+
#
|
39
|
+
# Use this command to add one or more owners to an existing
|
40
|
+
# owner group.
|
41
|
+
#
|
42
|
+
# Options:
|
43
|
+
#
|
44
|
+
# --help Get help on this command
|
45
|
+
#
|
46
|
+
# Examples:
|
47
|
+
#
|
48
|
+
# Adding a single user to the group admins:
|
49
|
+
#
|
50
|
+
# owners add user --to admins
|
51
|
+
#
|
52
|
+
# Adding a several users at once:
|
53
|
+
#
|
54
|
+
# owners add one two three --to admins
|
data/examples/readme/note
CHANGED
@@ -9,9 +9,11 @@ end
|
|
9
9
|
|
10
10
|
Cl.new('owners').run(%w(add --help))
|
11
11
|
|
12
|
-
#
|
12
|
+
# Output:
|
13
13
|
#
|
14
|
-
#
|
14
|
+
# Usage: owners add [options]
|
15
15
|
#
|
16
|
-
#
|
17
|
-
#
|
16
|
+
# Options:
|
17
|
+
#
|
18
|
+
# --to GROUP type: string, note: needs to be a group
|
19
|
+
# --help Get help on this command
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$: << File.expand_path('lib')
|
3
|
+
|
4
|
+
module Git
|
5
|
+
class Pull < Cl::Cmd
|
6
|
+
register :'git:pull'
|
7
|
+
|
8
|
+
def run
|
9
|
+
p cmd: registry_key, args: args
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# With this class registered (and assuming the executable that calls `Cl` is
|
15
|
+
# `bin/run`) the default runner would recognize and run it:
|
16
|
+
#
|
17
|
+
# $ bin/run git:pull master # instantiates Git::Pull, and passes ["master"] as args
|
18
|
+
# $ bin/run git pull master # does the same
|
19
|
+
|
20
|
+
Cl.new('run').run(%w(git:pull master))
|
21
|
+
# Output:
|
22
|
+
#
|
23
|
+
# {:cmd=>:"git:pull", :args=>["master"]}
|
24
|
+
|
25
|
+
Cl.new('run').run(%w(git pull master))
|
26
|
+
# Output:
|
27
|
+
#
|
28
|
+
# {:cmd=>:"git:pull", :args=>["master"]}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# anywhere in your library
|
2
|
+
|
3
|
+
require 'cl'
|
4
|
+
|
5
|
+
class Runner
|
6
|
+
Cl::Runner.register :custom, self
|
7
|
+
|
8
|
+
def initialize(ctx, args)
|
9
|
+
# ...
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
const = identify_cmd_class_from_args
|
14
|
+
const.new(ctx, args).run
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# in bin/run
|
19
|
+
Cl.new('run', runner: :custom).run(ARGV)
|
20
|
+
|
21
|
+
|
22
|
+
|
data/lib/cl/args.rb
CHANGED
@@ -13,11 +13,11 @@ class Cl
|
|
13
13
|
self.args << arg
|
14
14
|
end
|
15
15
|
|
16
|
-
def apply(cmd,
|
17
|
-
return
|
18
|
-
|
19
|
-
validate(
|
20
|
-
args.map { |(arg, value)| arg.set(cmd, value) }.flatten(1)
|
16
|
+
def apply(cmd, values, opts)
|
17
|
+
return values if args.empty? || opts[:help]
|
18
|
+
values = splat(values) if splat?
|
19
|
+
validate(values)
|
20
|
+
args.zip(values).map { |(arg, value)| arg.set(cmd, value) }.flatten(1).compact
|
21
21
|
end
|
22
22
|
|
23
23
|
def each(&block)
|
@@ -51,19 +51,11 @@ class Cl
|
|
51
51
|
select(&:required?).size
|
52
52
|
end
|
53
53
|
|
54
|
-
def
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
group[arg] << value
|
60
|
-
ix += 1 if args.size + group[arg].size > values.size
|
61
|
-
else
|
62
|
-
group[arg] = value
|
63
|
-
ix += 1
|
64
|
-
end
|
65
|
-
[ix, group]
|
66
|
-
end.last
|
54
|
+
def splat(values)
|
55
|
+
args.each.with_index.inject([]) do |group, (arg, ix)|
|
56
|
+
count = arg && arg.splat? ? [values.size - args.size + ix + 1] : []
|
57
|
+
group << values.shift(*count)
|
58
|
+
end
|
67
59
|
end
|
68
60
|
end
|
69
61
|
end
|
data/lib/cl/cast.rb
CHANGED
@@ -1,33 +1,55 @@
|
|
1
1
|
class Cl
|
2
2
|
module Cast
|
3
|
-
|
4
|
-
|
3
|
+
class Cast < Struct.new(:type, :value, :opts)
|
4
|
+
TRUE = /^(true|yes|on)$/
|
5
|
+
FALSE = /^(false|no|off)$/
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
Array(value).compact.flatten.map { |value| split(value) }.flatten
|
12
|
-
when :string, :str
|
13
|
-
value.to_s unless value.to_s.empty?
|
14
|
-
when :flag, :boolean, :bool
|
15
|
-
return true if value.to_s =~ TRUE
|
16
|
-
return false if value.to_s =~ FALSE
|
17
|
-
!!value
|
18
|
-
when :integer, :int
|
19
|
-
Integer(value)
|
20
|
-
when :float
|
21
|
-
Float(value)
|
22
|
-
else
|
23
|
-
raise ArgumentError, "Unknown type: #{type}" if value
|
7
|
+
def apply
|
8
|
+
return send(type) if respond_to?(type, true)
|
9
|
+
raise ArgumentError, "Unknown type: #{type}"
|
10
|
+
rescue ::ArgumentError => e
|
11
|
+
raise ArgumentError.new(:wrong_type, value.inspect, type)
|
24
12
|
end
|
25
|
-
|
26
|
-
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def array
|
17
|
+
Array(value).compact.flatten.map { |value| split(value) }.flatten.compact
|
18
|
+
end
|
19
|
+
|
20
|
+
def string
|
21
|
+
value.to_s unless value.to_s.empty?
|
22
|
+
end
|
23
|
+
alias str string
|
24
|
+
|
25
|
+
def boolean
|
26
|
+
return true if value.to_s =~ TRUE
|
27
|
+
return false if value.to_s =~ FALSE
|
28
|
+
!!value
|
29
|
+
end
|
30
|
+
alias bool boolean
|
31
|
+
alias flag boolean
|
32
|
+
|
33
|
+
def int
|
34
|
+
Integer(value)
|
35
|
+
end
|
36
|
+
alias integer int
|
37
|
+
|
38
|
+
def float
|
39
|
+
Float(value)
|
40
|
+
end
|
41
|
+
|
42
|
+
def split(value)
|
43
|
+
separator ? value.to_s.split(separator) : value
|
44
|
+
end
|
45
|
+
|
46
|
+
def separator
|
47
|
+
opts[:separator]
|
48
|
+
end
|
27
49
|
end
|
28
50
|
|
29
|
-
def
|
30
|
-
|
51
|
+
def cast(value)
|
52
|
+
type ? Cast.new(type, value, separator: separator).apply : value
|
31
53
|
end
|
32
54
|
end
|
33
55
|
end
|
data/lib/cl/help/cmd.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
require 'cl/help/format'
|
1
2
|
require 'cl/help/table'
|
2
3
|
require 'cl/help/usage'
|
3
4
|
|
4
5
|
class Cl
|
5
6
|
class Help
|
6
7
|
class Cmd < Struct.new(:ctx, :cmd)
|
7
|
-
include
|
8
|
+
include Format
|
8
9
|
|
9
10
|
def format
|
10
11
|
[usage, summary, description, arguments, options, common, examples].compact.join("\n\n")
|
@@ -86,59 +87,6 @@ class Cl
|
|
86
87
|
[args.width, opts.width, cmmn.width].max
|
87
88
|
end
|
88
89
|
|
89
|
-
def format_obj(obj)
|
90
|
-
opts = []
|
91
|
-
opts << "type: #{format_type(obj)}" unless obj.type == :flag
|
92
|
-
opts << 'required: true' if obj.required?
|
93
|
-
opts += format_opt(obj) if obj.is_a?(Opt)
|
94
|
-
opts = opts.join(', ')
|
95
|
-
opts = "(#{opts})" if obj.description && !opts.empty?
|
96
|
-
opts = [obj.description, opts]
|
97
|
-
opts.compact.map(&:strip).join(' ')
|
98
|
-
end
|
99
|
-
|
100
|
-
def format_opt(opt)
|
101
|
-
opts = []
|
102
|
-
opts << "alias: #{format_aliases(opt)}" if opt.aliases?
|
103
|
-
opts << "requires: #{opt.requires.join(', ')}" if opt.requires?
|
104
|
-
opts << "default: #{format_default(opt)}" if opt.default?
|
105
|
-
opts << "known values: #{format_enum(opt)}" if opt.enum?
|
106
|
-
opts << "format: #{opt.format}" if opt.format?
|
107
|
-
opts << "downcase: true" if opt.downcase?
|
108
|
-
opts << "min: #{opt.min}" if opt.min?
|
109
|
-
opts << "max: #{opt.max}" if opt.max?
|
110
|
-
opts << "e.g.: #{opt.example}" if opt.example?
|
111
|
-
opts << "note: #{opt.note}" if opt.note?
|
112
|
-
opts << "see: #{opt.see}" if opt.see?
|
113
|
-
opts << format_deprecated(opt) if opt.deprecated?
|
114
|
-
opts.compact
|
115
|
-
end
|
116
|
-
|
117
|
-
def format_aliases(opt)
|
118
|
-
opt.aliases.map do |name|
|
119
|
-
strs = [name]
|
120
|
-
strs << "(deprecated, please use #{opt.name})" if opt.deprecated[0] == name
|
121
|
-
strs.join(' ')
|
122
|
-
end.join(', ')
|
123
|
-
end
|
124
|
-
|
125
|
-
def format_enum(opt)
|
126
|
-
opt.enum.map { |value| format_regex(value) }.join(', ')
|
127
|
-
end
|
128
|
-
|
129
|
-
def format_type(obj)
|
130
|
-
return obj.type unless obj.is_a?(Opt) && obj.type == :array
|
131
|
-
"array (string, can be given multiple times)"
|
132
|
-
end
|
133
|
-
|
134
|
-
def format_default(opt)
|
135
|
-
opt.default.is_a?(Symbol) ? opt.default.to_s.sub('_', ' ') : opt.default
|
136
|
-
end
|
137
|
-
|
138
|
-
def format_deprecated(opt)
|
139
|
-
return "deprecated (#{opt.deprecated[1]})" if opt.deprecated[0] == opt.name
|
140
|
-
end
|
141
|
-
|
142
90
|
def rjust(objs)
|
143
91
|
return objs unless objs.any?
|
144
92
|
width = objs.max_by(&:size).size
|
@@ -146,7 +94,7 @@ class Cl
|
|
146
94
|
end
|
147
95
|
|
148
96
|
def indent(str)
|
149
|
-
str.lines.map { |line| " #{line}" }.join
|
97
|
+
str.lines.map { |line| " #{line}".rstrip }.join("\n")
|
150
98
|
end
|
151
99
|
end
|
152
100
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class Cl
|
2
|
+
class Help
|
3
|
+
module Format
|
4
|
+
def format_obj(obj)
|
5
|
+
Obj.new(obj).format
|
6
|
+
end
|
7
|
+
|
8
|
+
class Obj < Struct.new(:obj)
|
9
|
+
def format
|
10
|
+
opts = []
|
11
|
+
opts << "type: #{type(obj)}" unless obj.type == :flag
|
12
|
+
opts << 'required: true' if obj.required?
|
13
|
+
opts += Opt.new(obj).format if obj.is_a?(Cl::Opt)
|
14
|
+
opts = opts.join(', ')
|
15
|
+
opts = "(#{opts})" if obj.description && !opts.empty?
|
16
|
+
opts = [obj.description, opts]
|
17
|
+
opts.compact.map(&:strip).join(' ')
|
18
|
+
end
|
19
|
+
|
20
|
+
def type(obj)
|
21
|
+
return obj.type unless obj.is_a?(Cl::Opt) && obj.type == :array
|
22
|
+
"array (string, can be given multiple times)"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Opt < Struct.new(:opt)
|
27
|
+
include Regex
|
28
|
+
|
29
|
+
def format
|
30
|
+
opts = []
|
31
|
+
opts << "alias: #{format_aliases(opt)}" if opt.aliases?
|
32
|
+
opts << "requires: #{opt.requires.join(', ')}" if opt.requires?
|
33
|
+
opts << "default: #{format_default(opt)}" if opt.default?
|
34
|
+
opts << "known values: #{format_enum(opt)}" if opt.enum?
|
35
|
+
opts << "format: #{opt.format}" if opt.format?
|
36
|
+
opts << "downcase: true" if opt.downcase?
|
37
|
+
opts << "upcase: true" if opt.upcase?
|
38
|
+
opts << "min: #{opt.min}" if opt.min?
|
39
|
+
opts << "max: #{opt.max}" if opt.max?
|
40
|
+
opts << "e.g.: #{opt.example}" if opt.example?
|
41
|
+
opts << "note: #{opt.note}" if opt.note?
|
42
|
+
opts << "see: #{opt.see}" if opt.see?
|
43
|
+
opts << format_deprecated(opt) if opt.deprecated?
|
44
|
+
opts.compact
|
45
|
+
end
|
46
|
+
|
47
|
+
def format_aliases(opt)
|
48
|
+
opt.aliases.map do |name|
|
49
|
+
strs = [name]
|
50
|
+
strs << "(deprecated, please use #{opt.name})" if opt.deprecated[0] == name
|
51
|
+
strs.join(' ')
|
52
|
+
end.join(', ')
|
53
|
+
end
|
54
|
+
|
55
|
+
def format_enum(opt)
|
56
|
+
opt.enum.map { |value| format_regex(value) }.join(', ')
|
57
|
+
end
|
58
|
+
|
59
|
+
def format_default(opt)
|
60
|
+
opt.default.is_a?(Symbol) ? opt.default.to_s.sub('_', ' ') : opt.default
|
61
|
+
end
|
62
|
+
|
63
|
+
def format_deprecated(opt)
|
64
|
+
return "deprecated (#{opt.deprecated[1]})" if opt.deprecated[0] == opt.name
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/cl/help/table.rb
CHANGED
@@ -42,11 +42,14 @@ class Cl
|
|
42
42
|
def widths
|
43
43
|
cols.map.with_index do |col, ix|
|
44
44
|
max = col.compact.max_by(&:size)
|
45
|
-
|
46
|
-
ix < cols.size - 2 ? width : width + padding.to_i
|
45
|
+
pad(max ? max.size : 0, ix)
|
47
46
|
end
|
48
47
|
end
|
49
48
|
|
49
|
+
def pad(width, ix)
|
50
|
+
ix < cols.size - 2 ? width : width + padding.to_i
|
51
|
+
end
|
52
|
+
|
50
53
|
def cols
|
51
54
|
@cols ||= data.transpose
|
52
55
|
end
|
data/lib/cl/opt.rb
CHANGED
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'cl/helper'
|
2
|
+
|
3
|
+
class Cl
|
4
|
+
class Opts
|
5
|
+
module Validate
|
6
|
+
def validate(cmd, opts, values)
|
7
|
+
Validate.constants.each do |name|
|
8
|
+
next if name == :Validator
|
9
|
+
const = Validate.const_get(name)
|
10
|
+
const.new(cmd, opts, values).apply
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Validator < Struct.new(:cmd, :opts, :values)
|
15
|
+
include Regex
|
16
|
+
def compact(hash, *keys)
|
17
|
+
hash.reject { |_, value| value.nil? }.to_h
|
18
|
+
end
|
19
|
+
|
20
|
+
def invert(hash)
|
21
|
+
hash.map { |key, obj| Array(obj).map { |obj| [obj, key] } }.flatten(1).to_h
|
22
|
+
end
|
23
|
+
|
24
|
+
def only(hash, *keys)
|
25
|
+
hash.select { |key, _| keys.include?(key) }.to_h
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Required < Validator
|
30
|
+
def apply
|
31
|
+
# make sure we do not accept unnamed required options
|
32
|
+
raise RequiredOpts.new(missing.map(&:name)) if missing.any?
|
33
|
+
end
|
34
|
+
|
35
|
+
def missing
|
36
|
+
@missing ||= opts.select(&:required?).select { |opt| !values.key?(opt.name) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Requireds < Validator
|
41
|
+
def apply
|
42
|
+
raise RequiredsOpts.new(missing) if missing.any?
|
43
|
+
end
|
44
|
+
|
45
|
+
def missing
|
46
|
+
@missing ||= cmd.class.required.map do |alts|
|
47
|
+
alts if alts.none? { |alt| Array(alt).all? { |key| values.key?(key) } }
|
48
|
+
end.compact
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class Requires < Validator
|
53
|
+
def apply
|
54
|
+
raise RequiresOpts.new(invert(missing)) if missing.any?
|
55
|
+
end
|
56
|
+
|
57
|
+
def missing
|
58
|
+
@missing ||= requires.map do |opt|
|
59
|
+
missing = opt.requires.select { |key| !values.key?(key) }
|
60
|
+
[opt.name, missing] if missing.any?
|
61
|
+
end.compact
|
62
|
+
end
|
63
|
+
|
64
|
+
def requires
|
65
|
+
opts.select(&:requires?).select { |opt| values.key?(opt.name) }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Format < Validator
|
70
|
+
def apply
|
71
|
+
raise InvalidFormat.new(invalid) if invalid.any?
|
72
|
+
end
|
73
|
+
|
74
|
+
def invalid
|
75
|
+
@invalid ||= opts.select(&:format?).map do |opt|
|
76
|
+
value = values[opt.name]
|
77
|
+
[opt.name, opt.format] if value && !opt.formatted?(value)
|
78
|
+
end.compact
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class Enum < Validator
|
83
|
+
def apply
|
84
|
+
raise UnknownValues.new(unknown) if unknown.any?
|
85
|
+
end
|
86
|
+
|
87
|
+
def unknown
|
88
|
+
@unknown ||= opts.select(&:enum?).map do |opt|
|
89
|
+
value = values[opt.name]
|
90
|
+
next unless value && !opt.known?(value)
|
91
|
+
known = opt.enum.map { |str| format_regex(str) }
|
92
|
+
[opt.name, value, known]
|
93
|
+
end.compact
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class Range < Validator
|
98
|
+
def apply
|
99
|
+
raise OutOfRange.new(invalid) if invalid.any?
|
100
|
+
end
|
101
|
+
|
102
|
+
def invalid
|
103
|
+
@invalid ||= opts.map do |opt|
|
104
|
+
next unless value = values[opt.name]
|
105
|
+
range = only(opt.opts, :min, :max)
|
106
|
+
[opt.name, compact(range)] if invalid?(range, value)
|
107
|
+
end.compact
|
108
|
+
end
|
109
|
+
|
110
|
+
def invalid?(range, value)
|
111
|
+
min, max = range.values_at(:min, :max)
|
112
|
+
min && value < min || max && value > max
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|