clee 0.4.2
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/LICENSE.md +90 -0
- data/README.md +280 -0
- data/Rakefile +448 -0
- data/TODO.md +1 -0
- data/bin/clee +55 -0
- data/clee.gemspec +40 -0
- data/docs/sunwukong.md +38 -0
- data/lib/clee/_lib.rb +75 -0
- data/lib/clee.rb +623 -0
- metadata +52 -0
data/lib/clee.rb
ADDED
@@ -0,0 +1,623 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
#
|
3
|
+
require_relative 'clee/_lib.rb'
|
4
|
+
|
5
|
+
Clee.load_dependencies!
|
6
|
+
|
7
|
+
#
|
8
|
+
class Clee
|
9
|
+
class Error < ::StandardError; end
|
10
|
+
|
11
|
+
attr_accessor :env
|
12
|
+
attr_accessor :argv
|
13
|
+
attr_accessor :options
|
14
|
+
attr_accessor :params
|
15
|
+
attr_accessor :stdin
|
16
|
+
attr_accessor :stdout
|
17
|
+
attr_accessor :stderr
|
18
|
+
attr_accessor :help
|
19
|
+
|
20
|
+
def _run!
|
21
|
+
_setup!
|
22
|
+
_parse_command_line!
|
23
|
+
_set_mode!
|
24
|
+
_run_mode!
|
25
|
+
end
|
26
|
+
|
27
|
+
def _setup!
|
28
|
+
@klass = self.class
|
29
|
+
|
30
|
+
@env = Hash.new
|
31
|
+
@options = Hash.new
|
32
|
+
|
33
|
+
@argv = ARGV.map(&:dup)
|
34
|
+
|
35
|
+
@stdin = $stdin.dup
|
36
|
+
@stdout = $stdout.dup
|
37
|
+
@stderr = $stderr.dup
|
38
|
+
|
39
|
+
@name = @klass.name.dup
|
40
|
+
@help = @klass.help.dup
|
41
|
+
@tldr = @klass.tldr.dup
|
42
|
+
end
|
43
|
+
|
44
|
+
def _parse_command_line!
|
45
|
+
_parse_options!
|
46
|
+
_parse_env!
|
47
|
+
_parse_params!
|
48
|
+
end
|
49
|
+
|
50
|
+
def _parse_options!
|
51
|
+
@options = Hash.new
|
52
|
+
|
53
|
+
o = OptionParser.new
|
54
|
+
|
55
|
+
klass.options.each do |spec|
|
56
|
+
spec => long:, short:, value:
|
57
|
+
args = []
|
58
|
+
|
59
|
+
case value
|
60
|
+
when :required
|
61
|
+
args.push "-#{ short }" if short
|
62
|
+
args.push "--#{ long } value" if long
|
63
|
+
when :optional
|
64
|
+
args.push "-#{ short }" if short
|
65
|
+
args.push "--#{ long } [value]" if long
|
66
|
+
when :none
|
67
|
+
args.push "-#{ short }" if short
|
68
|
+
args.push "--[no-]#{ long }" if long
|
69
|
+
end
|
70
|
+
|
71
|
+
o.on(*args) do |val|
|
72
|
+
if @options.has_key?(long)
|
73
|
+
@options[long] = [@options[long], val].flatten
|
74
|
+
else
|
75
|
+
@options[long] = val
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
begin
|
81
|
+
o.parse!(@argv)
|
82
|
+
rescue OptionParser::MissingArgument => e
|
83
|
+
warn(e.message)
|
84
|
+
exit 1
|
85
|
+
rescue OptionParser::InvalidOption => e
|
86
|
+
warn(e.message)
|
87
|
+
exit 1
|
88
|
+
end
|
89
|
+
|
90
|
+
Clee.symbolize_keys!(@options)
|
91
|
+
end
|
92
|
+
|
93
|
+
def _parse_env!
|
94
|
+
#
|
95
|
+
envs = Hash.new
|
96
|
+
|
97
|
+
@argv.each_with_index do |arg, index|
|
98
|
+
if arg =~ /[^=]+\s*=/
|
99
|
+
k, v = arg.split(/\s*=\s*/, 3)
|
100
|
+
|
101
|
+
key = k.to_s.strip.to_sym
|
102
|
+
val = v.to_s.strip == '' ? nil : v
|
103
|
+
|
104
|
+
envs[index] = {key:, val:}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
validate = proc do |long:, value:, val:|
|
110
|
+
case value
|
111
|
+
when :required
|
112
|
+
unless val
|
113
|
+
warn("#{ long }=:missing")
|
114
|
+
exit 1
|
115
|
+
end
|
116
|
+
|
117
|
+
when :none
|
118
|
+
unless val.nil?
|
119
|
+
warn("#{ long }=#{ val }")
|
120
|
+
exit 1
|
121
|
+
end
|
122
|
+
|
123
|
+
when :optional
|
124
|
+
:noop
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
add_env = proc do |long, val|
|
130
|
+
if @env.has_key?(long)
|
131
|
+
@env[long] = [@env[long], val].flatten
|
132
|
+
else
|
133
|
+
@env[long] = val
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
klass.envs.each do |spec|
|
138
|
+
spec => long:, short:, keys:, value:
|
139
|
+
|
140
|
+
keys.reverse.each do |key|
|
141
|
+
if ENV.has_key?(key.to_s)
|
142
|
+
v = ENV[key.to_s]
|
143
|
+
val = v.to_s.strip == '' ? nil : v
|
144
|
+
validate[long:, value:, val:]
|
145
|
+
add_env[long, val]
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
envs.each do |index, env|
|
150
|
+
env => key:, val:
|
151
|
+
|
152
|
+
if([long, short].include?(key))
|
153
|
+
validate[long:, value:, val:]
|
154
|
+
add_env[long, val]
|
155
|
+
@argv[index] = nil
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
#
|
161
|
+
@argv.compact!
|
162
|
+
|
163
|
+
Clee.symbolize_keys!(@env)
|
164
|
+
end
|
165
|
+
|
166
|
+
def _parse_params!
|
167
|
+
@params = @env.merge(@options)
|
168
|
+
end
|
169
|
+
|
170
|
+
def _set_mode!
|
171
|
+
@mode = nil
|
172
|
+
|
173
|
+
first = 0
|
174
|
+
last = @argv.size
|
175
|
+
|
176
|
+
while last > 0
|
177
|
+
args = @argv[first ... last]
|
178
|
+
mode = klass.mode?(*args)
|
179
|
+
|
180
|
+
if mode
|
181
|
+
@mode = mode
|
182
|
+
@argv.replace(@argv[last..])
|
183
|
+
break
|
184
|
+
end
|
185
|
+
|
186
|
+
last = last - 1
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def _run_mode!
|
191
|
+
if @mode
|
192
|
+
mode_method = klass.mode_method_for(@mode)
|
193
|
+
send(mode_method)
|
194
|
+
exit 0
|
195
|
+
end
|
196
|
+
|
197
|
+
if argv.first == 'help' || params[:help]
|
198
|
+
help!(exit: 0)
|
199
|
+
end
|
200
|
+
|
201
|
+
send(:run)
|
202
|
+
exit 0
|
203
|
+
end
|
204
|
+
|
205
|
+
def _default_run!
|
206
|
+
help!(exit: 1)
|
207
|
+
end
|
208
|
+
|
209
|
+
def run
|
210
|
+
_default_run!
|
211
|
+
end
|
212
|
+
|
213
|
+
def progname
|
214
|
+
File.basename($0)
|
215
|
+
end
|
216
|
+
|
217
|
+
def help!(**kws)
|
218
|
+
help = (@help || _default_help)
|
219
|
+
puts help
|
220
|
+
status = kws.fetch(:exit){ kws.fetch('exit'){ 1 } }
|
221
|
+
exit(status.to_i) if status
|
222
|
+
end
|
223
|
+
|
224
|
+
def _default_help
|
225
|
+
[].tap do |l|
|
226
|
+
p = proc do |string|
|
227
|
+
l.push string.to_s.rstrip
|
228
|
+
l.push "\n"
|
229
|
+
end
|
230
|
+
|
231
|
+
h = proc do |header|
|
232
|
+
l.push "\n"
|
233
|
+
p[header]
|
234
|
+
p['_' * header.to_s.size]
|
235
|
+
end
|
236
|
+
|
237
|
+
h[:NAME]
|
238
|
+
p[" #{ progname }"]
|
239
|
+
|
240
|
+
if @tldr
|
241
|
+
h[:TLDR]
|
242
|
+
p[" #{ @tldr }"]
|
243
|
+
end
|
244
|
+
|
245
|
+
unless klass.envs.empty?
|
246
|
+
h[:ENVIRONMENT]
|
247
|
+
klass.envs.each do |spec|
|
248
|
+
spec => value:, keys:
|
249
|
+
|
250
|
+
if value == :none
|
251
|
+
p[" #{ keys.join(' | ') }"]
|
252
|
+
else
|
253
|
+
p[" #{ keys.join(' | ') } : value=#{ value }"]
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
unless klass.options.empty?
|
259
|
+
h[:OPTIONS]
|
260
|
+
klass.options.each do |spec|
|
261
|
+
spec => value:, opts:
|
262
|
+
|
263
|
+
if value == :none
|
264
|
+
p[" #{ opts.join(' | ') }"]
|
265
|
+
else
|
266
|
+
p[" #{ opts.join(' | ') } : value=#{ value }"]
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
|
272
|
+
h[:MODES]
|
273
|
+
p[" ~> #{ progname }"]
|
274
|
+
modes.each do |mode|
|
275
|
+
p[" ~> #{ progname } #{ mode.join ' ' }"]
|
276
|
+
end
|
277
|
+
p[" ~> #{ progname } help"]
|
278
|
+
|
279
|
+
end.join.strip
|
280
|
+
end
|
281
|
+
#
|
282
|
+
def Clee.deep_copy(obj)
|
283
|
+
Marshal.load(Marshal.dump(obj))
|
284
|
+
end
|
285
|
+
|
286
|
+
def Clee.symbolize_keys!(hash)
|
287
|
+
hash.transform_keys!(&:to_sym)
|
288
|
+
|
289
|
+
hash.each do |key, val|
|
290
|
+
if val.is_a?(Hash)
|
291
|
+
symbolize_keys!(val)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
hash
|
296
|
+
end
|
297
|
+
|
298
|
+
def Clee.stringify_keys(hash)
|
299
|
+
stringify_keys!(deep_copy(hash))
|
300
|
+
end
|
301
|
+
|
302
|
+
def Clee.stringify_keys!(hash)
|
303
|
+
hash.transform_keys!(&:to_sym)
|
304
|
+
|
305
|
+
hash.each do |key, val|
|
306
|
+
if val.is_a?(Hash)
|
307
|
+
stringify_keys!(val)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
hash
|
312
|
+
end
|
313
|
+
|
314
|
+
def Clee.stringify_keys(hash)
|
315
|
+
stringify_keys!(deep_copy(hash))
|
316
|
+
end
|
317
|
+
|
318
|
+
#
|
319
|
+
def klass
|
320
|
+
self.class
|
321
|
+
end
|
322
|
+
|
323
|
+
def Clee.klass
|
324
|
+
self
|
325
|
+
end
|
326
|
+
|
327
|
+
#
|
328
|
+
ANSI = {
|
329
|
+
:clear => "\e[0m",
|
330
|
+
:reset => "\e[0m",
|
331
|
+
:erase_line => "\e[K",
|
332
|
+
:erase_char => "\e[P",
|
333
|
+
:bold => "\e[1m",
|
334
|
+
:dark => "\e[2m",
|
335
|
+
:underline => "\e[4m",
|
336
|
+
:underscore => "\e[4m",
|
337
|
+
:blink => "\e[5m",
|
338
|
+
:reverse => "\e[7m",
|
339
|
+
:concealed => "\e[8m",
|
340
|
+
:black => "\e[30m",
|
341
|
+
:red => "\e[31m",
|
342
|
+
:green => "\e[32m",
|
343
|
+
:yellow => "\e[33m",
|
344
|
+
:blue => "\e[34m",
|
345
|
+
:magenta => "\e[35m",
|
346
|
+
:cyan => "\e[36m",
|
347
|
+
:white => "\e[37m",
|
348
|
+
:on_black => "\e[40m",
|
349
|
+
:on_red => "\e[41m",
|
350
|
+
:on_green => "\e[42m",
|
351
|
+
:on_yellow => "\e[43m",
|
352
|
+
:on_blue => "\e[44m",
|
353
|
+
:on_magenta => "\e[45m",
|
354
|
+
:on_cyan => "\e[46m",
|
355
|
+
:on_white => "\e[47m"
|
356
|
+
}
|
357
|
+
|
358
|
+
Ansi = Object.new
|
359
|
+
|
360
|
+
ANSI.each do |key, value|
|
361
|
+
Ansi.singleton_class.define_method(key){ value }
|
362
|
+
end
|
363
|
+
|
364
|
+
def Clee.ansi
|
365
|
+
@ansi ||= Ansi
|
366
|
+
end
|
367
|
+
|
368
|
+
def ansi
|
369
|
+
klass.ansi
|
370
|
+
end
|
371
|
+
|
372
|
+
#
|
373
|
+
class Logger
|
374
|
+
Levels = [
|
375
|
+
:success,
|
376
|
+
:failure,
|
377
|
+
:message,
|
378
|
+
:warning,
|
379
|
+
:special,
|
380
|
+
]
|
381
|
+
|
382
|
+
Colors = {
|
383
|
+
success: Ansi.green,
|
384
|
+
failure: Ansi.red,
|
385
|
+
message: Ansi.cyan,
|
386
|
+
default: Ansi.blue,
|
387
|
+
warning: Ansi.yellow,
|
388
|
+
special: Ansi.magenta,
|
389
|
+
clear: Ansi.clear,
|
390
|
+
}
|
391
|
+
|
392
|
+
def initialize(io = $stderr)
|
393
|
+
@io = io
|
394
|
+
end
|
395
|
+
|
396
|
+
def log(arg, *args, **kws)
|
397
|
+
level = kws.fetch(:level){ :message }
|
398
|
+
color = kws[:color] ? Ansi.public_send(kws[:color]) : color_for(level)
|
399
|
+
clear = color_for(:clear)
|
400
|
+
|
401
|
+
[arg, *args].each do |arg|
|
402
|
+
ts = Time.now.utc.iso8601(2)
|
403
|
+
msg = msg_for(arg)
|
404
|
+
|
405
|
+
prefix = "### [#{ level.to_s.upcase } @ #{ ts }]"
|
406
|
+
|
407
|
+
if @io.tty?
|
408
|
+
@io.write(color)
|
409
|
+
@io.write(prefix)
|
410
|
+
@io.write(clear)
|
411
|
+
else
|
412
|
+
@io.write(prefix)
|
413
|
+
end
|
414
|
+
|
415
|
+
@io.write("\n#{ msg }\n")
|
416
|
+
@io.flush
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
Levels.each do |level|
|
421
|
+
define_method(level){|arg, *args| log(arg, *args, level:)}
|
422
|
+
end
|
423
|
+
|
424
|
+
def color_for(level)
|
425
|
+
Colors.fetch(level.to_s.to_sym){ Colors.fetch(:default) }
|
426
|
+
end
|
427
|
+
|
428
|
+
def msg_for(arg)
|
429
|
+
case
|
430
|
+
when arg.is_a?(String)
|
431
|
+
arg.strip
|
432
|
+
when arg.is_a?(Exception)
|
433
|
+
"#{ e.message } (#{ e.class.name })\n#{ Array(e.backtrace).join(10.chr) }"
|
434
|
+
else
|
435
|
+
arg.pretty_inspect
|
436
|
+
end
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
def Clee.logger
|
441
|
+
@@logger ||= Logger.new
|
442
|
+
end
|
443
|
+
|
444
|
+
def logger
|
445
|
+
klass.logger
|
446
|
+
end
|
447
|
+
|
448
|
+
def log(*args, **kws, &block)
|
449
|
+
return logger if args.empty? && kws.empty? && block.nil?
|
450
|
+
|
451
|
+
logger.message(*args, **kws, &block)
|
452
|
+
end
|
453
|
+
|
454
|
+
def emsg(e)
|
455
|
+
if e.is_a?(Exception)
|
456
|
+
"#{ e.message } (#{ e.class.name })\n#{ Array(e.backtrace).join(10.chr) }"
|
457
|
+
else
|
458
|
+
e.to_s
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
#
|
463
|
+
def Clee.parse_spec(list, *args, **kws, &block)
|
464
|
+
return list if args.empty? && kws.empty? && block.nil?
|
465
|
+
|
466
|
+
argv = []
|
467
|
+
|
468
|
+
args.each do |arg|
|
469
|
+
if arg.is_a?(Hash)
|
470
|
+
kws.update(Clee.symbolize_keys(arg))
|
471
|
+
else
|
472
|
+
argv.push(arg)
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
long = kws.fetch(:long){ argv.shift }
|
477
|
+
raise ArgumentError.new('long=nil') unless long
|
478
|
+
long = long.to_s.to_sym
|
479
|
+
|
480
|
+
short = kws.fetch(:short){ argv.shift }
|
481
|
+
|
482
|
+
value = kws.fetch(:value){ :none }.to_s.to_sym
|
483
|
+
|
484
|
+
values = [:none, :required, :optional]
|
485
|
+
|
486
|
+
raise ArgumentError.new("value=#{ value }") unless values.include?(value)
|
487
|
+
|
488
|
+
keys = [long, short].compact
|
489
|
+
|
490
|
+
opts = [(long && "--#{ long }"), (short &&"-#{ short}")].compact
|
491
|
+
|
492
|
+
spec = {long:, short:, value:, keys:, opts:}
|
493
|
+
|
494
|
+
list.push(spec).uniq!
|
495
|
+
end
|
496
|
+
|
497
|
+
def Clee.options(*args, **kws, &block)
|
498
|
+
parse_spec(@@options ||= [], *args, **kws, &block)
|
499
|
+
end
|
500
|
+
|
501
|
+
def Clee.option(*args, **kws, &block)
|
502
|
+
options(*args, **kws, &block)
|
503
|
+
end
|
504
|
+
|
505
|
+
def Clee.opt(*args, **kws, &block)
|
506
|
+
options(*args, **kws, &block)
|
507
|
+
end
|
508
|
+
|
509
|
+
def Clee.envs(*args, **kws, &block)
|
510
|
+
parse_spec(@@envs ||= [], *args, **kws, &block)
|
511
|
+
end
|
512
|
+
|
513
|
+
def Clee.env(*args, **kws, &block)
|
514
|
+
envs(*args, **kws, &block)
|
515
|
+
end
|
516
|
+
|
517
|
+
def Clee.params(*args, **kws, &block)
|
518
|
+
parse_spec(@@options ||= [], *args, **kws, &block)
|
519
|
+
parse_spec(@@envs ||= [], *args, **kws, &block)
|
520
|
+
|
521
|
+
@@options + @@envs
|
522
|
+
end
|
523
|
+
|
524
|
+
def Clee.param(*args, **kws, &block)
|
525
|
+
params(*args, **kws, &block)
|
526
|
+
end
|
527
|
+
|
528
|
+
def Clee.help(*args)
|
529
|
+
@help ||= nil
|
530
|
+
|
531
|
+
unless args.empty?
|
532
|
+
@help = args.join("\n")
|
533
|
+
end
|
534
|
+
|
535
|
+
@help
|
536
|
+
end
|
537
|
+
|
538
|
+
def Clee.tldr(*args)
|
539
|
+
@tldr ||= nil
|
540
|
+
|
541
|
+
unless args.empty?
|
542
|
+
@tldr = args.join("\n")
|
543
|
+
end
|
544
|
+
|
545
|
+
@tldr
|
546
|
+
end
|
547
|
+
|
548
|
+
def Clee.run(*args, **kws, &block)
|
549
|
+
if args.empty?
|
550
|
+
method = :run
|
551
|
+
else
|
552
|
+
mode = mode_for(*args)
|
553
|
+
klass.modes.push(mode).uniq!
|
554
|
+
method = mode_method_for(mode)
|
555
|
+
end
|
556
|
+
|
557
|
+
define_method(method, &block)
|
558
|
+
end
|
559
|
+
|
560
|
+
def Clee.mode_for(*args)
|
561
|
+
args.flatten!
|
562
|
+
args.compact!
|
563
|
+
|
564
|
+
return nil if args.empty?
|
565
|
+
|
566
|
+
args.map(&:to_s).map(&:to_sym)
|
567
|
+
end
|
568
|
+
|
569
|
+
def Clee.modes
|
570
|
+
@@modes ||= []
|
571
|
+
end
|
572
|
+
|
573
|
+
def modes
|
574
|
+
klass.modes
|
575
|
+
end
|
576
|
+
|
577
|
+
def Clee.mode?(*args)
|
578
|
+
mode = mode_for(*args)
|
579
|
+
return mode if modes.include?(mode)
|
580
|
+
end
|
581
|
+
|
582
|
+
def Clee.mode_method_for(mode)
|
583
|
+
"__mode__#{ [mode].join('__') }"
|
584
|
+
end
|
585
|
+
|
586
|
+
#
|
587
|
+
def Clee.klass_for(name = 'clee', &block)
|
588
|
+
Class.new(Clee).tap do |klass|
|
589
|
+
klass.class_eval do
|
590
|
+
define_singleton_method(:name){ "#{ name }" }
|
591
|
+
|
592
|
+
option(:help, :h, value: :none)
|
593
|
+
end
|
594
|
+
|
595
|
+
klass.class_eval(&block)
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
def Clee._run!(name = 'clee', *args, &block)
|
600
|
+
$stdout.sync = true
|
601
|
+
$stderr.sync = true
|
602
|
+
|
603
|
+
%w[ PIPE INT ].each{|signal| Signal.trap(signal, "EXIT")}
|
604
|
+
|
605
|
+
klass = Clee.klass_for(name, &block)
|
606
|
+
|
607
|
+
clee = klass.new
|
608
|
+
|
609
|
+
clee._run!(*args)
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
BEGIN {
|
614
|
+
Object.send(:remove_const, :Clee) if Object.const_defined?(:Clee)
|
615
|
+
|
616
|
+
def Clee(*args, &block)
|
617
|
+
clee(*args, &block)
|
618
|
+
end
|
619
|
+
|
620
|
+
def clee(name = 'clee', *args, &block)
|
621
|
+
Clee._run!(name, *args, &block)
|
622
|
+
end
|
623
|
+
}
|
metadata
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: clee
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ara T. Howard
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2025-03-09 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: "`clee` has everything you need, and nothing you don't"
|
14
|
+
email: ara.t.howard@gmail.com
|
15
|
+
executables:
|
16
|
+
- clee
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- LICENSE.md
|
21
|
+
- README.md
|
22
|
+
- Rakefile
|
23
|
+
- TODO.md
|
24
|
+
- bin/clee
|
25
|
+
- clee.gemspec
|
26
|
+
- docs/sunwukong.md
|
27
|
+
- lib/clee.rb
|
28
|
+
- lib/clee/_lib.rb
|
29
|
+
homepage: https://github.com/ahoward/clee
|
30
|
+
licenses:
|
31
|
+
- LicenseRef-LICENSE.md
|
32
|
+
metadata: {}
|
33
|
+
post_install_message:
|
34
|
+
rdoc_options: []
|
35
|
+
require_paths:
|
36
|
+
- lib
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '3.0'
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
requirements: []
|
48
|
+
rubygems_version: 3.5.16
|
49
|
+
signing_key:
|
50
|
+
specification_version: 4
|
51
|
+
summary: "`clee` is a tiny, 0 dependency, DSL for building über clean CLIs in Ruby"
|
52
|
+
test_files: []
|