cielli 4.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +40 -0
- data/README.md.erb +21 -0
- data/Rakefile +389 -0
- data/bin/cielli +35 -0
- data/cielli.gemspec +41 -0
- data/lib/cielli.rb +240 -0
- data/lib/cielli/_lib.rb +49 -0
- data/lib/cielli/slug.rb +25 -0
- data/lib/cielli/utils.rb +277 -0
- data/samples/a.rb +9 -0
- metadata +53 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a879b0879f37e07178e19a7a5c2bd444ba2b0e52816ee65447fd20e889ffa620
|
4
|
+
data.tar.gz: 9dae86ada4ae248e61156b8d5b32da04411656b775e9ea767d04c058038c1484
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c5c19a68907b8e0c4f00d90edb0255f87bc68dc583bc9f629e6da3f01a3a3d47e8256583e56e50a09d6812d7a7faf9aa6e47b0a22f89547ccb3b203b8eb4be8c
|
7
|
+
data.tar.gz: 649d20e2a90c9762da2d5c276a20a65d2481e9793982855a5ed343d3dd9604111dcbe298147ab5546343640b0c568c7846c1ce1dba3c4cca4dd5c9955915411b
|
data/README.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
NAME
|
2
|
+
====
|
3
|
+
cielli
|
4
|
+
|
5
|
+
SYNOPSIS
|
6
|
+
========
|
7
|
+
a minimalist's toolkit for quickly writing well behaved CLI/cee-el-ahy programs
|
8
|
+
|
9
|
+
DESCRIPTION
|
10
|
+
===========
|
11
|
+
|
12
|
+
cielli is the command line tool your mother wanted you to use. i learned this [the hard way](https://github.com/ahoward/main].
|
13
|
+
|
14
|
+
|
15
|
+
SAMPLES
|
16
|
+
=======
|
17
|
+
|
18
|
+
<========< samples/a.rb >========>
|
19
|
+
|
20
|
+
~ > cat samples/a.rb
|
21
|
+
|
22
|
+
#! /usr/bin/env ruby
|
23
|
+
|
24
|
+
require 'cielli'
|
25
|
+
|
26
|
+
cli do
|
27
|
+
run do
|
28
|
+
p [@argv, @options]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
~ > ruby samples/a.rb
|
33
|
+
|
34
|
+
[[], {}]
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
INSTALL
|
39
|
+
=======
|
40
|
+
gem install cielli -v 0.4.2
|
data/README.md.erb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
NAME
|
2
|
+
====
|
3
|
+
<%= This.lib %>
|
4
|
+
|
5
|
+
SYNOPSIS
|
6
|
+
========
|
7
|
+
<%= This.summary %>
|
8
|
+
|
9
|
+
DESCRIPTION
|
10
|
+
===========
|
11
|
+
|
12
|
+
cielli is the command line tool your mother wanted you to use. i learned this [the hard way](https://github.com/ahoward/main].
|
13
|
+
|
14
|
+
|
15
|
+
SAMPLES
|
16
|
+
=======
|
17
|
+
<%= samples %>
|
18
|
+
|
19
|
+
INSTALL
|
20
|
+
=======
|
21
|
+
gem install <%= lib %> -v <%= version %>
|
data/Rakefile
ADDED
@@ -0,0 +1,389 @@
|
|
1
|
+
This.author = "Ara T. Howard"
|
2
|
+
This.email = "ara.t.howard@gmail.com"
|
3
|
+
This.homepage = "https://github.com/ahoward/#{ This.lib }"
|
4
|
+
|
5
|
+
task :license do
|
6
|
+
open('LICENSE', 'w'){|fd| fd.puts "Ruby"}
|
7
|
+
end
|
8
|
+
|
9
|
+
task :default do
|
10
|
+
puts((Rake::Task.tasks.map{|task| task.name.gsub(/::/,':')} - ['default']).sort)
|
11
|
+
end
|
12
|
+
|
13
|
+
task :test do
|
14
|
+
run_tests!
|
15
|
+
end
|
16
|
+
|
17
|
+
namespace :test do
|
18
|
+
task(:unit){ run_tests!(:unit) }
|
19
|
+
task(:functional){ run_tests!(:functional) }
|
20
|
+
task(:integration){ run_tests!(:integration) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def run_tests!(which = nil)
|
24
|
+
which ||= '**'
|
25
|
+
test_dir = File.join(This.dir, "test")
|
26
|
+
test_glob ||= File.join(test_dir, "#{ which }/**_test.rb")
|
27
|
+
test_rbs = Dir.glob(test_glob).sort
|
28
|
+
|
29
|
+
div = ('=' * 119)
|
30
|
+
line = ('-' * 119)
|
31
|
+
|
32
|
+
test_rbs.each_with_index do |test_rb, index|
|
33
|
+
testno = index + 1
|
34
|
+
command = "#{ This.ruby } -w -I ./lib -I ./test/lib #{ test_rb }"
|
35
|
+
|
36
|
+
puts
|
37
|
+
say(div, :color => :cyan, :bold => true)
|
38
|
+
say("@#{ testno } => ", :bold => true, :method => :print)
|
39
|
+
say(command, :color => :cyan, :bold => true)
|
40
|
+
say(line, :color => :cyan, :bold => true)
|
41
|
+
|
42
|
+
system(command)
|
43
|
+
|
44
|
+
say(line, :color => :cyan, :bold => true)
|
45
|
+
|
46
|
+
status = $?.exitstatus
|
47
|
+
|
48
|
+
if status.zero?
|
49
|
+
say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
|
50
|
+
say("SUCCESS", :color => :green, :bold => true)
|
51
|
+
else
|
52
|
+
say("@#{ testno } <= ", :bold => true, :color => :white, :method => :print)
|
53
|
+
say("FAILURE", :color => :red, :bold => true)
|
54
|
+
end
|
55
|
+
say(line, :color => :cyan, :bold => true)
|
56
|
+
|
57
|
+
exit(status) unless status.zero?
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
task :gemspec do
|
63
|
+
ignore_extensions = ['git', 'svn', 'tmp', /sw./, 'bak', 'gem']
|
64
|
+
ignore_directories = ['pkg']
|
65
|
+
ignore_files = ['test/log']
|
66
|
+
|
67
|
+
shiteless =
|
68
|
+
lambda do |list|
|
69
|
+
list.delete_if do |entry|
|
70
|
+
next unless test(?e, entry)
|
71
|
+
extension = File.basename(entry).split(%r/[.]/).last
|
72
|
+
ignore_extensions.any?{|ext| ext === extension}
|
73
|
+
end
|
74
|
+
list.delete_if do |entry|
|
75
|
+
next unless test(?d, entry)
|
76
|
+
dirname = File.expand_path(entry)
|
77
|
+
ignore_directories.any?{|dir| File.expand_path(dir) == dirname}
|
78
|
+
end
|
79
|
+
list.delete_if do |entry|
|
80
|
+
next unless test(?f, entry)
|
81
|
+
filename = File.expand_path(entry)
|
82
|
+
ignore_files.any?{|file| File.expand_path(file) == filename}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
lib = This.lib
|
87
|
+
object = This.object
|
88
|
+
version = This.version
|
89
|
+
files = shiteless[Dir::glob("**/**")]
|
90
|
+
executables = shiteless[Dir::glob("bin/*")].map{|exe| File.basename(exe)}
|
91
|
+
#has_rdoc = true #File.exist?('doc')
|
92
|
+
test_files = "test/#{ lib }.rb" if File.file?("test/#{ lib }.rb")
|
93
|
+
summary = object.respond_to?(:summary) ? object.summary : "summary: #{ lib } kicks the ass"
|
94
|
+
description = object.respond_to?(:description) ? object.description : "description: #{ lib } kicks the ass"
|
95
|
+
license = object.respond_to?(:license) ? object.license : "Ruby"
|
96
|
+
|
97
|
+
if This.extensions.nil?
|
98
|
+
This.extensions = []
|
99
|
+
extensions = This.extensions
|
100
|
+
%w( Makefile configure extconf.rb ).each do |ext|
|
101
|
+
extensions << ext if File.exists?(ext)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
extensions = [extensions].flatten.compact
|
105
|
+
|
106
|
+
if This.dependencies.nil?
|
107
|
+
dependencies = []
|
108
|
+
else
|
109
|
+
case This.dependencies
|
110
|
+
when Hash
|
111
|
+
dependencies = This.dependencies.values
|
112
|
+
when Array
|
113
|
+
dependencies = This.dependencies
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
template =
|
118
|
+
if test(?e, 'gemspec.erb')
|
119
|
+
Template{ IO.read('gemspec.erb') }
|
120
|
+
else
|
121
|
+
Template {
|
122
|
+
<<-__
|
123
|
+
## <%= lib %>.gemspec
|
124
|
+
#
|
125
|
+
|
126
|
+
Gem::Specification::new do |spec|
|
127
|
+
spec.name = <%= lib.inspect %>
|
128
|
+
spec.version = <%= version.inspect %>
|
129
|
+
spec.platform = Gem::Platform::RUBY
|
130
|
+
spec.summary = <%= lib.inspect %>
|
131
|
+
spec.description = <%= description.inspect %>
|
132
|
+
spec.license = <%= license.inspect %>
|
133
|
+
|
134
|
+
spec.files =\n<%= files.sort.pretty_inspect %>
|
135
|
+
spec.executables = <%= executables.inspect %>
|
136
|
+
|
137
|
+
spec.require_path = "lib"
|
138
|
+
|
139
|
+
spec.test_files = <%= test_files.inspect %>
|
140
|
+
|
141
|
+
<% dependencies.each do |lib_version| %>
|
142
|
+
spec.add_dependency(*<%= Array(lib_version).flatten.inspect %>)
|
143
|
+
<% end %>
|
144
|
+
|
145
|
+
spec.extensions.push(*<%= extensions.inspect %>)
|
146
|
+
|
147
|
+
spec.author = <%= This.author.inspect %>
|
148
|
+
spec.email = <%= This.email.inspect %>
|
149
|
+
spec.homepage = <%= This.homepage.inspect %>
|
150
|
+
end
|
151
|
+
__
|
152
|
+
}
|
153
|
+
end
|
154
|
+
|
155
|
+
Fu.mkdir_p(This.pkgdir)
|
156
|
+
gemspec = "#{ lib }.gemspec"
|
157
|
+
open(gemspec, "w"){|fd| fd.puts(template)}
|
158
|
+
This.gemspec = gemspec
|
159
|
+
end
|
160
|
+
|
161
|
+
task :gem => [:clean, :gemspec] do
|
162
|
+
Fu.mkdir_p(This.pkgdir)
|
163
|
+
before = Dir['*.gem']
|
164
|
+
cmd = "gem build #{ This.gemspec }"
|
165
|
+
`#{ cmd }`
|
166
|
+
after = Dir['*.gem']
|
167
|
+
gem = ((after - before).first || after.first) or abort('no gem!')
|
168
|
+
Fu.mv(gem, This.pkgdir)
|
169
|
+
This.gem = File.join(This.pkgdir, File.basename(gem))
|
170
|
+
end
|
171
|
+
|
172
|
+
task :readme do
|
173
|
+
lib = This.lib
|
174
|
+
version = This.version
|
175
|
+
|
176
|
+
samples = nil
|
177
|
+
prompt = '~ > '
|
178
|
+
|
179
|
+
Dir['sample*/*'].sort.each do |sample|
|
180
|
+
samples ||= ''
|
181
|
+
|
182
|
+
samples << "\n" << " <========< #{ sample } >========>" << "\n\n"
|
183
|
+
|
184
|
+
cmd = "cat #{ sample }"
|
185
|
+
samples << Util.indent(prompt + cmd, 2) << "\n\n"
|
186
|
+
samples << Util.indent(`#{ cmd }`, 4) << "\n"
|
187
|
+
|
188
|
+
cmd = "ruby #{ sample }"
|
189
|
+
samples << Util.indent(prompt + cmd, 2) << "\n\n"
|
190
|
+
|
191
|
+
cmd = "ruby -e'STDOUT.sync=true; exec %(ruby -I ./lib #{ sample })'"
|
192
|
+
samples << Util.indent(`#{ cmd } 2>&1`, 4) << "\n"
|
193
|
+
end
|
194
|
+
|
195
|
+
template =
|
196
|
+
if test(?e, 'README.md.erb')
|
197
|
+
Template{ IO.read('README.md.erb') }
|
198
|
+
else
|
199
|
+
IO.binread('README.md')
|
200
|
+
end
|
201
|
+
|
202
|
+
open("README.md", "w"){|fd| fd.puts template}
|
203
|
+
|
204
|
+
puts IO.binread("README.md")
|
205
|
+
end
|
206
|
+
|
207
|
+
task :clean do
|
208
|
+
Dir[File.join(This.pkgdir, '**/**')].each{|entry| Fu.rm_rf(entry)}
|
209
|
+
end
|
210
|
+
|
211
|
+
task :release => [:clean, :readme, :gemspec, :gem] do
|
212
|
+
gems = Dir[File.join(This.pkgdir, '*.gem')].flatten
|
213
|
+
raise "which one? : #{ gems.inspect }" if gems.size > 1
|
214
|
+
raise "no gems?" if gems.size < 1
|
215
|
+
|
216
|
+
cmd = "gem push #{ This.gem }"
|
217
|
+
puts cmd
|
218
|
+
puts
|
219
|
+
system(cmd)
|
220
|
+
abort("cmd(#{ cmd }) failed with (#{ $?.inspect })") unless $?.exitstatus.zero?
|
221
|
+
end
|
222
|
+
|
223
|
+
|
224
|
+
|
225
|
+
|
226
|
+
|
227
|
+
BEGIN {
|
228
|
+
# support for this rakefile
|
229
|
+
#
|
230
|
+
$VERBOSE = nil
|
231
|
+
|
232
|
+
require 'ostruct'
|
233
|
+
require 'erb'
|
234
|
+
require 'fileutils'
|
235
|
+
require 'rbconfig'
|
236
|
+
require 'pp'
|
237
|
+
|
238
|
+
# fu shortcut
|
239
|
+
#
|
240
|
+
Fu = FileUtils
|
241
|
+
|
242
|
+
# cache a bunch of stuff about this rakefile/environment
|
243
|
+
#
|
244
|
+
This = OpenStruct.new
|
245
|
+
|
246
|
+
This.file = File.expand_path(__FILE__)
|
247
|
+
This.dir = File.dirname(This.file)
|
248
|
+
This.pkgdir = File.join(This.dir, 'pkg')
|
249
|
+
This.lib = File.basename(Dir.pwd).sub(/[-].*$/, '')
|
250
|
+
|
251
|
+
# load _lib
|
252
|
+
#
|
253
|
+
_lib = ["./lib/#{ This.lib }/_lib.rb", "./lib/#{ This.lib }.rb"].detect{|l| test(?s, l)}
|
254
|
+
unless _lib
|
255
|
+
abort "could not find a _lib in ./lib!?"
|
256
|
+
end
|
257
|
+
This._lib = _lib
|
258
|
+
require This._lib
|
259
|
+
|
260
|
+
|
261
|
+
# extract name from _lib
|
262
|
+
#
|
263
|
+
lines = IO.binread(This._lib).split("\n")
|
264
|
+
re = %r`\A \s* (module|class) \s+ ([^\s]+) \s* \z`iomx
|
265
|
+
name = nil
|
266
|
+
lines.each do |line|
|
267
|
+
match = line.match(re)
|
268
|
+
if match
|
269
|
+
name = match.to_a.last
|
270
|
+
break
|
271
|
+
end
|
272
|
+
end
|
273
|
+
unless name
|
274
|
+
abort "could not extract `name` from #{ This._lib }"
|
275
|
+
end
|
276
|
+
This.name = name
|
277
|
+
|
278
|
+
# now, fully grok This
|
279
|
+
#
|
280
|
+
This.object = eval(This.name)
|
281
|
+
|
282
|
+
version = This.object.send(:version)
|
283
|
+
This.version = version
|
284
|
+
|
285
|
+
if This.object.respond_to?(:dependencies)
|
286
|
+
This.dependencies = This.object.dependencies
|
287
|
+
end
|
288
|
+
|
289
|
+
if This.object.respond_to?(:summary)
|
290
|
+
This.summary = This.object.summary
|
291
|
+
end
|
292
|
+
|
293
|
+
# discover full path to this ruby executable
|
294
|
+
#
|
295
|
+
c = RbConfig::CONFIG
|
296
|
+
bindir = c["bindir"] || c['BINDIR']
|
297
|
+
ruby_install_name = c['ruby_install_name'] || c['RUBY_INSTALL_NAME'] || 'ruby'
|
298
|
+
ruby_ext = c['EXEEXT'] || ''
|
299
|
+
ruby = File.join(bindir, (ruby_install_name + ruby_ext))
|
300
|
+
This.ruby = ruby
|
301
|
+
|
302
|
+
# some utils
|
303
|
+
#
|
304
|
+
module Util
|
305
|
+
def indent(s, n = 2)
|
306
|
+
s = unindent(s)
|
307
|
+
ws = ' ' * n
|
308
|
+
s.gsub(%r/^/, ws)
|
309
|
+
end
|
310
|
+
|
311
|
+
def unindent(s)
|
312
|
+
indent = nil
|
313
|
+
s.each_line do |line|
|
314
|
+
next if line =~ %r/^\s*$/
|
315
|
+
indent = line[%r/^\s*/] and break
|
316
|
+
end
|
317
|
+
indent ? s.gsub(%r/^#{ indent }/, "") : s
|
318
|
+
end
|
319
|
+
extend self
|
320
|
+
end
|
321
|
+
|
322
|
+
# template support
|
323
|
+
#
|
324
|
+
class Template
|
325
|
+
def initialize(&block)
|
326
|
+
@block = block
|
327
|
+
@template = block.call.to_s
|
328
|
+
end
|
329
|
+
def expand(b=nil)
|
330
|
+
ERB.new(Util.unindent(@template)).result((b||@block).binding)
|
331
|
+
end
|
332
|
+
alias_method 'to_s', 'expand'
|
333
|
+
end
|
334
|
+
def Template(*args, &block) Template.new(*args, &block) end
|
335
|
+
|
336
|
+
# colored console output support
|
337
|
+
#
|
338
|
+
This.ansi = {
|
339
|
+
:clear => "\e[0m",
|
340
|
+
:reset => "\e[0m",
|
341
|
+
:erase_line => "\e[K",
|
342
|
+
:erase_char => "\e[P",
|
343
|
+
:bold => "\e[1m",
|
344
|
+
:dark => "\e[2m",
|
345
|
+
:underline => "\e[4m",
|
346
|
+
:underscore => "\e[4m",
|
347
|
+
:blink => "\e[5m",
|
348
|
+
:reverse => "\e[7m",
|
349
|
+
:concealed => "\e[8m",
|
350
|
+
:black => "\e[30m",
|
351
|
+
:red => "\e[31m",
|
352
|
+
:green => "\e[32m",
|
353
|
+
:yellow => "\e[33m",
|
354
|
+
:blue => "\e[34m",
|
355
|
+
:magenta => "\e[35m",
|
356
|
+
:cyan => "\e[36m",
|
357
|
+
:white => "\e[37m",
|
358
|
+
:on_black => "\e[40m",
|
359
|
+
:on_red => "\e[41m",
|
360
|
+
:on_green => "\e[42m",
|
361
|
+
:on_yellow => "\e[43m",
|
362
|
+
:on_blue => "\e[44m",
|
363
|
+
:on_magenta => "\e[45m",
|
364
|
+
:on_cyan => "\e[46m",
|
365
|
+
:on_white => "\e[47m"
|
366
|
+
}
|
367
|
+
def say(phrase, *args)
|
368
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
369
|
+
options[:color] = args.shift.to_s.to_sym unless args.empty?
|
370
|
+
keys = options.keys
|
371
|
+
keys.each{|key| options[key.to_s.to_sym] = options.delete(key)}
|
372
|
+
|
373
|
+
color = options[:color]
|
374
|
+
bold = options.has_key?(:bold)
|
375
|
+
|
376
|
+
parts = [phrase]
|
377
|
+
parts.unshift(This.ansi[color]) if color
|
378
|
+
parts.unshift(This.ansi[:bold]) if bold
|
379
|
+
parts.push(This.ansi[:clear]) if parts.size > 1
|
380
|
+
|
381
|
+
method = options[:method] || :puts
|
382
|
+
|
383
|
+
Kernel.send(method, parts.join)
|
384
|
+
end
|
385
|
+
|
386
|
+
# always run out of the project dir
|
387
|
+
#
|
388
|
+
Dir.chdir(This.dir)
|
389
|
+
}
|
data/bin/cielli
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require_relative '../lib/cielli.rb'
|
5
|
+
|
6
|
+
cielli do
|
7
|
+
run do
|
8
|
+
puts CLI
|
9
|
+
end
|
10
|
+
|
11
|
+
CLI = <<~________
|
12
|
+
#! /usr/bin/env ruby
|
13
|
+
# encoding: utf-8
|
14
|
+
|
15
|
+
require 'cielli'
|
16
|
+
|
17
|
+
cielli do
|
18
|
+
help <<~____
|
19
|
+
#{ Cielli.utils.indent(Cielli::DEFAULT_HELP, 4).strip }
|
20
|
+
____
|
21
|
+
|
22
|
+
run do
|
23
|
+
p [@argv, @options]
|
24
|
+
end
|
25
|
+
|
26
|
+
run(:foo) do
|
27
|
+
p [@mode, @argv, @options]
|
28
|
+
end
|
29
|
+
|
30
|
+
run(:bar) do
|
31
|
+
p [@mode, @argv, @options]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
________
|
35
|
+
end
|
data/cielli.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
## cielli.gemspec
|
2
|
+
#
|
3
|
+
|
4
|
+
Gem::Specification::new do |spec|
|
5
|
+
spec.name = "cielli"
|
6
|
+
spec.version = "4.2.2"
|
7
|
+
spec.platform = Gem::Platform::RUBY
|
8
|
+
spec.summary = "cielli"
|
9
|
+
spec.description = "description: cielli kicks the ass"
|
10
|
+
spec.license = "Ruby"
|
11
|
+
|
12
|
+
spec.files =
|
13
|
+
["README.md",
|
14
|
+
"README.md.erb",
|
15
|
+
"Rakefile",
|
16
|
+
"bin",
|
17
|
+
"bin/cielli",
|
18
|
+
"cielli.gemspec",
|
19
|
+
"lib",
|
20
|
+
"lib/cielli",
|
21
|
+
"lib/cielli.rb",
|
22
|
+
"lib/cielli/_lib.rb",
|
23
|
+
"lib/cielli/slug.rb",
|
24
|
+
"lib/cielli/utils.rb",
|
25
|
+
"samples",
|
26
|
+
"samples/a.rb"]
|
27
|
+
|
28
|
+
spec.executables = ["cielli"]
|
29
|
+
|
30
|
+
spec.require_path = "lib"
|
31
|
+
|
32
|
+
spec.test_files = nil
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
spec.extensions.push(*[])
|
37
|
+
|
38
|
+
spec.author = "Ara T. Howard"
|
39
|
+
spec.email = "ara.t.howard@gmail.com"
|
40
|
+
spec.homepage = "https://github.com/ahoward/cielli"
|
41
|
+
end
|
data/lib/cielli.rb
ADDED
@@ -0,0 +1,240 @@
|
|
1
|
+
#
|
2
|
+
Object.send(:remove_const, :Cielli) if Object.const_defined?(:Cielli)
|
3
|
+
|
4
|
+
#
|
5
|
+
require 'json'
|
6
|
+
require 'yaml'
|
7
|
+
require 'base64'
|
8
|
+
require 'securerandom'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'pathname'
|
11
|
+
require 'set'
|
12
|
+
require 'openssl'
|
13
|
+
require 'uri'
|
14
|
+
require 'cgi'
|
15
|
+
require 'shellwords'
|
16
|
+
require 'tmpdir'
|
17
|
+
require 'tempfile'
|
18
|
+
require 'pp'
|
19
|
+
require 'open3'
|
20
|
+
|
21
|
+
#
|
22
|
+
require_relative 'cielli/_lib'
|
23
|
+
|
24
|
+
#
|
25
|
+
class Cielli
|
26
|
+
attr_accessor :source
|
27
|
+
attr_accessor :root
|
28
|
+
attr_accessor :env
|
29
|
+
attr_accessor :argv
|
30
|
+
attr_accessor :stdout
|
31
|
+
attr_accessor :stdin
|
32
|
+
attr_accessor :stderr
|
33
|
+
attr_accessor :help
|
34
|
+
|
35
|
+
def run!(env = ENV, argv = ARGV)
|
36
|
+
init!(env, argv)
|
37
|
+
parse_command_line!
|
38
|
+
set_mode!
|
39
|
+
run_mode!
|
40
|
+
end
|
41
|
+
|
42
|
+
def init!(env, argv)
|
43
|
+
@klass = self.class
|
44
|
+
@env = env.to_hash.dup
|
45
|
+
@argv = argv.map{|arg| arg.dup}
|
46
|
+
@stdout = $stdout.dup
|
47
|
+
@stdin = $stdin.dup
|
48
|
+
@stderr = $stderr.dup
|
49
|
+
@help = @klass.help || utils.unindent(EXAMPLE_HELP)
|
50
|
+
end
|
51
|
+
|
52
|
+
EXAMPLE_HELP = <<-__
|
53
|
+
NAME
|
54
|
+
#TODO
|
55
|
+
|
56
|
+
SYNOPSIS
|
57
|
+
#TODO
|
58
|
+
|
59
|
+
DESCRIPTION
|
60
|
+
#TODO
|
61
|
+
|
62
|
+
EXAMPLES
|
63
|
+
#TODO
|
64
|
+
__
|
65
|
+
|
66
|
+
def parse_command_line!
|
67
|
+
@options = Hash.new
|
68
|
+
@opts = Hash.new
|
69
|
+
|
70
|
+
argv = []
|
71
|
+
head = []
|
72
|
+
tail = []
|
73
|
+
|
74
|
+
%w[ :: -- ].each do |stop|
|
75
|
+
if((i = @argv.index(stop)))
|
76
|
+
head = @argv.slice(0 ... i)
|
77
|
+
tail = @argv.slice((i + 1) ... @argv.size)
|
78
|
+
@argv = head
|
79
|
+
break
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
@argv.each do |arg|
|
84
|
+
case
|
85
|
+
when arg =~ %r`^\s*:([^:\s]+)[=](.+)`
|
86
|
+
key = $1
|
87
|
+
val = $2
|
88
|
+
@options[key] = val
|
89
|
+
when arg =~ %r`^\s*(:+)(.+)`
|
90
|
+
leader = $1
|
91
|
+
key = $2
|
92
|
+
val = leader.size.odd?
|
93
|
+
@options[key] = val
|
94
|
+
else
|
95
|
+
argv.push(arg)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
argv += tail
|
100
|
+
|
101
|
+
@argv.replace(argv)
|
102
|
+
|
103
|
+
@options.each do |key, val|
|
104
|
+
@opts[key.to_s.to_sym] = val
|
105
|
+
end
|
106
|
+
|
107
|
+
[@options, @opts]
|
108
|
+
end
|
109
|
+
|
110
|
+
def set_mode!
|
111
|
+
case
|
112
|
+
when respond_to?("run_#{ @argv[0] }")
|
113
|
+
@mode = @argv.shift
|
114
|
+
else
|
115
|
+
@mode = nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def run_mode!
|
120
|
+
if @mode
|
121
|
+
return send("run_#{ @mode }")
|
122
|
+
else
|
123
|
+
if respond_to?(:run)
|
124
|
+
return send(:run)
|
125
|
+
end
|
126
|
+
|
127
|
+
if @argv.empty?
|
128
|
+
run_help!
|
129
|
+
else
|
130
|
+
abort("#{ $0 } help")
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def run_help!
|
136
|
+
STDOUT.puts(@help)
|
137
|
+
end
|
138
|
+
|
139
|
+
def help!
|
140
|
+
run_help!
|
141
|
+
abort
|
142
|
+
end
|
143
|
+
|
144
|
+
#
|
145
|
+
def Cielli.help(*args)
|
146
|
+
@help ||= nil
|
147
|
+
|
148
|
+
unless args.empty?
|
149
|
+
@help = utils.unindent(args.join)
|
150
|
+
end
|
151
|
+
|
152
|
+
@help
|
153
|
+
end
|
154
|
+
|
155
|
+
def Cielli.run(*args, &block)
|
156
|
+
modes =
|
157
|
+
if args.empty?
|
158
|
+
[nil]
|
159
|
+
else
|
160
|
+
args
|
161
|
+
end
|
162
|
+
|
163
|
+
modes.each do |mode|
|
164
|
+
method_name =
|
165
|
+
if mode
|
166
|
+
"run_#{ mode }"
|
167
|
+
else
|
168
|
+
"run"
|
169
|
+
end
|
170
|
+
|
171
|
+
define_method(method_name, &block)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
#
|
176
|
+
def Cielli.klass_for(&block)
|
177
|
+
Class.new(Cielli) do |klass|
|
178
|
+
def klass.name; "Cielli::Klass__#{ SecureRandom.uuid.to_s.gsub('-', '_') }"; end
|
179
|
+
klass.class_eval(&block)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def Cielli.run!(*args, &block)
|
184
|
+
STDOUT.sync = true
|
185
|
+
STDERR.sync = true
|
186
|
+
|
187
|
+
%w[ PIPE INT ].each{|signal| Signal.trap(signal, "EXIT")}
|
188
|
+
|
189
|
+
cielli = (
|
190
|
+
source =
|
191
|
+
if binding.respond_to?(:source_location)
|
192
|
+
File.expand_path(binding.source_location.first)
|
193
|
+
else
|
194
|
+
File.expand_path(eval('__FILE__', block.binding))
|
195
|
+
end
|
196
|
+
|
197
|
+
root = File.dirname(source)
|
198
|
+
|
199
|
+
klass = Cielli.klass_for(&block)
|
200
|
+
|
201
|
+
instance = klass.new
|
202
|
+
|
203
|
+
instance.source = source
|
204
|
+
|
205
|
+
instance.root = root
|
206
|
+
|
207
|
+
instance
|
208
|
+
)
|
209
|
+
|
210
|
+
cielli.run!(*args)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
#
|
215
|
+
require_relative 'cielli/utils'
|
216
|
+
|
217
|
+
def Cielli.utils(&block)
|
218
|
+
block ? Cielli::Utils.module_eval(&block) : Cielli::Utils
|
219
|
+
end
|
220
|
+
|
221
|
+
def Cielli.u(&block)
|
222
|
+
Cielli.utils(&block)
|
223
|
+
end
|
224
|
+
|
225
|
+
def utils
|
226
|
+
Cielli::Utils
|
227
|
+
end
|
228
|
+
|
229
|
+
def u
|
230
|
+
Cielli::Utils
|
231
|
+
end
|
232
|
+
|
233
|
+
#
|
234
|
+
class << self
|
235
|
+
def cielli(*args, &block)
|
236
|
+
Cielli.run!(*args, &block)
|
237
|
+
end
|
238
|
+
|
239
|
+
alias_method :cli, :cielli
|
240
|
+
end
|
data/lib/cielli/_lib.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
class Cielli
|
2
|
+
Version = '4.2.2' unless defined?(Version)
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def version
|
6
|
+
Version
|
7
|
+
end
|
8
|
+
|
9
|
+
def summary
|
10
|
+
"a minimalist's toolkit for quickly writing well behaved CLI/cee-el-ahy programs"
|
11
|
+
end
|
12
|
+
|
13
|
+
def dependencies
|
14
|
+
{ }
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_dependencies!
|
18
|
+
begin
|
19
|
+
require 'rubygems'
|
20
|
+
rescue LoadError
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
dependencies.each do |lib, dependency|
|
25
|
+
gem(*dependency) if defined?(gem)
|
26
|
+
require(lib)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def libdir(*args, &block)
|
31
|
+
@libdir ||= File.dirname(File.expand_path(__FILE__).sub(/\.rb$/,''))
|
32
|
+
args.empty? ? @libdir : File.join(@libdir, *args)
|
33
|
+
ensure
|
34
|
+
if block
|
35
|
+
begin
|
36
|
+
$LOAD_PATH.unshift(@libdir)
|
37
|
+
block.call()
|
38
|
+
ensure
|
39
|
+
$LOAD_PATH.shift()
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def load(*libs)
|
45
|
+
libs = libs.join(' ').scan(/[^\s+]+/)
|
46
|
+
libdir{ libs.each{|lib| Kernel.load(lib) } }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/cielli/slug.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
class Cielli
|
2
|
+
class Slug < ::String
|
3
|
+
Join = '-'
|
4
|
+
|
5
|
+
def Slug.for(*args)
|
6
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
7
|
+
|
8
|
+
join = (options[:join] || options['join'] || Join).to_s
|
9
|
+
|
10
|
+
string = args.flatten.compact.join(' ')
|
11
|
+
|
12
|
+
tokens = string.scan(%r`[^\s#{ join }]+`)
|
13
|
+
|
14
|
+
tokens.map! do |token|
|
15
|
+
token.gsub(%r`[^\p{L}/.]`, '').downcase
|
16
|
+
end
|
17
|
+
|
18
|
+
tokens.map! do |token|
|
19
|
+
token.gsub(%r`[/.]`, join * 2)
|
20
|
+
end
|
21
|
+
|
22
|
+
tokens.join(join)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/cielli/utils.rb
ADDED
@@ -0,0 +1,277 @@
|
|
1
|
+
class Cielli
|
2
|
+
module Utils
|
3
|
+
def unindent(arg)
|
4
|
+
string = arg.to_s.dup
|
5
|
+
margin = nil
|
6
|
+
string.each_line do |line|
|
7
|
+
next if line =~ %r/^\s*$/
|
8
|
+
margin = line[%r/^\s*/] and break
|
9
|
+
end
|
10
|
+
string.gsub!(%r/^#{ margin }/, "") if margin
|
11
|
+
margin ? string : nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def indent(arg, *args)
|
15
|
+
opts = extract_options!(args)
|
16
|
+
n = (args.shift || opts[:n] || 2).to_i
|
17
|
+
|
18
|
+
string = unindent(arg)
|
19
|
+
|
20
|
+
indentation = ' ' * n
|
21
|
+
|
22
|
+
string.gsub(/^/, indentation)
|
23
|
+
end
|
24
|
+
|
25
|
+
def esc(*args)
|
26
|
+
args.flatten.compact.map{|arg| Shellwords.escape(arg)}.join(' ')
|
27
|
+
end
|
28
|
+
|
29
|
+
def uuid
|
30
|
+
SecureRandom.uuid
|
31
|
+
end
|
32
|
+
|
33
|
+
def tmpname(*args)
|
34
|
+
opts = extract_options!(*args)
|
35
|
+
|
36
|
+
base = opts.fetch(:base){ uuid }.to_s.strip
|
37
|
+
ext = opts.fetch(:ext){ 'tmp' }.to_s.strip.sub(/^[.]+/, '')
|
38
|
+
basename = opts.fetch(:basename){ "#{ base }.#{ ext }" }
|
39
|
+
|
40
|
+
File.join(Dir.tmpdir, basename)
|
41
|
+
end
|
42
|
+
|
43
|
+
def tmpfile(*args, &block)
|
44
|
+
opts = extract_options!(args)
|
45
|
+
|
46
|
+
path = tmpname(opts)
|
47
|
+
|
48
|
+
|
49
|
+
tmp = open(path, 'w+')
|
50
|
+
tmp.binmode
|
51
|
+
tmp.sync = true
|
52
|
+
|
53
|
+
unless args.empty?
|
54
|
+
src = args.join
|
55
|
+
tmp.write(src)
|
56
|
+
tmp.flush
|
57
|
+
tmp.rewind
|
58
|
+
end
|
59
|
+
|
60
|
+
if block
|
61
|
+
begin
|
62
|
+
block.call(tmp)
|
63
|
+
ensure
|
64
|
+
FileUtilss.rm_rf(path)
|
65
|
+
end
|
66
|
+
else
|
67
|
+
at_exit{ Kernel.system("rm -rf #{ esc(path) }") }
|
68
|
+
return tmp
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def extract_options!(args)
|
73
|
+
unless args.is_a?(Array)
|
74
|
+
args = [args]
|
75
|
+
end
|
76
|
+
|
77
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
78
|
+
|
79
|
+
symbolize_keys!(opts)
|
80
|
+
|
81
|
+
return opts
|
82
|
+
end
|
83
|
+
|
84
|
+
def extract_options(args)
|
85
|
+
opts = extract_options!(args)
|
86
|
+
|
87
|
+
args.push(opts)
|
88
|
+
|
89
|
+
opts
|
90
|
+
end
|
91
|
+
|
92
|
+
def symbolize_keys!(hash)
|
93
|
+
hash.keys.each do |key|
|
94
|
+
if key.is_a?(String)
|
95
|
+
val = hash.delete(key)
|
96
|
+
|
97
|
+
if val.is_a?(Hash)
|
98
|
+
symbolize_keys!(val)
|
99
|
+
end
|
100
|
+
|
101
|
+
hash[key.to_s.gsub('-', '_').to_sym] = val
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
return hash
|
106
|
+
end
|
107
|
+
|
108
|
+
def symbolize_keys(hash)
|
109
|
+
symbolize_keys!(deepcopy(hash))
|
110
|
+
end
|
111
|
+
|
112
|
+
def deepcopy(object)
|
113
|
+
Marshal.load(Marshal.dump(object))
|
114
|
+
end
|
115
|
+
|
116
|
+
def debug!(arg)
|
117
|
+
if arg.is_a?(String)
|
118
|
+
warn "[DEBUG] #{ arg }"
|
119
|
+
else
|
120
|
+
warn "[DEBUG] >\n#{ arg.to_yaml rescue arg.pretty_inspect }"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def debug(arg)
|
125
|
+
debug!(arg) if debug?
|
126
|
+
end
|
127
|
+
|
128
|
+
def debug?
|
129
|
+
ENV['CIELLI_DEBUG'] || ENV['DEBUG']
|
130
|
+
end
|
131
|
+
|
132
|
+
def noop
|
133
|
+
ENV['CIELLI_NOOP'] || ENV['NOOP']
|
134
|
+
end
|
135
|
+
alias_method :noop?, :noop
|
136
|
+
|
137
|
+
def sys!(*args, &block)
|
138
|
+
opts = extract_options!(args)
|
139
|
+
|
140
|
+
cmd = args
|
141
|
+
|
142
|
+
debug(:cmd => cmd)
|
143
|
+
|
144
|
+
open3 = (
|
145
|
+
block ||
|
146
|
+
opts[:stdin] ||
|
147
|
+
opts[:quiet] ||
|
148
|
+
opts[:capture]
|
149
|
+
)
|
150
|
+
|
151
|
+
die = proc do |command, *args|
|
152
|
+
status = args.shift || $?
|
153
|
+
warn("#{ [command].join(' ') } #=> status=#{ status.exitstatus }") unless opts[:quiet]
|
154
|
+
exit(1)
|
155
|
+
end
|
156
|
+
|
157
|
+
if(open3)
|
158
|
+
stdin = opts[:stdin]
|
159
|
+
stdout = ''
|
160
|
+
stderr = ''
|
161
|
+
status = nil
|
162
|
+
|
163
|
+
begin
|
164
|
+
Open3.popen3(*cmd) do |i, o, e, t|
|
165
|
+
ot = async_reader_thread_for(o, stdout)
|
166
|
+
et = async_reader_thread_for(e, stderr)
|
167
|
+
|
168
|
+
i.write(stdin) if stdin
|
169
|
+
i.close
|
170
|
+
|
171
|
+
ot.join
|
172
|
+
et.join
|
173
|
+
|
174
|
+
status = t.value
|
175
|
+
end
|
176
|
+
rescue
|
177
|
+
die[cmd]
|
178
|
+
end
|
179
|
+
|
180
|
+
if status.exitstatus == 0
|
181
|
+
result = nil
|
182
|
+
|
183
|
+
if opts[:capture]
|
184
|
+
result = stdout.to_s.strip
|
185
|
+
else
|
186
|
+
if block
|
187
|
+
result = block.call(status, stdout, stderr)
|
188
|
+
else
|
189
|
+
result = [status, stdout, stderr]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
return(result)
|
194
|
+
else
|
195
|
+
die[cmd, status]
|
196
|
+
end
|
197
|
+
else
|
198
|
+
env = opts[:env] || {}
|
199
|
+
argv = [env, *cmd]
|
200
|
+
system(*argv) || die[cmd]
|
201
|
+
return true
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def sys(*args, &block)
|
206
|
+
opts = extract_options!(args)
|
207
|
+
opts[:quiet] = true
|
208
|
+
|
209
|
+
args.push(opts)
|
210
|
+
|
211
|
+
begin
|
212
|
+
sys!(*args, &block)
|
213
|
+
rescue Object
|
214
|
+
false
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def async_reader_thread_for(io, accum)
|
219
|
+
Thread.new(io, accum) do |i, a|
|
220
|
+
Thread.current.abort_on_exception = true
|
221
|
+
|
222
|
+
while true
|
223
|
+
buf = i.read(8192)
|
224
|
+
|
225
|
+
if buf
|
226
|
+
a << buf
|
227
|
+
else
|
228
|
+
break
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def realpath(path)
|
235
|
+
Pathname.new(path.to_s).expand_path.realpath.to_s
|
236
|
+
end
|
237
|
+
|
238
|
+
def filelist(*args, &block)
|
239
|
+
accum = (block || proc{ Set.new }).call
|
240
|
+
raise ArgumentError.new('accum.class != Set') unless accum.is_a?(Set)
|
241
|
+
|
242
|
+
_ = args.last.is_a?(Hash) ? args.pop : {}
|
243
|
+
|
244
|
+
entries = args.flatten.compact.map{|arg| realpath("#{ arg }")}.uniq.sort
|
245
|
+
|
246
|
+
entries.each do |entry|
|
247
|
+
case
|
248
|
+
when test(?f, entry)
|
249
|
+
file = realpath(entry)
|
250
|
+
accum << file
|
251
|
+
|
252
|
+
when test(?d, entry)
|
253
|
+
glob = File.join(entry, '**/**')
|
254
|
+
|
255
|
+
Dir.glob(glob) do |_entry|
|
256
|
+
case
|
257
|
+
when test(?f, _entry)
|
258
|
+
filelist(_entry){ accum }
|
259
|
+
when test(?d, entry)
|
260
|
+
filelist(_entry){ accum }
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
accum.to_a
|
267
|
+
end
|
268
|
+
|
269
|
+
require_relative 'slug.rb'
|
270
|
+
|
271
|
+
def slug_for(*args, &block)
|
272
|
+
Slug.for(*args, &block)
|
273
|
+
end
|
274
|
+
|
275
|
+
extend Utils
|
276
|
+
end
|
277
|
+
end
|
data/samples/a.rb
ADDED
metadata
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cielli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 4.2.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ara T. Howard
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-12-20 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: 'description: cielli kicks the ass'
|
14
|
+
email: ara.t.howard@gmail.com
|
15
|
+
executables:
|
16
|
+
- cielli
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- README.md
|
21
|
+
- README.md.erb
|
22
|
+
- Rakefile
|
23
|
+
- bin/cielli
|
24
|
+
- cielli.gemspec
|
25
|
+
- lib/cielli.rb
|
26
|
+
- lib/cielli/_lib.rb
|
27
|
+
- lib/cielli/slug.rb
|
28
|
+
- lib/cielli/utils.rb
|
29
|
+
- samples/a.rb
|
30
|
+
homepage: https://github.com/ahoward/cielli
|
31
|
+
licenses:
|
32
|
+
- Ruby
|
33
|
+
metadata: {}
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
requirements: []
|
49
|
+
rubygems_version: 3.0.3
|
50
|
+
signing_key:
|
51
|
+
specification_version: 4
|
52
|
+
summary: cielli
|
53
|
+
test_files: []
|