cl 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|