travis-cl 1.2.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +134 -0
  3. data/Gemfile +11 -0
  4. data/Gemfile.lock +59 -0
  5. data/MIT_LICENSE.md +21 -0
  6. data/README.md +1283 -0
  7. data/cl.gemspec +30 -0
  8. data/examples/README.md +22 -0
  9. data/examples/_src/args/cast.erb.rb +100 -0
  10. data/examples/_src/args/opts.erb.rb +100 -0
  11. data/examples/_src/args/required.erb.rb +63 -0
  12. data/examples/_src/args/splat.erb.rb +55 -0
  13. data/examples/_src/gem.erb.rb +99 -0
  14. data/examples/_src/heroku.erb.rb +47 -0
  15. data/examples/_src/rakeish.erb.rb +54 -0
  16. data/examples/_src/readme/abstract.erb.rb +27 -0
  17. data/examples/_src/readme/alias.erb.rb +22 -0
  18. data/examples/_src/readme/arg.erb.rb +21 -0
  19. data/examples/_src/readme/arg_array.erb.rb +21 -0
  20. data/examples/_src/readme/arg_type.erb.rb +23 -0
  21. data/examples/_src/readme/args_splat.erb.rb +55 -0
  22. data/examples/_src/readme/array.erb.rb +21 -0
  23. data/examples/_src/readme/basic.erb.rb +72 -0
  24. data/examples/_src/readme/default.erb.rb +21 -0
  25. data/examples/_src/readme/deprecated.erb.rb +21 -0
  26. data/examples/_src/readme/deprecated_alias.erb.rb +21 -0
  27. data/examples/_src/readme/description.erb.rb +60 -0
  28. data/examples/_src/readme/downcase.erb.rb +21 -0
  29. data/examples/_src/readme/enum.erb.rb +35 -0
  30. data/examples/_src/readme/example.erb.rb +25 -0
  31. data/examples/_src/readme/format.erb.rb +35 -0
  32. data/examples/_src/readme/internal.erb.rb +28 -0
  33. data/examples/_src/readme/negate.erb.rb +37 -0
  34. data/examples/_src/readme/note.erb.rb +25 -0
  35. data/examples/_src/readme/opts.erb.rb +33 -0
  36. data/examples/_src/readme/opts_block.erb.rb +30 -0
  37. data/examples/_src/readme/range.erb.rb +35 -0
  38. data/examples/_src/readme/registry.erb.rb +18 -0
  39. data/examples/_src/readme/required.erb.rb +35 -0
  40. data/examples/_src/readme/requireds.erb.rb +46 -0
  41. data/examples/_src/readme/requires.erb.rb +35 -0
  42. data/examples/_src/readme/runner.erb.rb +29 -0
  43. data/examples/_src/readme/runner_custom.erb.rb +25 -0
  44. data/examples/_src/readme/secret.erb.rb +22 -0
  45. data/examples/_src/readme/see.erb.rb +25 -0
  46. data/examples/_src/readme/type.erb.rb +21 -0
  47. data/examples/args/cast +98 -0
  48. data/examples/args/opts +98 -0
  49. data/examples/args/required +62 -0
  50. data/examples/args/splat +58 -0
  51. data/examples/gem +97 -0
  52. data/examples/heroku +48 -0
  53. data/examples/rakeish +50 -0
  54. data/examples/readme/abstract +28 -0
  55. data/examples/readme/alias +21 -0
  56. data/examples/readme/arg +20 -0
  57. data/examples/readme/arg_array +20 -0
  58. data/examples/readme/arg_type +22 -0
  59. data/examples/readme/args_splat +58 -0
  60. data/examples/readme/array +20 -0
  61. data/examples/readme/basic +67 -0
  62. data/examples/readme/default +20 -0
  63. data/examples/readme/deprecated +20 -0
  64. data/examples/readme/deprecated_alias +20 -0
  65. data/examples/readme/description +56 -0
  66. data/examples/readme/downcase +20 -0
  67. data/examples/readme/enum +33 -0
  68. data/examples/readme/example +21 -0
  69. data/examples/readme/format +33 -0
  70. data/examples/readme/internal +24 -0
  71. data/examples/readme/negate +44 -0
  72. data/examples/readme/note +21 -0
  73. data/examples/readme/opts +31 -0
  74. data/examples/readme/opts_block +29 -0
  75. data/examples/readme/range +33 -0
  76. data/examples/readme/registry +15 -0
  77. data/examples/readme/required +33 -0
  78. data/examples/readme/requireds +46 -0
  79. data/examples/readme/requires +33 -0
  80. data/examples/readme/runner +30 -0
  81. data/examples/readme/runner_custom +22 -0
  82. data/examples/readme/secret +21 -0
  83. data/examples/readme/see +21 -0
  84. data/examples/readme/type +20 -0
  85. data/lib/cl/arg.rb +79 -0
  86. data/lib/cl/args.rb +92 -0
  87. data/lib/cl/cast.rb +55 -0
  88. data/lib/cl/cmd.rb +74 -0
  89. data/lib/cl/config/env.rb +52 -0
  90. data/lib/cl/config/files.rb +34 -0
  91. data/lib/cl/config.rb +30 -0
  92. data/lib/cl/ctx.rb +36 -0
  93. data/lib/cl/dsl.rb +182 -0
  94. data/lib/cl/errors.rb +119 -0
  95. data/lib/cl/help/cmd.rb +118 -0
  96. data/lib/cl/help/cmds.rb +26 -0
  97. data/lib/cl/help/format.rb +69 -0
  98. data/lib/cl/help/table.rb +58 -0
  99. data/lib/cl/help/usage.rb +26 -0
  100. data/lib/cl/help.rb +37 -0
  101. data/lib/cl/helper/suggest.rb +10 -0
  102. data/lib/cl/helper.rb +47 -0
  103. data/lib/cl/opt.rb +276 -0
  104. data/lib/cl/opts/validate.rb +117 -0
  105. data/lib/cl/opts.rb +114 -0
  106. data/lib/cl/parser/format.rb +63 -0
  107. data/lib/cl/parser.rb +70 -0
  108. data/lib/cl/runner/default.rb +86 -0
  109. data/lib/cl/runner/multi.rb +34 -0
  110. data/lib/cl/runner.rb +10 -0
  111. data/lib/cl/ui.rb +146 -0
  112. data/lib/cl/version.rb +3 -0
  113. data/lib/cl.rb +62 -0
  114. metadata +177 -0
