cl 0.0.4 → 0.1.0
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 +47 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +22 -12
- data/NOTES.md +2 -24
- data/README.md +1 -1
- data/examples/args/cast +82 -0
- data/examples/args/opts +36 -0
- data/examples/args/required +46 -0
- data/examples/args/splat +61 -0
- data/examples/gem +104 -0
- data/examples/heroku +49 -0
- data/examples/{multi.rb → rakeish} +13 -11
- data/lib/cl/arg.rb +12 -30
- data/lib/cl/args.rb +8 -5
- data/lib/cl/cast.rb +29 -0
- data/lib/cl/cmd.rb +65 -17
- 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 +29 -0
- data/lib/cl/help/cmd.rb +142 -0
- data/lib/cl/help/cmds.rb +28 -0
- data/lib/cl/help/table.rb +45 -0
- data/lib/cl/{format → help}/usage.rb +10 -4
- data/lib/cl/help.rb +10 -12
- data/lib/cl/helper.rb +11 -0
- data/lib/cl/opt.rb +141 -0
- data/lib/cl/opts.rb +155 -0
- data/lib/cl/parser.rb +39 -0
- data/lib/cl/runner/default.rb +35 -19
- data/lib/cl/runner/multi.rb +6 -7
- data/lib/cl/ui.rb +137 -0
- data/lib/cl/version.rb +2 -2
- data/lib/cl.rb +64 -19
- metadata +40 -18
- data/examples/args/cast.rb +0 -63
- data/examples/args/opts.rb +0 -32
- data/examples/args/required.rb +0 -35
- data/examples/args/splat.rb +0 -52
- data/examples/gem.rb +0 -81
- data/examples/heroku.rb +0 -41
- data/lib/cl/format/cmd.rb +0 -31
- data/lib/cl/format/list.rb +0 -22
- data/lib/cl/format/table.rb +0 -19
- data/lib/cl/options.rb +0 -13
- data/lib/cl/registry.rb +0 -53
data/lib/cl/opt.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'cl/cast'
|
2
|
+
|
3
|
+
class Cl
|
4
|
+
class Opt < Struct.new(:strs, :opts, :block)
|
5
|
+
include Cast
|
6
|
+
|
7
|
+
OPT = /^--(?:\[.*\])?(.*)$/
|
8
|
+
|
9
|
+
TYPES = {
|
10
|
+
int: :integer,
|
11
|
+
str: :string,
|
12
|
+
bool: :flag,
|
13
|
+
boolean: :flag
|
14
|
+
}
|
15
|
+
|
16
|
+
def initialize(*)
|
17
|
+
super
|
18
|
+
noize(strs) if type == :flag && default.is_a?(TrueClass)
|
19
|
+
end
|
20
|
+
|
21
|
+
def define(const)
|
22
|
+
return unless __key__ = name
|
23
|
+
const.include Module.new {
|
24
|
+
define_method (__key__) { opts[__key__] }
|
25
|
+
define_method (:"#{__key__}?") { !!opts[__key__] }
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def name
|
30
|
+
return @name if instance_variable_defined?(:@name)
|
31
|
+
opt = strs.detect { |str| str.start_with?('--') }
|
32
|
+
name = opt.split(' ').first.match(OPT)[1] if opt
|
33
|
+
@name = name&.sub('-', '_')&.to_sym
|
34
|
+
end
|
35
|
+
|
36
|
+
def type
|
37
|
+
TYPES[opts[:type]] || opts[:type] || infer_type
|
38
|
+
end
|
39
|
+
|
40
|
+
def infer_type
|
41
|
+
strs.any? { |str| str.split(' ').size > 1 } ? :string : :flag
|
42
|
+
end
|
43
|
+
|
44
|
+
def int?
|
45
|
+
type == :int || type == :integer
|
46
|
+
end
|
47
|
+
|
48
|
+
def description
|
49
|
+
opts[:description]
|
50
|
+
end
|
51
|
+
|
52
|
+
def aliases?
|
53
|
+
!!opts[:alias]
|
54
|
+
end
|
55
|
+
|
56
|
+
def aliases
|
57
|
+
Array(opts[:alias])
|
58
|
+
end
|
59
|
+
|
60
|
+
def deprecated?
|
61
|
+
!!opts[:deprecated]
|
62
|
+
end
|
63
|
+
|
64
|
+
def deprecated
|
65
|
+
return [name] if opts[:deprecated].is_a?(TrueClass)
|
66
|
+
Array(opts[:deprecated]) if opts[:deprecated]
|
67
|
+
end
|
68
|
+
|
69
|
+
def default?
|
70
|
+
!!default
|
71
|
+
end
|
72
|
+
|
73
|
+
def default
|
74
|
+
opts[:default]
|
75
|
+
end
|
76
|
+
|
77
|
+
def enum?
|
78
|
+
!!opts[:enum]
|
79
|
+
end
|
80
|
+
|
81
|
+
def enum
|
82
|
+
Array(opts[:enum])
|
83
|
+
end
|
84
|
+
|
85
|
+
def known?(value)
|
86
|
+
enum.include?(value)
|
87
|
+
end
|
88
|
+
|
89
|
+
def format?
|
90
|
+
!!opts[:format]
|
91
|
+
end
|
92
|
+
|
93
|
+
def format
|
94
|
+
opts[:format].to_s.sub('(?-mix:', '').sub(/\)$/, '')
|
95
|
+
end
|
96
|
+
|
97
|
+
def formatted?(value)
|
98
|
+
opts[:format] =~ value
|
99
|
+
end
|
100
|
+
|
101
|
+
def max?
|
102
|
+
int? && !!opts[:max]
|
103
|
+
end
|
104
|
+
|
105
|
+
def max
|
106
|
+
opts[:max]
|
107
|
+
end
|
108
|
+
|
109
|
+
def required?
|
110
|
+
!!opts[:required]
|
111
|
+
end
|
112
|
+
|
113
|
+
def requires?
|
114
|
+
!!opts[:requires]
|
115
|
+
end
|
116
|
+
|
117
|
+
def requires
|
118
|
+
Array(opts[:requires])
|
119
|
+
end
|
120
|
+
|
121
|
+
def block
|
122
|
+
# raise if no block was given, and the option's name cannot be inferred
|
123
|
+
super || method(:assign)
|
124
|
+
end
|
125
|
+
|
126
|
+
def assign(opts, type, name, value)
|
127
|
+
if type == :array
|
128
|
+
opts[name] ||= []
|
129
|
+
opts[name] << value
|
130
|
+
else
|
131
|
+
opts[name] = value
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def noize(strs)
|
136
|
+
strs = strs.select { |str| str.start_with?('--') }
|
137
|
+
strs = strs.reject { |str| str.include?('[no-]') }
|
138
|
+
strs.each { |str| str.replace(str.sub('--', '--[no-]')) }
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/lib/cl/opts.rb
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'cl/opt'
|
2
|
+
|
3
|
+
class Cl
|
4
|
+
class Opts
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def define(const, *args, &block)
|
8
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
9
|
+
strs = args.select { |arg| arg.start_with?('-') }
|
10
|
+
opts[:description] = args.-(strs).first
|
11
|
+
|
12
|
+
opt = Opt.new(strs, opts, block)
|
13
|
+
opt.define(const)
|
14
|
+
self << opt
|
15
|
+
end
|
16
|
+
|
17
|
+
def apply(cmd, opts)
|
18
|
+
opts = with_defaults(cmd, opts)
|
19
|
+
opts = cast(opts)
|
20
|
+
validate(cmd, opts) unless opts[:help]
|
21
|
+
opts
|
22
|
+
end
|
23
|
+
|
24
|
+
def <<(opt)
|
25
|
+
# keep the --help option at the end for help output
|
26
|
+
opts.empty? ? opts << opt : opts.insert(-2, opt)
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](key)
|
30
|
+
opts.detect { |opt| opt.name == key }
|
31
|
+
end
|
32
|
+
|
33
|
+
def each(&block)
|
34
|
+
opts.each(&block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_a
|
38
|
+
opts
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_writer :opts
|
42
|
+
|
43
|
+
def opts
|
44
|
+
@opts ||= []
|
45
|
+
end
|
46
|
+
|
47
|
+
def deprecated
|
48
|
+
map(&:deprecated).flatten.compact
|
49
|
+
end
|
50
|
+
|
51
|
+
def dup
|
52
|
+
super.tap { |obj| obj.opts = opts.dup }
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def validate(cmd, opts)
|
58
|
+
validate_requireds(cmd, opts)
|
59
|
+
validate_required(opts)
|
60
|
+
validate_requires(opts)
|
61
|
+
validate_max(opts)
|
62
|
+
validate_format(opts)
|
63
|
+
validate_enum(opts)
|
64
|
+
end
|
65
|
+
|
66
|
+
def validate_requireds(cmd, opts)
|
67
|
+
opts = missing_requireds(cmd, opts)
|
68
|
+
raise RequiredsOpts.new(opts) if opts.any?
|
69
|
+
end
|
70
|
+
|
71
|
+
def validate_required(opts)
|
72
|
+
opts = missing_required(opts)
|
73
|
+
# make sure we do not accept unnamed required options
|
74
|
+
raise RequiredOpts.new(opts.map(&:name)) if opts.any?
|
75
|
+
end
|
76
|
+
|
77
|
+
def validate_requires(opts)
|
78
|
+
opts = missing_requires(opts)
|
79
|
+
raise RequiresOpts.new(invert(opts)) if opts.any?
|
80
|
+
end
|
81
|
+
|
82
|
+
def validate_max(opts)
|
83
|
+
opts = exceeding_max(opts)
|
84
|
+
raise ExceedingMax.new(opts) if opts.any?
|
85
|
+
end
|
86
|
+
|
87
|
+
def validate_format(opts)
|
88
|
+
opts = invalid_format(opts)
|
89
|
+
raise InvalidFormat.new(opts) if opts.any?
|
90
|
+
end
|
91
|
+
|
92
|
+
def validate_enum(opts)
|
93
|
+
opts = unknown_values(opts)
|
94
|
+
raise UnknownValues.new(opts) if opts.any?
|
95
|
+
end
|
96
|
+
|
97
|
+
def missing_requireds(cmd, opts)
|
98
|
+
opts = cmd.class.required.map do |alts|
|
99
|
+
alts if alts.none? { |alt| Array(alt).all? { |key| opts.key?(key) } }
|
100
|
+
end.compact
|
101
|
+
end
|
102
|
+
|
103
|
+
def missing_required(opts)
|
104
|
+
select(&:required?).select { |opt| !opts.key?(opt.name) }
|
105
|
+
end
|
106
|
+
|
107
|
+
def missing_requires(opts)
|
108
|
+
select(&:requires?).map do |opt|
|
109
|
+
missing = opt.requires.select { |key| !opts.key?(key) }
|
110
|
+
[opt.name, missing] if missing.any?
|
111
|
+
end.compact
|
112
|
+
end
|
113
|
+
|
114
|
+
def exceeding_max(opts)
|
115
|
+
select(&:max?).map do |opt|
|
116
|
+
[opt.name, opt.max] if opts[opt.name] > opt.max
|
117
|
+
end.compact
|
118
|
+
end
|
119
|
+
|
120
|
+
def invalid_format(opts)
|
121
|
+
select(&:format?).map do |opt|
|
122
|
+
[opt.name, opt.format] unless opt.formatted?(opts[opt.name])
|
123
|
+
end.compact
|
124
|
+
end
|
125
|
+
|
126
|
+
def unknown_values(opts)
|
127
|
+
select(&:enum?).map do |opt|
|
128
|
+
[opt.name, opts[opt.name], opt.enum] unless opt.known?(opts[opt.name])
|
129
|
+
end.compact
|
130
|
+
end
|
131
|
+
|
132
|
+
def with_defaults(cmd, opts)
|
133
|
+
select(&:default?).inject(opts) do |opts, opt|
|
134
|
+
next opts if opts.key?(opt.name)
|
135
|
+
value = opt.default
|
136
|
+
value = resolve(cmd, opts, value) if value.is_a?(Symbol)
|
137
|
+
opts.merge(opt.name => value)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def resolve(cmd, opts, key)
|
142
|
+
opts[key] || cmd.respond_to?(key) && cmd.send(key)
|
143
|
+
end
|
144
|
+
|
145
|
+
def cast(opts)
|
146
|
+
opts.map do |key, value|
|
147
|
+
[key, self[key] ? self[key].cast(value) : value]
|
148
|
+
end.to_h
|
149
|
+
end
|
150
|
+
|
151
|
+
def invert(hash)
|
152
|
+
hash.map { |key, obj| Array(obj).map { |obj| [obj, key] } }.flatten(1).to_h
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
data/lib/cl/parser.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
class Cl
|
4
|
+
class Parser < OptionParser
|
5
|
+
attr_reader :opts
|
6
|
+
|
7
|
+
def initialize(opts, args)
|
8
|
+
@opts = {}
|
9
|
+
|
10
|
+
super do
|
11
|
+
opts.each do |opt|
|
12
|
+
on(*opt.strs) do |value|
|
13
|
+
set(opt, value)
|
14
|
+
end
|
15
|
+
|
16
|
+
opt.aliases.each do |name|
|
17
|
+
on(aliased(opt, name)) do |value|
|
18
|
+
@opts[name] = set(opt, value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
parse!(args)
|
25
|
+
end
|
26
|
+
|
27
|
+
def aliased(opt, name)
|
28
|
+
str = opt.strs.detect { |str| str.start_with?('--') } || raise
|
29
|
+
str.sub(opt.name.to_s, name.to_s)
|
30
|
+
end
|
31
|
+
|
32
|
+
# should consider negative arities (e.g. |one, *two|)
|
33
|
+
def set(opt, value)
|
34
|
+
args = [opts, opt.type, opt.name, value]
|
35
|
+
args = args[-opt.block.arity, opt.block.arity]
|
36
|
+
instance_exec(*args, &opt.block)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/cl/runner/default.rb
CHANGED
@@ -1,41 +1,57 @@
|
|
1
|
-
require '
|
1
|
+
require 'forwardable'
|
2
|
+
require 'cl/ctx'
|
3
|
+
require 'cl/parser'
|
4
|
+
require 'cl/helper'
|
2
5
|
|
3
|
-
|
6
|
+
class Cl
|
4
7
|
module Runner
|
5
8
|
class Default
|
6
|
-
|
9
|
+
extend Forwardable
|
10
|
+
include Merge
|
7
11
|
|
8
|
-
|
9
|
-
|
10
|
-
|
12
|
+
def_delegators :ctx, :abort
|
13
|
+
|
14
|
+
attr_reader :ctx, :const, :args, :opts
|
15
|
+
|
16
|
+
def initialize(ctx, args)
|
17
|
+
@ctx = ctx
|
18
|
+
@const, @args = lookup(args)
|
19
|
+
# @opts, @args = parse(args)
|
11
20
|
end
|
12
21
|
|
13
22
|
def run
|
14
|
-
cmd.run
|
23
|
+
cmd.help? ? help.run : cmd.run
|
15
24
|
end
|
16
25
|
|
17
26
|
def cmd
|
18
|
-
const.new(
|
27
|
+
@cmd ||= const.new(ctx, args)
|
28
|
+
end
|
29
|
+
|
30
|
+
def help
|
31
|
+
Help.new(ctx, [cmd.registry_key])
|
19
32
|
end
|
20
33
|
|
21
34
|
private
|
22
35
|
|
23
36
|
def lookup(args)
|
24
|
-
|
25
|
-
cmd || abort("Unknown command: #{args.join(' ')}")
|
26
|
-
|
27
|
-
|
37
|
+
keys = expand(args) & Cmd.registry.keys.map(&:to_s)
|
38
|
+
cmd = Cmd[keys.last] || abort("Unknown command: #{args.join(' ')}")
|
39
|
+
[cmd, args - keys(cmd)]
|
40
|
+
end
|
41
|
+
|
42
|
+
def name
|
43
|
+
const.registry_key
|
28
44
|
end
|
29
45
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
name = name.sub(/#{arg}(:|$)/, '') if name =~ /#{arg}(:|$)/
|
34
|
-
end
|
46
|
+
def keys(cmd)
|
47
|
+
keys = cmd.registry_key.to_s.split(':')
|
48
|
+
keys.concat(expand(keys)).uniq
|
35
49
|
end
|
36
50
|
|
37
|
-
def
|
38
|
-
|
51
|
+
def expand(strs)
|
52
|
+
# strs = strs.reject { |str| str.start_with?('-') }
|
53
|
+
strs = strs.take_while { |str| !str.start_with?('-') }
|
54
|
+
strs.inject([]) { |strs, str| strs << [strs.last, str].compact.join(':') }
|
39
55
|
end
|
40
56
|
end
|
41
57
|
end
|
data/lib/cl/runner/multi.rb
CHANGED
@@ -1,11 +1,10 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
module Cl
|
1
|
+
class Cl
|
4
2
|
module Runner
|
5
3
|
class Multi
|
6
|
-
attr_reader :cmds
|
4
|
+
attr_reader :name, :cmds
|
7
5
|
|
8
|
-
def initialize(*args)
|
6
|
+
def initialize(name, *args)
|
7
|
+
@name = name
|
9
8
|
@cmds = build(group(args))
|
10
9
|
end
|
11
10
|
|
@@ -17,7 +16,7 @@ module Cl
|
|
17
16
|
|
18
17
|
def group(args, cmds = [])
|
19
18
|
args.flatten.map(&:to_s).inject([[]]) do |cmds, arg|
|
20
|
-
cmd =
|
19
|
+
cmd = Cmd.registered?(arg) ? Cmd[arg] : nil
|
21
20
|
cmd ? cmds << [cmd] : cmds.last << arg
|
22
21
|
cmds.reject(&:empty?)
|
23
22
|
end
|
@@ -25,7 +24,7 @@ module Cl
|
|
25
24
|
|
26
25
|
def build(cmds)
|
27
26
|
cmds.map do |(cmd, *args)|
|
28
|
-
cmd.new(
|
27
|
+
cmd.new(name, args)
|
29
28
|
end
|
30
29
|
end
|
31
30
|
end
|
data/lib/cl/ui.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
class Cl
|
4
|
+
module Ui
|
5
|
+
def self.new(ctx, opts)
|
6
|
+
const = Test if ctx.test?
|
7
|
+
const ||= Silent if opts[:silent]
|
8
|
+
const ||= Tty if $stdout.tty?
|
9
|
+
const ||= Pipe
|
10
|
+
const.new(opts)
|
11
|
+
end
|
12
|
+
|
13
|
+
class Base < Struct.new(:opts)
|
14
|
+
attr_writer :stdout
|
15
|
+
|
16
|
+
def stdout
|
17
|
+
@stdout ||= opts[:stdout] || $stdout
|
18
|
+
end
|
19
|
+
|
20
|
+
def puts(*str)
|
21
|
+
stdout.puts(*str)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class Silent < Base
|
26
|
+
%i(announce info notice warn error success cmd).each do |name|
|
27
|
+
define_method (name) { |*| }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class Test < Base
|
32
|
+
%i(announce info notice warn error success cmd).each do |name|
|
33
|
+
define_method (name) do |*args|
|
34
|
+
puts ["[#{name}]", *args.map(&:inspect)].join(' ')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def stdout
|
39
|
+
@stdout ||= StringIO.new
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Pipe < Base
|
44
|
+
%i(announce info notice warn error).each do |name|
|
45
|
+
define_method (name) do |msg, args = nil, _ = nil|
|
46
|
+
puts format_msg(msg, args)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
%i(success cmd).each do |name|
|
51
|
+
define_method (name) { |*| }
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def format_msg(msg, args)
|
57
|
+
msg = [msg, args].flatten.map(&:to_s)
|
58
|
+
msg = msg.map { |str| quote_spaced(str) }
|
59
|
+
msg.join(' ').strip
|
60
|
+
end
|
61
|
+
|
62
|
+
def quote_spaced(str)
|
63
|
+
str.include?(' ') ? %("#{str}") : str
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
module Colors
|
68
|
+
COLORS = {
|
69
|
+
red: "\e[31m",
|
70
|
+
green: "\e[32m",
|
71
|
+
yellow: "\e[33m",
|
72
|
+
blue: "\e[34m",
|
73
|
+
gray: "\e[37m",
|
74
|
+
reset: "\e[0m"
|
75
|
+
}
|
76
|
+
|
77
|
+
def colored(color, str)
|
78
|
+
[COLORS[color], str, COLORS[:reset]].join
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class Tty < Base
|
83
|
+
include Colors
|
84
|
+
|
85
|
+
def announce(msg, args = [], msgs = [])
|
86
|
+
msg = format_msg(msg, args, msgs)
|
87
|
+
puts colored(:green, with_spacing(msg, true))
|
88
|
+
end
|
89
|
+
|
90
|
+
def info(msg, args = [], msgs = [])
|
91
|
+
msg = format_msg(msg, args, msgs)
|
92
|
+
puts colored(:blue, with_spacing(msg, true))
|
93
|
+
end
|
94
|
+
|
95
|
+
def notice(msg, args = [], msgs = [])
|
96
|
+
msg = format_msg(msg, args, msgs)
|
97
|
+
puts colored(:gray, with_spacing(msg, false))
|
98
|
+
end
|
99
|
+
|
100
|
+
def warn(msg, args = [], msgs = [])
|
101
|
+
msg = format_msg(msg, args, msgs)
|
102
|
+
puts colored(:yellow, with_spacing(msg, false))
|
103
|
+
end
|
104
|
+
|
105
|
+
def error(msg, args = [], msgs = [])
|
106
|
+
msg = format_msg(msg, args, msgs)
|
107
|
+
puts colored(:red, with_spacing(msg, true))
|
108
|
+
end
|
109
|
+
|
110
|
+
def success(msg)
|
111
|
+
announce(msg)
|
112
|
+
puts
|
113
|
+
end
|
114
|
+
|
115
|
+
def cmd(msg)
|
116
|
+
notice("$ #{msg}")
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def colored(color, str)
|
122
|
+
opts[:color] ? super : str
|
123
|
+
end
|
124
|
+
|
125
|
+
def format_msg(msg, args, msgs)
|
126
|
+
msg = msgs[msg] % args if msg.is_a?(Symbol)
|
127
|
+
msg.strip
|
128
|
+
end
|
129
|
+
|
130
|
+
def with_spacing(str, space)
|
131
|
+
str = "\n#{str}" if space && !@last
|
132
|
+
@last = space
|
133
|
+
str
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/lib/cl/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = '0.0
|
1
|
+
class Cl
|
2
|
+
VERSION = '0.1.0'
|
3
3
|
end
|
data/lib/cl.rb
CHANGED
@@ -3,12 +3,19 @@ require 'cl/help'
|
|
3
3
|
require 'cl/runner/default'
|
4
4
|
require 'cl/runner/multi'
|
5
5
|
|
6
|
-
|
6
|
+
class Cl
|
7
7
|
class Error < StandardError
|
8
8
|
MSGS = {
|
9
|
-
missing_args:
|
10
|
-
too_many_args:
|
11
|
-
wrong_type:
|
9
|
+
missing_args: 'Missing arguments (given: %s, required: %s)',
|
10
|
+
too_many_args: 'Too many arguments (given: %s, allowed: %s)',
|
11
|
+
wrong_type: 'Wrong argument type (given: %s, expected: %s)',
|
12
|
+
exceeding_max: 'Exceeds max value: %s',
|
13
|
+
invalid_format: 'Invalid format: %s',
|
14
|
+
unknown_values: 'Unknown value: %s',
|
15
|
+
required_opt: 'Missing required option: %s',
|
16
|
+
required_opts: 'Missing required options: %s',
|
17
|
+
requires_opt: 'Missing option: %s',
|
18
|
+
requires_opts: 'Missing options: %s',
|
12
19
|
}
|
13
20
|
|
14
21
|
def initialize(msg, *args)
|
@@ -17,29 +24,67 @@ module Cl
|
|
17
24
|
end
|
18
25
|
|
19
26
|
ArgumentError = Class.new(Error)
|
27
|
+
OptionError = Class.new(Error)
|
28
|
+
RequiredOpts = Class.new(OptionError)
|
20
29
|
|
21
|
-
|
22
|
-
|
30
|
+
class RequiredsOpts < OptionError
|
31
|
+
def initialize(opts)
|
32
|
+
opts = opts.map { |alts| alts.map { |alt| Array(alt).join(' and ') }.join(' or ' ) }
|
33
|
+
super(:requires_opts, opts.join(', '))
|
34
|
+
end
|
23
35
|
end
|
24
36
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
37
|
+
class RequiresOpts < OptionError
|
38
|
+
def initialize(opts)
|
39
|
+
msg = opts.size == 1 ? :requires_opt : :requires_opts
|
40
|
+
opts = opts.map { |one, other| "#{one} (required by #{other})" }.join(', ')
|
41
|
+
super(msg, opts)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class ExceedingMax < OptionError
|
46
|
+
def initialize(opts)
|
47
|
+
opts = opts.map { |opt, max| "#{opt} (max: #{max})" }.join(', ')
|
48
|
+
super(:exceeding_max, opts)
|
49
|
+
end
|
29
50
|
end
|
30
51
|
|
31
|
-
|
32
|
-
|
52
|
+
class InvalidFormat < OptionError
|
53
|
+
def initialize(opts)
|
54
|
+
opts = opts.map { |opt, format| "#{opt} (format: #{format})" }.join(', ')
|
55
|
+
super(:invalid_format, opts)
|
56
|
+
end
|
33
57
|
end
|
34
58
|
|
35
|
-
|
36
|
-
|
59
|
+
class UnknownValues < OptionError
|
60
|
+
def initialize(opts)
|
61
|
+
opts = opts.map { |(key, value, known)| "#{key}=#{value} (known values: #{known.join(', ')})" }.join(', ')
|
62
|
+
super(:unknown_values, opts)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
attr_reader :ctx, :name, :opts
|
67
|
+
|
68
|
+
def initialize(*args)
|
69
|
+
ctx = args.shift if args.first.is_a?(Ctx)
|
70
|
+
@opts = args.last.is_a?(Hash) ? args.pop : {}
|
71
|
+
@name = args.shift || $0
|
72
|
+
@ctx = ctx || Ctx.new(name, opts)
|
73
|
+
end
|
74
|
+
|
75
|
+
def run(args)
|
76
|
+
runner(args).run
|
77
|
+
rescue Error => e
|
78
|
+
abort [e.message, runner(['help', args.first]).cmd.help].join("\n\n")
|
79
|
+
end
|
37
80
|
|
38
|
-
def runner(
|
39
|
-
|
40
|
-
runner
|
41
|
-
Runner.const_get(runner.to_s.capitalize).new(
|
81
|
+
def runner(args)
|
82
|
+
runner = :default if args.first.to_s == 'help'
|
83
|
+
runner ||= opts[:runner] || :default
|
84
|
+
Runner.const_get(runner.to_s.capitalize).new(ctx, args)
|
42
85
|
end
|
43
86
|
|
44
|
-
|
87
|
+
# def help(*args)
|
88
|
+
# runner(:help, *args).run
|
89
|
+
# end
|
45
90
|
end
|