travis-cl 1.2.4
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 +7 -0
- data/CHANGELOG.md +134 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +59 -0
- data/MIT_LICENSE.md +21 -0
- data/README.md +1283 -0
- data/cl.gemspec +30 -0
- data/examples/README.md +22 -0
- data/examples/_src/args/cast.erb.rb +100 -0
- data/examples/_src/args/opts.erb.rb +100 -0
- data/examples/_src/args/required.erb.rb +63 -0
- data/examples/_src/args/splat.erb.rb +55 -0
- data/examples/_src/gem.erb.rb +99 -0
- data/examples/_src/heroku.erb.rb +47 -0
- data/examples/_src/rakeish.erb.rb +54 -0
- data/examples/_src/readme/abstract.erb.rb +27 -0
- data/examples/_src/readme/alias.erb.rb +22 -0
- data/examples/_src/readme/arg.erb.rb +21 -0
- data/examples/_src/readme/arg_array.erb.rb +21 -0
- data/examples/_src/readme/arg_type.erb.rb +23 -0
- data/examples/_src/readme/args_splat.erb.rb +55 -0
- data/examples/_src/readme/array.erb.rb +21 -0
- data/examples/_src/readme/basic.erb.rb +72 -0
- data/examples/_src/readme/default.erb.rb +21 -0
- data/examples/_src/readme/deprecated.erb.rb +21 -0
- data/examples/_src/readme/deprecated_alias.erb.rb +21 -0
- data/examples/_src/readme/description.erb.rb +60 -0
- data/examples/_src/readme/downcase.erb.rb +21 -0
- data/examples/_src/readme/enum.erb.rb +35 -0
- data/examples/_src/readme/example.erb.rb +25 -0
- data/examples/_src/readme/format.erb.rb +35 -0
- data/examples/_src/readme/internal.erb.rb +28 -0
- data/examples/_src/readme/negate.erb.rb +37 -0
- data/examples/_src/readme/note.erb.rb +25 -0
- data/examples/_src/readme/opts.erb.rb +33 -0
- data/examples/_src/readme/opts_block.erb.rb +30 -0
- data/examples/_src/readme/range.erb.rb +35 -0
- data/examples/_src/readme/registry.erb.rb +18 -0
- data/examples/_src/readme/required.erb.rb +35 -0
- data/examples/_src/readme/requireds.erb.rb +46 -0
- data/examples/_src/readme/requires.erb.rb +35 -0
- data/examples/_src/readme/runner.erb.rb +29 -0
- data/examples/_src/readme/runner_custom.erb.rb +25 -0
- data/examples/_src/readme/secret.erb.rb +22 -0
- data/examples/_src/readme/see.erb.rb +25 -0
- data/examples/_src/readme/type.erb.rb +21 -0
- data/examples/args/cast +98 -0
- data/examples/args/opts +98 -0
- data/examples/args/required +62 -0
- data/examples/args/splat +58 -0
- data/examples/gem +97 -0
- data/examples/heroku +48 -0
- data/examples/rakeish +50 -0
- data/examples/readme/abstract +28 -0
- data/examples/readme/alias +21 -0
- data/examples/readme/arg +20 -0
- data/examples/readme/arg_array +20 -0
- data/examples/readme/arg_type +22 -0
- data/examples/readme/args_splat +58 -0
- data/examples/readme/array +20 -0
- data/examples/readme/basic +67 -0
- data/examples/readme/default +20 -0
- data/examples/readme/deprecated +20 -0
- data/examples/readme/deprecated_alias +20 -0
- data/examples/readme/description +56 -0
- data/examples/readme/downcase +20 -0
- data/examples/readme/enum +33 -0
- data/examples/readme/example +21 -0
- data/examples/readme/format +33 -0
- data/examples/readme/internal +24 -0
- data/examples/readme/negate +44 -0
- data/examples/readme/note +21 -0
- data/examples/readme/opts +31 -0
- data/examples/readme/opts_block +29 -0
- data/examples/readme/range +33 -0
- data/examples/readme/registry +15 -0
- data/examples/readme/required +33 -0
- data/examples/readme/requireds +46 -0
- data/examples/readme/requires +33 -0
- data/examples/readme/runner +30 -0
- data/examples/readme/runner_custom +22 -0
- data/examples/readme/secret +21 -0
- data/examples/readme/see +21 -0
- data/examples/readme/type +20 -0
- data/lib/cl/arg.rb +79 -0
- data/lib/cl/args.rb +92 -0
- data/lib/cl/cast.rb +55 -0
- data/lib/cl/cmd.rb +74 -0
- data/lib/cl/config/env.rb +52 -0
- data/lib/cl/config/files.rb +34 -0
- data/lib/cl/config.rb +30 -0
- data/lib/cl/ctx.rb +36 -0
- data/lib/cl/dsl.rb +182 -0
- data/lib/cl/errors.rb +119 -0
- data/lib/cl/help/cmd.rb +118 -0
- data/lib/cl/help/cmds.rb +26 -0
- data/lib/cl/help/format.rb +69 -0
- data/lib/cl/help/table.rb +58 -0
- data/lib/cl/help/usage.rb +26 -0
- data/lib/cl/help.rb +37 -0
- data/lib/cl/helper/suggest.rb +10 -0
- data/lib/cl/helper.rb +47 -0
- data/lib/cl/opt.rb +276 -0
- data/lib/cl/opts/validate.rb +117 -0
- data/lib/cl/opts.rb +114 -0
- data/lib/cl/parser/format.rb +63 -0
- data/lib/cl/parser.rb +70 -0
- data/lib/cl/runner/default.rb +86 -0
- data/lib/cl/runner/multi.rb +34 -0
- data/lib/cl/runner.rb +10 -0
- data/lib/cl/ui.rb +146 -0
- data/lib/cl/version.rb +3 -0
- data/lib/cl.rb +62 -0
- metadata +177 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
$: << File.expand_path('lib')
|
|
3
|
+
|
|
4
|
+
class Add < Cl::Cmd
|
|
5
|
+
register :add
|
|
6
|
+
|
|
7
|
+
# read DNF, i.e. "token OR user AND pass
|
|
8
|
+
required :token, [:user, :pass]
|
|
9
|
+
|
|
10
|
+
opt '--token TOKEN'
|
|
11
|
+
opt '--user NAME'
|
|
12
|
+
opt '--pass PASS'
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
p token: token, user: user, pass: pass
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
Cl.new('owners').run(%w(add --token token))
|
|
20
|
+
|
|
21
|
+
# Output:
|
|
22
|
+
#
|
|
23
|
+
# {:token=>"token", :user=>nil, :pass=>nil}
|
|
24
|
+
|
|
25
|
+
Cl.new('owners').run(%w(add --user user --pass pass))
|
|
26
|
+
|
|
27
|
+
# Output:
|
|
28
|
+
#
|
|
29
|
+
# {:token=>nil, :user=>"user", :pass=>"pass"}
|
|
30
|
+
|
|
31
|
+
Cl.new('owners').run(%w(add))
|
|
32
|
+
|
|
33
|
+
# Output:
|
|
34
|
+
#
|
|
35
|
+
# Missing options: token, or user and pass
|
|
36
|
+
#
|
|
37
|
+
# Usage: owners add [options]
|
|
38
|
+
#
|
|
39
|
+
# Options:
|
|
40
|
+
#
|
|
41
|
+
# Either token, or user and pass are required.
|
|
42
|
+
#
|
|
43
|
+
# --token TOKEN type: string
|
|
44
|
+
# --user NAME type: string
|
|
45
|
+
# --pass PASS type: string
|
|
46
|
+
# --help Get help on this command
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
$: << File.expand_path('lib')
|
|
3
|
+
|
|
4
|
+
class Add < Cl::Cmd
|
|
5
|
+
register :add
|
|
6
|
+
|
|
7
|
+
opt '--to GROUP'
|
|
8
|
+
opt '--other GROUP', requires: :to
|
|
9
|
+
|
|
10
|
+
def run
|
|
11
|
+
p to: to, other: other
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
Cl.new('owners').run(%w(add --to one --other two))
|
|
16
|
+
|
|
17
|
+
# Output:
|
|
18
|
+
#
|
|
19
|
+
# {:to=>"one", :other=>"two"}
|
|
20
|
+
|
|
21
|
+
Cl.new('owners').run(%w(add --other two))
|
|
22
|
+
|
|
23
|
+
# Output:
|
|
24
|
+
#
|
|
25
|
+
# Missing option: to (required by other)
|
|
26
|
+
#
|
|
27
|
+
# Usage: owners add [options]
|
|
28
|
+
#
|
|
29
|
+
# Options:
|
|
30
|
+
#
|
|
31
|
+
# --to GROUP type: string
|
|
32
|
+
# --other GROUP type: string, requires: to
|
|
33
|
+
# --help Get help on this command
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
arg :branch
|
|
9
|
+
|
|
10
|
+
def run
|
|
11
|
+
p cmd: registry_key, args: args
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# With this class registered (and assuming the executable that calls `Cl` is
|
|
17
|
+
# `bin/run`) the default runner would recognize and run it:
|
|
18
|
+
#
|
|
19
|
+
# $ bin/run git:pull master # instantiates Git::Pull, and passes ["master"] as args
|
|
20
|
+
# $ bin/run git pull master # does the same
|
|
21
|
+
|
|
22
|
+
Cl.new('run').run(%w(git:pull master))
|
|
23
|
+
# Output:
|
|
24
|
+
#
|
|
25
|
+
# {:cmd=>:"git:pull", :args=>["master"]}
|
|
26
|
+
|
|
27
|
+
Cl.new('run').run(%w(git pull master))
|
|
28
|
+
# Output:
|
|
29
|
+
#
|
|
30
|
+
# {: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
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
$: << File.expand_path('lib')
|
|
3
|
+
|
|
4
|
+
class Add < Cl::Cmd
|
|
5
|
+
register :add
|
|
6
|
+
|
|
7
|
+
opt '--pass PASS', secret: true
|
|
8
|
+
|
|
9
|
+
def run
|
|
10
|
+
p(
|
|
11
|
+
secret?: self.class.opts[:pass].secret?,
|
|
12
|
+
tainted?: pass
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
Cl.new('owners').run(%w(add --pass pass))
|
|
18
|
+
|
|
19
|
+
# Output:
|
|
20
|
+
#
|
|
21
|
+
# {:secret?=>true, :tainted?=>true}
|
data/examples/readme/see
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
$: << File.expand_path('lib')
|
|
3
|
+
|
|
4
|
+
require 'cl'
|
|
5
|
+
|
|
6
|
+
class Add < Cl::Cmd
|
|
7
|
+
register :add
|
|
8
|
+
|
|
9
|
+
opt '--to GROUP', see: 'https://docs.io/cli/owners/add'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
Cl.new('owners').run(%w(add --help))
|
|
13
|
+
|
|
14
|
+
# Output:
|
|
15
|
+
#
|
|
16
|
+
# Usage: owners add [options]
|
|
17
|
+
#
|
|
18
|
+
# Options:
|
|
19
|
+
#
|
|
20
|
+
# --to GROUP type: string, see: https://docs.io/cli/owners/add
|
|
21
|
+
# --help Get help on this command
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
$: << File.expand_path('lib')
|
|
3
|
+
|
|
4
|
+
class Add < Cl::Cmd
|
|
5
|
+
register :add
|
|
6
|
+
|
|
7
|
+
opt '--active BOOL', type: :boolean
|
|
8
|
+
opt '--retries INT', type: :integer
|
|
9
|
+
opt '--sleep FLOAT', type: :float
|
|
10
|
+
|
|
11
|
+
def run
|
|
12
|
+
p active: active.class, retries: retries.class, sleep: sleep.class
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Cl.new('owners').run(%w(add --active yes --retries 1 --sleep 0.1))
|
|
17
|
+
|
|
18
|
+
# Output:
|
|
19
|
+
#
|
|
20
|
+
# {:active=>TrueClass, :retries=>Integer, :sleep=>Float}
|
data/lib/cl/arg.rb
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
require 'cl/cast'
|
|
2
|
+
|
|
3
|
+
class Cl
|
|
4
|
+
class Arg < Struct.new(:name, :opts)
|
|
5
|
+
include Cast
|
|
6
|
+
|
|
7
|
+
def define(const)
|
|
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.send(:include, mod)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def set(cmd, value)
|
|
15
|
+
value = cast(value)
|
|
16
|
+
unknown(value) if enum? && !known?(value)
|
|
17
|
+
cmd.send(:"#{name}=", value)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def type
|
|
21
|
+
opts[:type] || :string
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def array?
|
|
25
|
+
type == :array
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def description
|
|
29
|
+
opts[:description]
|
|
30
|
+
end
|
|
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
|
+
|
|
52
|
+
def required?
|
|
53
|
+
!!opts[:required]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def separator
|
|
57
|
+
opts[:sep]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def splat?
|
|
61
|
+
!!opts[:splat] && array?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def unknown(value)
|
|
65
|
+
raise UnknownArgumentValue.new(value, enum.join(', '))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_s
|
|
69
|
+
str = name
|
|
70
|
+
case type
|
|
71
|
+
when :array then str = "#{str}.."
|
|
72
|
+
when :boolean, :bool then str = "#{str}:bool"
|
|
73
|
+
when :integer, :int then str = "#{str}:int"
|
|
74
|
+
when :float then str = "#{str}:float"
|
|
75
|
+
end
|
|
76
|
+
required? ? str : "[#{str}]"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/cl/args.rb
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require 'cl/arg'
|
|
2
|
+
|
|
3
|
+
class Cl
|
|
4
|
+
class Args
|
|
5
|
+
include Enumerable
|
|
6
|
+
|
|
7
|
+
def define(const, name, *args)
|
|
8
|
+
opts = args.last.is_a?(Hash) ? args.pop.dup : {}
|
|
9
|
+
opts[:description] = args.shift if args.any?
|
|
10
|
+
|
|
11
|
+
arg = Arg.new(name, opts)
|
|
12
|
+
arg.define(const)
|
|
13
|
+
self.args << arg
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def apply(cmd, values, opts)
|
|
17
|
+
values = splat(values) if splat?
|
|
18
|
+
values = default(values) if default?
|
|
19
|
+
validate(values)
|
|
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)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def each(&block)
|
|
26
|
+
args.each(&block)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def index(*args, &block)
|
|
30
|
+
self.args.index(*args, &block)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
attr_writer :args
|
|
34
|
+
|
|
35
|
+
def args
|
|
36
|
+
@args ||= []
|
|
37
|
+
end
|
|
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
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def validate(args)
|
|
52
|
+
# raise ArgumentError.new(:unknown_arg, arg) if unknown?(arg)
|
|
53
|
+
raise ArgumentError.new(:missing_args, args.size, required) if args.size < required
|
|
54
|
+
raise ArgumentError.new(:too_many_args, args.join(' '), args.size, allowed) if args.size > allowed && !splat?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def allowed
|
|
58
|
+
args.size
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def splat?
|
|
62
|
+
any?(&:splat?)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def default?
|
|
66
|
+
any?(&:default?)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def required
|
|
70
|
+
select(&:required?).size
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def splat(values)
|
|
74
|
+
args.each.with_index.inject([]) do |group, (arg, ix)|
|
|
75
|
+
count = arg && arg.splat? ? [values.size - args.size + ix + 1] : []
|
|
76
|
+
count = 0 if count.first.to_i < 0
|
|
77
|
+
group << values.shift(*count)
|
|
78
|
+
end
|
|
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
|
|
91
|
+
end
|
|
92
|
+
end
|
data/lib/cl/cast.rb
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
class Cl
|
|
2
|
+
module Cast
|
|
3
|
+
class Cast < Struct.new(:type, :value, :opts)
|
|
4
|
+
TRUE = /^(true|yes|on)$/
|
|
5
|
+
FALSE = /^(false|no|off)$/
|
|
6
|
+
|
|
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)
|
|
12
|
+
end
|
|
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) if value
|
|
35
|
+
end
|
|
36
|
+
alias integer int
|
|
37
|
+
|
|
38
|
+
def float
|
|
39
|
+
Float(value) if 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
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def cast(value)
|
|
52
|
+
type ? Cast.new(type, value, separator: separator).apply : value
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
data/lib/cl/cmd.rb
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
require 'registry'
|
|
2
|
+
require 'cl'
|
|
3
|
+
require 'cl/args'
|
|
4
|
+
require 'cl/dsl'
|
|
5
|
+
require 'cl/opts'
|
|
6
|
+
require 'cl/parser'
|
|
7
|
+
|
|
8
|
+
class Cl
|
|
9
|
+
# Base class for all command classes that can be run.
|
|
10
|
+
#
|
|
11
|
+
# Inherit your command classes from this class, use the {Cl::Cmd::Dsl} to
|
|
12
|
+
# declare arguments, options, summary, description, examples etc., and
|
|
13
|
+
# implement the method #run.
|
|
14
|
+
#
|
|
15
|
+
# See {Cl::Cmd::Dsl} for details on the DSL methods.
|
|
16
|
+
class Cmd
|
|
17
|
+
include Registry
|
|
18
|
+
extend Dsl
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
include Merge, Suggest, Underscore
|
|
22
|
+
|
|
23
|
+
attr_accessor :auto_register
|
|
24
|
+
|
|
25
|
+
inherited = ->(const) do
|
|
26
|
+
if const.name && Cmd.auto_register
|
|
27
|
+
key = underscore(const.name.split('::').last)
|
|
28
|
+
key = [registry_key, key].compact.join(':') unless abstract?
|
|
29
|
+
const.register(key)
|
|
30
|
+
end
|
|
31
|
+
const.define_singleton_method(:inherited, &inherited)
|
|
32
|
+
end
|
|
33
|
+
define_method(:inherited, &inherited)
|
|
34
|
+
|
|
35
|
+
def cmds
|
|
36
|
+
registry.values.uniq
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def parse(ctx, cmd, args)
|
|
40
|
+
parser = Parser.new(cmd, args)
|
|
41
|
+
args, opts = parser.args, parser.opts unless self == Help
|
|
42
|
+
opts = merge(ctx.config[registry_key], opts) if ctx.config[registry_key]
|
|
43
|
+
[args, opts || {}]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def suggestions(opt)
|
|
47
|
+
suggest(opts.map(&:name), opt.sub(/^--/, ''))
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
self.auto_register = true
|
|
52
|
+
|
|
53
|
+
abstract
|
|
54
|
+
|
|
55
|
+
opt '--help', 'Get help on this command'
|
|
56
|
+
|
|
57
|
+
attr_reader :ctx, :args
|
|
58
|
+
|
|
59
|
+
def initialize(ctx, args)
|
|
60
|
+
@ctx = ctx
|
|
61
|
+
args, opts = self.class.parse(ctx, self, args)
|
|
62
|
+
@opts = self.class.opts.apply(self, self.opts.merge(opts))
|
|
63
|
+
@args = self.class.args.apply(self, args, opts) unless help? && !is_a?(Help)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def opts
|
|
67
|
+
@opts ||= {}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def deprecations
|
|
71
|
+
@deprecations ||= {}
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'cl/helper'
|
|
2
|
+
|
|
3
|
+
class Cl
|
|
4
|
+
class Config
|
|
5
|
+
class Env < Struct.new(:name)
|
|
6
|
+
include Merge
|
|
7
|
+
|
|
8
|
+
TRUE = /^(true|yes|on)$/
|
|
9
|
+
FALSE = /^(false|no|off)$/
|
|
10
|
+
|
|
11
|
+
def load
|
|
12
|
+
vars = opts.map { |cmd, opts| vars(cmd, opts) }
|
|
13
|
+
merge(*vars.flatten.compact)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def vars(cmd, opts)
|
|
19
|
+
opts.map { |opt| var(cmd, opt, key(cmd, opt)) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def opts
|
|
23
|
+
Cmd.registry.map { |key, cmd| [key, cmd.opts.map(&:name) - [:help]] }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def var(cmd, opt, key)
|
|
27
|
+
{ cmd => { opt => cast(ENV[key]) } } if ENV[key]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def key(*keys)
|
|
31
|
+
[name.upcase, *keys].join('_').upcase.sub('-', '_')
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def only(hash, *keys)
|
|
35
|
+
hash.select { |key, _| keys.include?(key) }.to_h
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def cast(value)
|
|
39
|
+
case value
|
|
40
|
+
when TRUE
|
|
41
|
+
true
|
|
42
|
+
when FALSE
|
|
43
|
+
false
|
|
44
|
+
when ''
|
|
45
|
+
false
|
|
46
|
+
else
|
|
47
|
+
value
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
require 'cl/helper'
|
|
3
|
+
|
|
4
|
+
class Cl
|
|
5
|
+
class Config
|
|
6
|
+
class Files < Struct.new(:name)
|
|
7
|
+
include Merge
|
|
8
|
+
|
|
9
|
+
PATHS = %w(
|
|
10
|
+
~/.%s.yml
|
|
11
|
+
./.%s.yml
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
def load
|
|
15
|
+
configs.any? ? symbolize(merge(*configs)) : {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def configs
|
|
21
|
+
@configs ||= paths.map { |path| YAML.load_file(path) || {} }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def paths
|
|
25
|
+
paths = PATHS.map { |path| File.expand_path(path % name) }
|
|
26
|
+
paths.select { |path| File.exist?(path) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def symbolize(hash)
|
|
30
|
+
hash.map { |key, value| [key.to_sym, value] }.to_h
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
data/lib/cl/config.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
require 'cl/config/env'
|
|
2
|
+
require 'cl/config/files'
|
|
3
|
+
require 'cl/helper'
|
|
4
|
+
|
|
5
|
+
class Cl
|
|
6
|
+
class Config
|
|
7
|
+
include Merge
|
|
8
|
+
|
|
9
|
+
attr_reader :name, :opts
|
|
10
|
+
|
|
11
|
+
def initialize(name)
|
|
12
|
+
@name = name
|
|
13
|
+
@opts = load
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_h
|
|
17
|
+
opts
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def load
|
|
23
|
+
merge(*sources.map(&:load))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def sources
|
|
27
|
+
[Files.new(name), Env.new(name)]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/lib/cl/ctx.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
require 'forwardable'
|
|
2
|
+
require 'cl/config'
|
|
3
|
+
require 'cl/ui'
|
|
4
|
+
|
|
5
|
+
class Cl
|
|
6
|
+
class Ctx
|
|
7
|
+
extend Forwardable
|
|
8
|
+
|
|
9
|
+
def_delegators :ui, :puts, :stdout, :announce, :info, :notice, :warn,
|
|
10
|
+
:error, :success, :cmd
|
|
11
|
+
|
|
12
|
+
attr_accessor :config, :name, :opts
|
|
13
|
+
|
|
14
|
+
def initialize(name, opts = {})
|
|
15
|
+
@config = Config.new(name).to_h
|
|
16
|
+
@opts = opts
|
|
17
|
+
@name = name
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def ui
|
|
21
|
+
@ui ||= opts[:ui] || Ui.new(self, opts)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def abort(error, *strs)
|
|
25
|
+
abort? ? ui.abort(error, *strs) : raise(error)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def abort?
|
|
29
|
+
!opts[:abort].is_a?(FalseClass)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test?
|
|
33
|
+
ENV['ENV'] == 'test'
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|