data/lib/cl/opt.rb ADDED
@@ -0,0 +1,276 @@
1
+ require 'cl/cast'
2
+ require 'cl/errors'
3
+
4
+ class Cl
5
+ class Opt < Struct.new(:strs, :opts, :block)
6
+ include Cast, Regex
7
+
8
+ OPTS = %i(
9
+ alias default deprecated description downcase eg enum example format
10
+ internal max min negate note required requires secret see sep type upcase
11
+ )
12
+
13
+ OPT = /^--(?:\[.*\])?(.*)$/
14
+
15
+ TYPES = {
16
+ int: :integer,
17
+ str: :string,
18
+ bool: :flag,
19
+ boolean: :flag
20
+ }
21
+
22
+ attr_reader :short, :long
23
+
24
+ def initialize(strs, *)
25
+ super
26
+ @short, @long = Validator.new(strs, opts).apply
27
+ end
28
+
29
+ def define(const)
30
+ return unless __key__ = name
31
+ const.send :include, Module.new {
32
+ define_method (__key__) { opts[__key__] }
33
+ define_method (:"#{__key__}?") { !!opts[__key__] }
34
+ }
35
+ end
36
+
37
+ def name
38
+ return @name if instance_variable_defined?(:@name)
39
+ name = long.split(' ').first.match(OPT)[1] if long
40
+ @name = name.sub('-', '_').to_sym if name
41
+ end
42
+
43
+ def type
44
+ @type ||= TYPES[opts[:type]] || opts[:type] || infer_type
45
+ end
46
+
47
+ def infer_type
48
+ strs.any? { |str| str.split(' ').size > 1 } ? :string : :flag
49
+ end
50
+
51
+ def help?
52
+ name == :help
53
+ end
54
+
55
+ def flag?
56
+ type == :flag
57
+ end
58
+
59
+ def int?
60
+ type == :int || type == :integer
61
+ end
62
+
63
+ def array?
64
+ type == :array
65
+ end
66
+
67
+ def aliases?
68
+ !!opts[:alias]
69
+ end
70
+
71
+ def aliases
72
+ Array(opts[:alias])
73
+ end
74
+
75
+ def description
76
+ opts[:description]
77
+ end
78
+
79
+ def deprecated?(name = nil)
80
+ return !!opts[:deprecated] if name.nil?
81
+ names = [name.to_s.gsub('_', '-').to_sym, name.to_s.gsub('-', '_').to_sym]
82
+ deprecated? && names.include?(deprecated.first)
83
+ end
84
+
85
+ def deprecated
86
+ # If it's a string then it's a deprecation message and the option itself
87
+ # is considered deprecated. If it's a symbol it refers to a deprecated
88
+ # alias, and the option's name is the deprecation message.
89
+ return [name, opts[:deprecated]] unless opts[:deprecated].is_a?(Symbol)
90
+ opts[:deprecated] ? [opts[:deprecated], name] : []
91
+ end
92
+
93
+ def downcase?
94
+ !!opts[:downcase]
95
+ end
96
+
97
+ def default?
98
+ opts.key?(:default)
99
+ end
100
+
101
+ def default
102
+ opts[:default]
103
+ end
104
+
105
+ def enum?
106
+ !!opts[:enum]
107
+ end
108
+
109
+ def enum
110
+ Array(opts[:enum])
111
+ end
112
+
113
+ def known?(value)
114
+ return value.all? { |value| known?(value) } if value.is_a?(Array)
115
+ enum.any? { |obj| obj.is_a?(Regexp) ? obj =~ value.to_s : obj == value }
116
+ end
117
+
118
+ def unknown(value)
119
+ return value.reject { |value| known?(value) } if value.is_a?(Array)
120
+ known?(value) ? [] : Array(value)
121
+ end
122
+
123
+ def example?
124
+ !!opts[:example]
125
+ end
126
+
127
+ def example
128
+ opts[:example]
129
+ end
130
+
131
+ def format?
132
+ !!opts[:format]
133
+ end
134
+
135
+ def format
136
+ format_regex(opts[:format])
137
+ end
138
+
139
+ def formatted?(value)
140
+ return value.all? { |value| formatted?(value) } if value.is_a?(Array)
141
+ opts[:format] =~ value
142
+ end
143
+
144
+ def internal?
145
+ !!opts[:internal]
146
+ end
147
+
148
+ def min?
149
+ int? && !!opts[:min]
150
+ end
151
+
152
+ def min
153
+ opts[:min]
154
+ end
155
+
156
+ def max?
157
+ int? && !!opts[:max]
158
+ end
159
+
160
+ def max
161
+ opts[:max]
162
+ end
163
+
164
+ def negate?
165
+ !!negate
166
+ end
167
+
168
+ def negate
169
+ ['no'] + Array(opts[:negate]) if flag?
170
+ end
171
+
172
+ def note?
173
+ !!opts[:note]
174
+ end
175
+
176
+ def note
177
+ opts[:note]
178
+ end
179
+
180
+ def required?
181
+ !!opts[:required]
182
+ end
183
+
184
+ def requires?
185
+ !!opts[:requires]
186
+ end
187
+
188
+ def requires
189
+ Array(opts[:requires])
190
+ end
191
+
192
+ def secret?
193
+ !!opts[:secret]
194
+ end
195
+
196
+ def see?
197
+ !!opts[:see]
198
+ end
199
+
200
+ def see
201
+ opts[:see]
202
+ end
203
+
204
+ def separator
205
+ opts[:sep]
206
+ end
207
+
208
+ def upcase?
209
+ !!opts[:upcase]
210
+ end
211
+
212
+ def block
213
+ # raise if no block was given, and the option's name cannot be inferred
214
+ super || method(:assign)
215
+ end
216
+
217
+ def assign(opts, type, _, value)
218
+ [name, *aliases].each do |name|
219
+ if array?
220
+ opts[name] ||= []
221
+ opts[name] << value
222
+ else
223
+ opts[name] = value
224
+ end
225
+ end
226
+ end
227
+
228
+ def long?(str)
229
+ str.start_with?('--')
230
+ end
231
+
232
+ class Validator < Struct.new(:strs, :opts)
233
+ SHORT = /^-\w( \w+)?$/
234
+ LONG = /^--[\w\-\[\]]+( \[?\w+\]?)?$/
235
+
236
+ MSGS = {
237
+ missing_strs: 'No option strings given. Pass one short -s and/or one --long option string.',
238
+ wrong_strs: 'Wrong option strings given. Pass one short -s and/or one --long option string.',
239
+ invalid_strs: 'Invalid option strings given: %p',
240
+ unknown_opts: 'Unknown options: %s'
241
+ }
242
+
243
+ def apply
244
+ error :missing_strs if strs.empty?
245
+ error :wrong_strs if short.size > 1 || long.size > 1
246
+ error :invalid_strs, invalid unless invalid.empty?
247
+ error :unknown_opts, unknown.map(&:inspect).join(', ') unless unknown.empty?
248
+ [short.first, long.first]
249
+ end
250
+
251
+ def unknown
252
+ @unknown ||= opts.keys - Opt::OPTS
253
+ end
254
+
255
+ def invalid
256
+ @invalid ||= strs.-(valid).join(', ')
257
+ end
258
+
259
+ def valid
260
+ strs.grep(Regexp.union(SHORT, LONG))
261
+ end
262
+
263
+ def short
264
+ strs.grep(SHORT)
265
+ end
266
+
267
+ def long
268
+ strs.grep(LONG)
269
+ end
270
+
271
+ def error(key, *args)
272
+ raise Cl::Error, MSGS[key] % args
273
+ end
274
+ end
275
+ end
276
+ end
@@ -0,0 +1,117 @@
1
+ require 'cl/helper'
2
+
3
+ class Cl
4
+ class Opts
5
+ module Validate
6
+ def validate(cmd, opts, values, orig)
7
+ Validate.constants.each do |name|
8
+ next if name == :Validator
9
+ const = Validate.const_get(name)
10
+ const.new(cmd, opts, values, orig).apply
11
+ end
12
+ end
13
+
14
+ class Validator < Struct.new(:cmd, :opts, :values, :orig)
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| orig.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
+ unknown = opt.unknown(values[opt.name])
90
+ next if unknown.empty?
91
+ known = opt.enum.map { |str| format_regex(str) }
92
+ [opt.name, unknown, 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
data/lib/cl/opts.rb ADDED
@@ -0,0 +1,114 @@
1
+ require 'cl/opt'
2
+ require 'cl/opts/validate'
3
+
4
+ class Cl
5
+ class Opts
6
+ include Enumerable, Validate
7
+
8
+ def define(const, *args, &block)
9
+ opts = args.last.is_a?(Hash) ? args.pop : {}
10
+ strs = args.select { |arg| arg.start_with?('-') }
11
+ opts[:description] = args.-(strs).first
12
+
13
+ opt = Opt.new(strs, opts, block)
14
+ opt.define(const)
15
+ insert(opt, const)
16
+ end
17
+
18
+ def apply(cmd, opts)
19
+ return opts if opts[:help]
20
+ orig = opts.dup
21
+ opts = defaults(cmd, opts)
22
+ opts = downcase(opts)
23
+ opts = upcase(opts)
24
+ opts = cast(opts)
25
+ opts = taint(opts)
26
+ validate(cmd, self, opts, orig)
27
+ opts
28
+ end
29
+
30
+ def insert(opt, const)
31
+ delete(opt)
32
+ return opts << opt if const == Cmd
33
+ ix = opts.index(const.superclass.opts.first)
34
+ opts.empty? ? opts << opt : opts.insert(ix.to_i, opt)
35
+ end
36
+
37
+ def [](key)
38
+ opts.detect { |opt| opt.name == key }
39
+ end
40
+
41
+ def each(&block)
42
+ opts.each(&block)
43
+ end
44
+
45
+ def delete(opt)
46
+ opts.delete(opts.detect { |o| o.strs == opt.strs })
47
+ end
48
+
49
+ def first
50
+ opts.first
51
+ end
52
+
53
+ def to_a
54
+ opts
55
+ end
56
+
57
+ attr_writer :opts
58
+
59
+ def opts
60
+ @opts ||= []
61
+ end
62
+
63
+ def deprecated
64
+ map(&:deprecated).flatten.compact
65
+ end
66
+
67
+ def ==(other)
68
+ strs == other.strs
69
+ end
70
+
71
+ def dup
72
+ super.tap { |obj| obj.opts = opts.dup }
73
+ end
74
+
75
+ private
76
+
77
+ def defaults(cmd, opts)
78
+ select(&:default?).inject(opts) do |opts, opt|
79
+ next opts if opts.key?(opt.name)
80
+ value = opt.default
81
+ value = resolve(cmd, opts, value) if value.is_a?(Symbol)
82
+ opts.merge(opt.name => value)
83
+ end
84
+ end
85
+
86
+ def resolve(cmd, opts, key)
87
+ opts[key] || cmd.respond_to?(key) && cmd.send(key)
88
+ end
89
+
90
+ def downcase(opts)
91
+ select(&:downcase?).inject(opts) do |opts, opt|
92
+ next opts unless value = opts[opt.name]
93
+ opts.merge(opt.name => value.to_s.downcase)
94
+ end
95
+ end
96
+
97
+ def upcase(opts)
98
+ select(&:upcase?).inject(opts) do |opts, opt|
99
+ next opts unless value = opts[opt.name]
100
+ opts.merge(opt.name => value.to_s.upcase)
101
+ end
102
+ end
103
+
104
+ def cast(opts)
105
+ opts.map do |key, value|
106
+ [key, self[key] ? self[key].cast(value) : value]
107
+ end.to_h
108
+ end
109
+
110
+ def taint(opts)
111
+ opts.to_h
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,63 @@
1
+ class Cl
2
+ class Parser < OptionParser
3
+ class Format < Struct.new(:opt)
4
+ NAME = /^(--(?:\[no-\])?)([^= ]+)/
5
+
6
+ def strs
7
+ strs = opt.strs + aliases
8
+ strs.map { |str| long?(str) ? long(str) : short(str) }.flatten
9
+ end
10
+
11
+ def long(str)
12
+ strs = [unnegate(str)]
13
+ strs = strs.map { |str| negated(str) }.flatten if flag?
14
+ strs = collect(strs, :dashed)
15
+ strs = collect(strs, :underscored)
16
+ strs = collect(strs, :valued) if flag? && Cl.flag_values
17
+ strs.uniq
18
+ end
19
+
20
+ def short(str)
21
+ str = "#{str} #{opt.name.upcase}" unless opt.flag? || str.include?(' ')
22
+ str
23
+ end
24
+
25
+ def unnegate(str)
26
+ str.sub('--[no-]', '--')
27
+ end
28
+
29
+ def aliases
30
+ opt.aliases.map { |name| "--#{name} #{ name.upcase unless opt.flag?}".strip }
31
+ end
32
+
33
+ def collect(strs, mod)
34
+ strs = strs + strs.map { |str| send(mod, str) }
35
+ strs.flatten.uniq
36
+ end
37
+
38
+ def negated(str)
39
+ str.dup.insert(2, '[no-]')
40
+ end
41
+
42
+ def dashed(str)
43
+ str =~ NAME && str.sub("#{$1}#{$2}", "#{$1}#{$2.tr('_', '-')}") || str
44
+ end
45
+
46
+ def underscored(str)
47
+ str =~ NAME && str.sub("#{$1}#{$2}", "#{$1}#{$2.tr('-', '_')}") || str
48
+ end
49
+
50
+ def valued(str)
51
+ "#{str} [true|false|yes|no]"
52
+ end
53
+
54
+ def long?(str)
55
+ str.start_with?('--')
56
+ end
57
+
58
+ def flag?
59
+ opt.flag? && !opt.help?
60
+ end
61
+ end
62
+ end
63
+ end
data/lib/cl/parser.rb ADDED
@@ -0,0 +1,70 @@
1
+ require 'optparse'
2
+ require 'cl/parser/format'
3
+
4
+ class Cl
5
+ class Parser < OptionParser
6
+ attr_reader :cmd, :args, :opts
7
+
8
+ def initialize(cmd, args)
9
+ @cmd = cmd
10
+ @opts = {}
11
+ opts = cmd.class.opts
12
+
13
+ super do
14
+ opts.each do |opt|
15
+ Format.new(opt).strs.each do |str|
16
+ on(str) { |value| set(opt, str, value) }
17
+ end
18
+ end
19
+ end
20
+
21
+ @args = parse!(normalize(opts, args))
22
+ end
23
+
24
+ # should consider negative arities (e.g. |one, *two|)
25
+ def set(opt, str, value)
26
+ name = long?(str) ? opt_name(str) : opt.name
27
+ value = true if value.nil? && opt.flag?
28
+ args = [opts, opt.type, name, value]
29
+ args = args[-opt.block.arity, opt.block.arity]
30
+ instance_exec(*args, &opt.block)
31
+ cmd.deprecations.update([opt.deprecated].to_h) if opt.deprecated?(name)
32
+ end
33
+
34
+ def normalize(opts, args)
35
+ args = noize(opts, args)
36
+ # dasherize(args)
37
+ args
38
+ end
39
+
40
+ def noize(opts, args)
41
+ args.map do |arg|
42
+ str = negation(opts, arg)
43
+ str ? arg.sub(/^--#{str}[-_]+/, '--no-') : arg
44
+ end
45
+ end
46
+
47
+ def negation(opts, arg)
48
+ opts.select(&:flag?).detect do |opt|
49
+ str = opt.negate.detect { |str| arg =~ /^--#{str}[-_]+#{opt.name}/ }
50
+ break str if str
51
+ end
52
+ end
53
+
54
+ # DASHERIZE = /^--([^= ])*/
55
+ #
56
+ # def dasherize(strs)
57
+ # strs.map do |str|
58
+ # str.is_a?(String) ? str.gsub(DASHERIZE) { |opt| opt.gsub('_', '-') } : str
59
+ # end
60
+ # end
61
+
62
+ def long?(str)
63
+ str.start_with?('--')
64
+ end
65
+
66
+ def opt_name(str)
67
+ str.split(' ').first.sub(/--(\[no[_\-]\])?/, '').to_sym
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,86 @@
1
+ require 'forwardable'
2
+ require 'cl/ctx'
3
+ require 'cl/helper'
4
+
5
+ class Cl
6
+ module Runner
7
+ class Default
8
+ Runner.register :default, self
9
+
10
+ singleton_class.send(:attr_accessor, :run_method)
11
+ self.run_method = :run
12
+
13
+ extend Forwardable
14
+ include Merge, Suggest
15
+
16
+ def_delegators :ctx, :abort
17
+
18
+ attr_reader :ctx, :const, :args, :opts
19
+
20
+ def initialize(ctx, args)
21
+ @ctx = ctx
22
+ @const, @args = lookup(args)
23
+ end
24
+
25
+ def run
26
+ cmd.help? ? help.run : cmd.send(self.class.run_method)
27
+ rescue OptionParser::InvalidOption => e
28
+ raise UnknownOption.new(const, e.message)
29
+ end
30
+
31
+ def cmd
32
+ @cmd ||= const.new(ctx, args)
33
+ end
34
+
35
+ def help
36
+ cmd.is_a?(Help) ? cmd : Help.new(ctx, [cmd.registry_key])
37
+ end
38
+
39
+ def suggestions(args)
40
+ keys = args.inject([]) { |keys, arg| keys << [keys.last, arg].compact.join(':') }
41
+ keys.map { |key| suggest(providers.map(&:to_s), key) }.flatten
42
+ end
43
+
44
+ private
45
+
46
+ # Finds a command class to run for the given arguments.
47
+ #
48
+ # Stopping at any arg that starts with a dash, find the command
49
+ # with the key matching the most args when joined with ":", and
50
+ # remove these used args from the array
51
+ #
52
+ # For example, if there are commands registered with the keys
53
+ #
54
+ # git:pull
55
+ # git:push
56
+ #
57
+ # then for the arguments:
58
+ #
59
+ # git push master
60
+ #
61
+ # the method `lookup` will find the constant registered as `git:push`,
62
+ # remove these from the `args` array, and return both the constant, and
63
+ # the remaining args.
64
+ #
65
+ # @param args [Array<String>] arguments to run (usually ARGV)
66
+ def lookup(args)
67
+ keys = args.take_while { |key| !key.start_with?('-') }
68
+
69
+ keys = keys.inject([[], []]) do |keys, key|
70
+ keys[1] << key
71
+ keys[0] << [Cmd[keys[1].join(':')], keys[1].dup] if Cmd.registered?(keys[1].join(':'))
72
+ keys
73
+ end
74
+
75
+ cmd, keys = keys[0].last
76
+ raise UnknownCmd.new(self, args) if cmd.nil? || cmd.abstract?
77
+ keys.each { |key| args.delete_at(args.index(key)) }
78
+ [cmd, args]
79
+ end
80
+
81
+ def providers
82
+ Cmd.registry.keys
83
+ end
84
+ end
85
+ end
86
+ end