exctl 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/exctl.gemspec +6 -2
- data/lib/exctl/cli.rb +35 -13
- data/lib/exctl/dispatch.rb +5 -0
- data/lib/exctl.rb +12 -4
- data/lib/shorthand.rb +438 -0
- data/notes.md +37 -0
- data/templates/default-dot-commands.erb +1 -0
- data/templates/dispatch-wrapper.erb +14 -0
- metadata +7 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3
|
data/exctl.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "exctl"
|
8
|
-
s.version = "0.0.
|
8
|
+
s.version = "0.0.3"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Joseph Wecker"]
|
12
|
-
s.date = "2013-11-
|
12
|
+
s.date = "2013-11-13"
|
13
13
|
s.description = "Allows you to create a command-line dispatcher for a project to consolidate dev, release, and runtime workflows."
|
14
14
|
s.email = "joseph.wecker@gmail.com"
|
15
15
|
s.executables = ["exctl"]
|
@@ -31,7 +31,11 @@ Gem::Specification.new do |s|
|
|
31
31
|
"exctl.gemspec",
|
32
32
|
"lib/exctl.rb",
|
33
33
|
"lib/exctl/cli.rb",
|
34
|
+
"lib/exctl/dispatch.rb",
|
35
|
+
"lib/shorthand.rb",
|
34
36
|
"notes.md",
|
37
|
+
"templates/default-dot-commands.erb",
|
38
|
+
"templates/dispatch-wrapper.erb",
|
35
39
|
"test/helper.rb",
|
36
40
|
"test/test_exctl.rb"
|
37
41
|
]
|
data/lib/exctl/cli.rb
CHANGED
@@ -1,8 +1,4 @@
|
|
1
|
-
# TODO: Yes, at some point this will be bootstrapped by exctl itself.
|
2
|
-
|
3
1
|
module Exctl
|
4
|
-
def self.cli(argv) Exctl::CLI.new(argv).run! end
|
5
|
-
|
6
2
|
class CLI
|
7
3
|
def initialize(argv)
|
8
4
|
@args = argv.clone
|
@@ -22,9 +18,8 @@ module Exctl
|
|
22
18
|
end
|
23
19
|
end
|
24
20
|
|
25
|
-
def avail_cmds()
|
26
|
-
|
27
|
-
end
|
21
|
+
def avail_cmds; @avail_cmds ||= (self.methods-Object.methods).map(&:to_s).select{|m|m[0..3]=='cmd_'}.map{|m|m[4..-1]} end
|
22
|
+
def cmd_version; puts Exctl.version end
|
28
23
|
|
29
24
|
def doc_help; 'Output this help.' end
|
30
25
|
def cmd_help
|
@@ -34,15 +29,42 @@ module Exctl
|
|
34
29
|
puts avail_cmds.map{|c| hc='doc_'+c; respond_to?(hc) ? ' ' + c.ljust(10) + ': ' + send(hc) : nil}.compact.join("\n") + "\n\n"
|
35
30
|
end
|
36
31
|
|
37
|
-
def cmd_version
|
38
|
-
puts Exctl.version
|
39
|
-
end
|
40
|
-
|
41
32
|
def doc_init; 'BIN-NAME [DIR=.] - Creates/updates main ctl script and support files in the given DIR.' end
|
42
33
|
def cmd_init
|
34
|
+
require 'erb'
|
35
|
+
binname = @args.shift
|
36
|
+
dir = @args.shift || '.'
|
37
|
+
if binname.nil?
|
38
|
+
$stderr.puts "ERROR: project/binary name not specified."
|
39
|
+
$stderr.puts "USAGE: exctl init BIN-NAME [DIR=.]"
|
40
|
+
exit 3
|
41
|
+
end
|
42
|
+
|
43
|
+
b = binding
|
44
|
+
|
45
|
+
libdest = Path["./.exctl"]
|
46
|
+
$stderr.puts " create dir: #{libdest}/"
|
47
|
+
libdest.dir!
|
48
|
+
|
49
|
+
LIB_DIR['**/**',false].each do |f|
|
50
|
+
relative = f.short(LIB_DIR)
|
51
|
+
unless relative.to_s[0..0]=='.' || relative.to_s == 'exctl/cli.rb'
|
52
|
+
dest = libdest ** relative
|
53
|
+
if f.dir?
|
54
|
+
$stderr.puts " create dir: #{dest}/"
|
55
|
+
dest.dir!
|
56
|
+
else
|
57
|
+
$stderr.puts " create file: #{dest}"
|
58
|
+
system "cp", f, dest
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
43
62
|
|
44
|
-
$stderr.puts "
|
45
|
-
|
63
|
+
$stderr.puts " create file: #{binname}"
|
64
|
+
dispatch_wrapper = ERB.new(File.read(TEMPLATE_DIR ** 'dispatch-wrapper.erb')).result(b)
|
65
|
+
File.write(binname, dispatch_wrapper)
|
66
|
+
$stderr.puts " chmod file: #{binname}"
|
67
|
+
File.chmod(0774, binname)
|
46
68
|
end
|
47
69
|
end
|
48
70
|
end
|
data/lib/exctl.rb
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
module Exctl
|
2
|
-
|
3
|
-
def self.version() Exctl::VERSION end
|
4
|
-
end
|
2
|
+
require 'shorthand'
|
5
3
|
|
6
|
-
|
4
|
+
LIB_DIR = Path[File.dirname(__FILE__)]
|
5
|
+
TEMPLATE_DIR = (LIB_DIR ** '..') ** 'templates'
|
7
6
|
|
7
|
+
VFILE = (LIB_DIR ** '..') ** 'VERSION'
|
8
|
+
VERSION = VFILE.exists? ? VFILE.read : ''
|
9
|
+
def self.version() Exctl::VERSION end
|
10
|
+
|
11
|
+
def self.cli(argv)
|
12
|
+
require 'exctl/cli'
|
13
|
+
Exctl::CLI.new(argv).run!
|
14
|
+
end
|
15
|
+
end
|
data/lib/shorthand.rb
ADDED
@@ -0,0 +1,438 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'pathname'
|
3
|
+
require 'etc'
|
4
|
+
require 'set'
|
5
|
+
require 'pp'
|
6
|
+
require 'digest'
|
7
|
+
require 'base64'
|
8
|
+
|
9
|
+
class Nilish
|
10
|
+
def self.global()$__N||= self.new end
|
11
|
+
def inspect() "nil(ish)" end
|
12
|
+
def to_str() '' end
|
13
|
+
def blank?() true end
|
14
|
+
def nil?() true end
|
15
|
+
def empty?() true end
|
16
|
+
def wrap(*args) '' end
|
17
|
+
def method_missing(m,*a,&b)
|
18
|
+
case
|
19
|
+
when nil.respond_to?(m) then nil.send(m,*a,&b)
|
20
|
+
when false.respond_to?(m) then false.send(m,*a,&b)
|
21
|
+
when m.to_s[-1..-1]=='?' then nil
|
22
|
+
else self end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Object
|
27
|
+
def blank?() respond_to?(:empty?) ? empty? : !self end
|
28
|
+
def maybe() self.nil? ? Nilish.global : self end
|
29
|
+
def present?() !blank? end
|
30
|
+
def presence() self if present? end
|
31
|
+
def to_sig() Digest::SHA1.digest(self.to_s).base64url end
|
32
|
+
def numeric?() respond_to?(:to_f) end
|
33
|
+
end
|
34
|
+
|
35
|
+
class NilClass
|
36
|
+
def blank?() true end
|
37
|
+
def wrap(*args) '' end
|
38
|
+
def s(*args) '' end
|
39
|
+
def sign() nil end
|
40
|
+
end
|
41
|
+
class FalseClass; def blank?() true end end
|
42
|
+
class TrueClass; def blank?() false end end
|
43
|
+
class Numeric; def blank?() false end end
|
44
|
+
class Hash; alias_method :blank?,:empty? end
|
45
|
+
class Array; alias_method :blank?,:empty? end
|
46
|
+
class Set; alias_method :blank?,:empty? end
|
47
|
+
|
48
|
+
module Enumerable
|
49
|
+
def amap(m,*a,&b) self.map {|i|i.send(m,*a,&b)} end
|
50
|
+
def amap!(m,*a,&b)self.map!{|i|i.send(m,*a,&b)} end
|
51
|
+
def sum() self.inject(0){|acc,i|acc + i} end
|
52
|
+
def average() self.sum/self.length.to_f end
|
53
|
+
def median() self.dup.to_a.sort[self.size/2] end
|
54
|
+
def medianr(r) d = self.dup.to_a.sort; d[self.size/2-r..self.size/2+r].average end
|
55
|
+
def median_ad() m = self.median; self.map{|v| (v.to_f - m.to_f).abs}.average end
|
56
|
+
def median_mad() m = self.median; self.map{|v| (v.to_f - m.to_f).abs}.median end
|
57
|
+
def median_madr(r) m = self.median; self.map{|v| (v.to_f - m.to_f).abs}.medianr(r) end
|
58
|
+
def mean_ad() m = self.average; self.map{|v| (v.to_f - m.to_f).abs}.average end
|
59
|
+
def mid() srt=self.sort; self.size % 2 == 0 ? (srt[self.size/2] + srt[self.size/2-1]).to_f/2.0 : srt[self.size/2] end
|
60
|
+
def robust_ad() r = self.robust_avg; self.map{|v| (v.to_f - r).abs}.average end
|
61
|
+
def robust_ad2() r = self.robust_avgm; self.map{|v| (v.to_f - r).abs}.average end
|
62
|
+
def q20()
|
63
|
+
fifth = self.size.to_f / 5.0
|
64
|
+
return self[0] if fifth < 1.0
|
65
|
+
i1,i2 = [fifth.floor-1, fifth.ceil-1]
|
66
|
+
srt = self.sort
|
67
|
+
return srt[i1].to_f if i1 == i2
|
68
|
+
return srt[i1].to_f * (fifth.ceil - fifth) + srt[i2] * (fifth - fifth.floor)
|
69
|
+
end
|
70
|
+
def q80()
|
71
|
+
fifth = self.size.to_f / 5.0
|
72
|
+
return self.last if fifth < 1.0
|
73
|
+
i1,i2 = [self.size - fifth.floor, self.size - fifth.ceil]
|
74
|
+
srt = self.sort
|
75
|
+
return srt[i1].to_f if i1 == i2
|
76
|
+
return srt[i1].to_f * (fifth.ceil - fifth) + srt[i2] * (fifth - fifth.floor)
|
77
|
+
end
|
78
|
+
def robust_avg() (self.q20 + self.mid + self.q80) / 3.0 end
|
79
|
+
def robust_avgm() (self.q20 + self.mid + self.mid + self.q80) / 4.0 end
|
80
|
+
def sample_variance
|
81
|
+
avg=self.average
|
82
|
+
sum=self.inject(0){|acc,i|acc + (i-avg)**2}
|
83
|
+
sum.to_f/self.length.to_f
|
84
|
+
end
|
85
|
+
def standard_deviation() Math.sqrt(self.sample_variance) end
|
86
|
+
def summarize_runs
|
87
|
+
return [] if size == 0
|
88
|
+
self.slice_before([self[0]]){|e,c| e==c[0] ? false : (c[0]=e; true)}.map do |chk|
|
89
|
+
[chk.size, chk[0]]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
alias_method :stddev, :standard_deviation
|
93
|
+
alias_method :avg, :average
|
94
|
+
alias_method :mean, :average
|
95
|
+
alias_method :var, :sample_variance
|
96
|
+
end
|
97
|
+
|
98
|
+
class Set; alias_method :[],:member? end
|
99
|
+
|
100
|
+
unless defined?(Path)
|
101
|
+
Path = Pathname
|
102
|
+
class Pathname
|
103
|
+
def self.[](p) Path.new(p) end
|
104
|
+
alias old_init initialize
|
105
|
+
def initialize(*args) old_init(*args); @rc={}; @rc2={} end
|
106
|
+
alias_method :exists?,:exist?
|
107
|
+
def to_p() self end
|
108
|
+
def ** (p) self+p.to_p end
|
109
|
+
def r? () readable_real? end
|
110
|
+
def w? () writable_real? end
|
111
|
+
def x? () executable_real? end
|
112
|
+
def rw? () r? && w? end
|
113
|
+
def rwx?() r? && w? && x? end
|
114
|
+
def dir?() directory? end
|
115
|
+
def ===(p) real == p.real end
|
116
|
+
def perm?() exp.dir? ? rwx? : rw? end
|
117
|
+
def exp () return @exp ||= self.expand_path end
|
118
|
+
def real() begin exp.realpath rescue exp end end
|
119
|
+
def dir() (exp.dir? || to_s[-1].chr == '/') ? exp : exp.dirname end
|
120
|
+
def dir!() (exp.mkpath unless exp.dir? rescue return nil); self end
|
121
|
+
def [](p,dots=true) Path.glob((dir + p.to_s).to_s, dots ? File::FNM_DOTMATCH : 0) end
|
122
|
+
def older_than?(p) self.stat.mtime < p.stat.mtime end
|
123
|
+
def missing?() !self.exist? end
|
124
|
+
def as_other(new_dir, new_ext=nil)
|
125
|
+
p = new_dir.nil? ? self : (new_dir.to_p + self.basename)
|
126
|
+
p = Path[p.to_s.sub(/#{self.extname}$/,'.'+new_ext)] if new_ext
|
127
|
+
return p
|
128
|
+
end
|
129
|
+
def rel(p=nil,home=true)
|
130
|
+
p ||= ($pwd || Path.pwd)
|
131
|
+
return @rc2[p] if @rc2[p]
|
132
|
+
r = abs.rel_path_from(p.abs)
|
133
|
+
r = r.sub(ENV['HOME'],'~') if home
|
134
|
+
r
|
135
|
+
end
|
136
|
+
def contents; IO.read(self) end
|
137
|
+
def different_contents?(str) IO.read(self).strip != str.strip end
|
138
|
+
def short(p=nil,home=true)
|
139
|
+
p ||= ($pwd || Path.pwd)
|
140
|
+
return @rc2[p.to_s] if @rc2[p.to_s]
|
141
|
+
sr = real; pr = p.real
|
142
|
+
se = exp; pe = p.exp
|
143
|
+
candidates = [sr.rel_path_from(pr), sr.rel_path_from(pe),
|
144
|
+
se.rel_path_from(pr), se.rel_path_from(pe)]
|
145
|
+
candidates += [sr.sub(ENV['HOME'],'~'), se.sub(ENV['HOME'],'~')] if home
|
146
|
+
@rc2[p.to_s] = candidates.sort_by{|v|v.to_s.size}[0]
|
147
|
+
end
|
148
|
+
def rel_path_from(p) @rc ||= {}; @rc[p.to_s] ||= relative_path_from(p) end
|
149
|
+
def relation_to(p)
|
150
|
+
travp = p.rel(self,false).to_s
|
151
|
+
if travp =~ /^(..\/)+..(\/|$)/ then :child
|
152
|
+
else travp =~ /^..\// ? :stranger : :parent end
|
153
|
+
end
|
154
|
+
def dist_from(p)
|
155
|
+
return 0 if self === p
|
156
|
+
travp = p.dir.rel(self.dir,false).to_s
|
157
|
+
return 1 if travp =~ /^\/?\.\/?$/
|
158
|
+
return travp.split('/').size + 1
|
159
|
+
end
|
160
|
+
alias old_mm method_missing
|
161
|
+
def method_missing(m,*a,&b) to_s.respond_to?(m) ? to_s.send(m,*a,&b) : old_mm(m,*a,&b) end
|
162
|
+
def abs(wd=nil)
|
163
|
+
wd ||= ($pwd || Path.pwd); wd = wd.to_s
|
164
|
+
s = self.to_s
|
165
|
+
raise ArgumentError.new('Bad working directory- must be absolute') if wd[0].chr != '/'
|
166
|
+
if s.blank? ; return nil
|
167
|
+
elsif s[0].chr=='/' ; return s
|
168
|
+
elsif s[0].chr=='~' && (s[1].nil?||s[1].chr=='/'); _abs_i(s[1..-1], ENV['HOME'])
|
169
|
+
elsif s =~ /~([^\/]+)/; _abs_i($', Etc.getpwnam($1).dir)
|
170
|
+
else _abs_i(s, wd) end
|
171
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
def _abs_i(p,wd)
|
175
|
+
str = wd + '/' + p ; last = str[-1].chr
|
176
|
+
combo = []
|
177
|
+
str.split('/').each do |part|
|
178
|
+
case part
|
179
|
+
when part.blank?, '.' then next
|
180
|
+
when '..' then combo.pop
|
181
|
+
else combo << part end
|
182
|
+
end
|
183
|
+
Path.new('/' + combo.join('/') + (last == '/' ? '/' : ''))
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
class String
|
188
|
+
def to_p() Path.new(self) end
|
189
|
+
alias old_mm method_missing
|
190
|
+
#def method_missing(m,*a,&b) to_p.respond_to?(m) ? to_p.send(m,*a,&b) : old_mm(m,*a,&b) end
|
191
|
+
#def respond_to_missing?(m,p=false) to_p.respond_to?(m,p) end
|
192
|
+
def same_path(p) to_p === p end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
|
197
|
+
class String
|
198
|
+
alias_method :l, :ljust
|
199
|
+
alias_method :r, :rjust
|
200
|
+
def eval() Kernel.eval(self) end
|
201
|
+
def base64url() Base64::encode64(self).tr("\n\s\r=",'').tr('+/','-_') end
|
202
|
+
def fnv32() bytes.reduce(0x811c9dc5) {|h,b|((h^b)*0x01000193) % (1<<32)} end
|
203
|
+
def fnv64() bytes.reduce(0xcbf29ce484222325){|h,b|((h^b)*0x100000001b3) % (1<<64)} end
|
204
|
+
def blank?() self !~ /[^[:space:]]/ end
|
205
|
+
def to_sh() blank? ? '' : gsub(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1").gsub("\n","'\n'") end
|
206
|
+
|
207
|
+
def wrap(indent=:first, width=90)
|
208
|
+
width ||= 90
|
209
|
+
ind = case indent
|
210
|
+
when :first then self[/^[[:space:]]*/u]
|
211
|
+
when String then indent
|
212
|
+
when Integer then ' ' * indent.abs
|
213
|
+
else ' ' * indent.to_i.abs end
|
214
|
+
ind_size = ind.count("\t")*8 + ind.count("^\t")
|
215
|
+
width = width.to_i.abs - ind_size; width = 1 if width < 1
|
216
|
+
paras = dup.strip.split(/\n[ \t]*\n[[:space:]]*/mu)
|
217
|
+
paras = paras.map{|p| p.strip.gsub(/[[:space:]]+/mu,' ')}
|
218
|
+
paras = paras.map{|p| p.scan(/.{1,#{width}}(?: |$)/u).map{|row| ind + row.strip}.join("\n")}
|
219
|
+
paras.join("\n\n")
|
220
|
+
end
|
221
|
+
def sentences() gsub(/\s+/,' ').scan(/([^.!?]+([.!?](\s+|$)|$))/).map{|s|s[0].strip}.reject{|s|s.nil? || s.strip==''} end
|
222
|
+
def make_esc(esc_interp=true)
|
223
|
+
if esc_interp
|
224
|
+
gsub(/\$([A-Za-z0-9_-]+)/){|m| "${"+$1.gsub(/\W/,'_')+"}"}.gsub("\n","\\\n")
|
225
|
+
else
|
226
|
+
gsub('$','$$').gsub("\n","\\\n")
|
227
|
+
end
|
228
|
+
end
|
229
|
+
def numeric?() true if Float(self) rescue false end
|
230
|
+
alias_method :ssize, :size
|
231
|
+
def last(n=1) self.size < n ? self : self[-n..-1] end
|
232
|
+
end
|
233
|
+
|
234
|
+
class Numeric
|
235
|
+
def sign(neg=-1,zer=0,pos=1) self == 0 ? zer : (self < 0 ? neg : pos) end
|
236
|
+
def sign2(neg=-1,pos=1) self < 0 ? neg : pos end
|
237
|
+
def ssize() self.to_s.size end
|
238
|
+
end
|
239
|
+
|
240
|
+
class Integer
|
241
|
+
def fact() f=1; (1..self).each{|i| f *= i}; f end
|
242
|
+
end
|
243
|
+
|
244
|
+
class Fixnum
|
245
|
+
def numeric?() true end
|
246
|
+
def s(p=0,ch='0')
|
247
|
+
return ch if self == 0 && !ch.nil?
|
248
|
+
p > 0 ? ("%.#{p}f" % self) : self.to_s
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
class Float
|
253
|
+
def numeric?() true end
|
254
|
+
def s(p=2,ch='0')
|
255
|
+
return ch if self == 0.0 && p == 0 && !ch.nil?
|
256
|
+
v = self.round
|
257
|
+
v = (self > 0.0 ? self.ceil : self.floor) if v == 0 && !ch.nil?
|
258
|
+
ret = p > 0 ? ("%.#{p}f" % self) : v.to_s
|
259
|
+
(ret =~ /^0+(\.0+)?$/ && !ch.nil?) ? ch : ret
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
class Array
|
264
|
+
def wrap(indent=:first, width=nil) join(' ').wrap(indent, width) end
|
265
|
+
def sentences() join(' ').sentences end
|
266
|
+
def compacted() map{|v| v.blank? ? nil : v}.compact end
|
267
|
+
alias_method :stable_compact, :compacted
|
268
|
+
def avg0() _i = map{|v| v.numeric? ? v.to_f : nil}.compact; _i.size > 0 ? (_i = _i.inject(:+) / size.to_f) : 0.0 end
|
269
|
+
def hmap(&block) OrderedHash[*(self.each_with_index.map{|v,k| yield(k,v)}.compact.flatten)] end
|
270
|
+
end
|
271
|
+
|
272
|
+
class Proj
|
273
|
+
def initialize(cwd=nil, cf=nil)
|
274
|
+
@cached_lookups = {}
|
275
|
+
@p_cf = nil
|
276
|
+
@p_cwd = (cwd || Path.pwd).exp
|
277
|
+
@p_cf = cf.exp unless cf.blank?
|
278
|
+
end
|
279
|
+
|
280
|
+
def root_dir
|
281
|
+
wd_root = root_dir_for(@p_cwd)
|
282
|
+
return wd_root if @p_cf.blank?
|
283
|
+
cf_root = root_dir_for(@p_cf)
|
284
|
+
return @p_cwd if wd_root.blank? && cf_root.blank?
|
285
|
+
return cf_root if wd_root.blank?
|
286
|
+
return wd_root if cf_root.blank?
|
287
|
+
return wd_root if wd_root === cf_root
|
288
|
+
return wd_root if wd_root.relation_to(cf_root) == :parent
|
289
|
+
return cf_root
|
290
|
+
end
|
291
|
+
|
292
|
+
def [](pattern,refresh=false)
|
293
|
+
@cached_lookups.delete(pattern) if refresh
|
294
|
+
return @cached_lookups[pattern] ||= root_dir[pattern]
|
295
|
+
end
|
296
|
+
|
297
|
+
private
|
298
|
+
def root_dir_for(path)
|
299
|
+
in_cvs = in_svn = in_rcs = false
|
300
|
+
tentative = path.dir
|
301
|
+
tentative.ascend do |d|
|
302
|
+
has_cvs = has_svn = has_rcs = false
|
303
|
+
d['{.hg,.svn,CVS,RCS,[MR]akefile,configure,LICENSE}'].each do |c|
|
304
|
+
case c.basename.to_s
|
305
|
+
when '.hg'||'.git' then return d
|
306
|
+
when '.svn' then in_svn = d; has_svn = true
|
307
|
+
when 'CVS' then in_cvs = d; has_cvs = true
|
308
|
+
when 'RCS' then in_rcs = d; has_rcs = true
|
309
|
+
when /[MR]akefile.*/ then tentative = d
|
310
|
+
when 'configure'||'LICENSE' then tentative = d
|
311
|
+
end
|
312
|
+
end
|
313
|
+
return in_svn if in_svn && !has_svn
|
314
|
+
return in_cvs if in_cvs && !has_cvs
|
315
|
+
return in_rcs if in_rcs && !has_rcs
|
316
|
+
end
|
317
|
+
return tentative
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
module ForHashes
|
322
|
+
def stable_compact()
|
323
|
+
h = self.dup
|
324
|
+
h = h.reject{|k,v| v.nil?}
|
325
|
+
dat = h.map do |k,v|
|
326
|
+
v = v.stable_compact if v.respond_to?(:stable_compact)
|
327
|
+
[k.to_s, v]
|
328
|
+
end
|
329
|
+
dat.sort
|
330
|
+
end
|
331
|
+
def to_sig() stable_compact.inspect.to_sig end
|
332
|
+
def map_vals!(&block) each{|k,v| self[k] = yield(k,v)}; self end
|
333
|
+
def map_vals(&block) dup.map_vals!(&block) end
|
334
|
+
def hmap(&block) OrderedHash[*(self.map{|k,v| yield(k,v)}.compact.flatten)] end
|
335
|
+
def clean() map_vals{|k,v| v.respond_to?(:clean) ? v.clean : (v.respond_to?(:to_str) ? v.to_str : v.to_s)} end
|
336
|
+
end
|
337
|
+
|
338
|
+
|
339
|
+
if RUBY_VERSION >= '1.9'
|
340
|
+
OrderedHash = ::Hash
|
341
|
+
else
|
342
|
+
class OrderedHash < Hash
|
343
|
+
def self.[](*args)
|
344
|
+
ordered_hash = new
|
345
|
+
args.each_with_index { |val,ind|
|
346
|
+
# Only every second value is a key.
|
347
|
+
next if ind % 2 != 0
|
348
|
+
ordered_hash[val] = args[ind + 1]
|
349
|
+
}
|
350
|
+
ordered_hash
|
351
|
+
end
|
352
|
+
alias_method :blank?,:empty?
|
353
|
+
def initialize(*args, &block) super; @keys = [] end
|
354
|
+
def initialize_copy(other) super; @keys = other.keys end
|
355
|
+
def []=(key, value) @keys << key if !has_key?(key); super end
|
356
|
+
def delete(key)
|
357
|
+
$stderr.puts "Deleting key #{key}"
|
358
|
+
if has_key? key
|
359
|
+
index = @keys.index(key)
|
360
|
+
@keys.delete_at index
|
361
|
+
end
|
362
|
+
super
|
363
|
+
end
|
364
|
+
def delete_if() super; sync_keys!; self end
|
365
|
+
def reject!; super; sync_keys!; self end
|
366
|
+
def reject(&block) dup.reject!(&block) end
|
367
|
+
def keys; @keys.dup end
|
368
|
+
def values; @keys.collect { |key| self[key] } end
|
369
|
+
def to_hash; self end
|
370
|
+
def to_a; @keys.map { |key| [ key, self[key] ] } end
|
371
|
+
def each_key; @keys.each { |key| yield key } end
|
372
|
+
def each_value; @keys.each { |key| yield self[key]} end
|
373
|
+
def each; @keys.each {|key| yield [key, self[key]]} end
|
374
|
+
alias_method :each_pair, :each
|
375
|
+
def clear; super; @keys.clear; self end
|
376
|
+
def shift; k = @keys.first; v = delete(k) [k, v] end
|
377
|
+
def merge!(other_hash) other_hash.each {|k,v| self[k] = v }; self end
|
378
|
+
def merge(other_hash) dup.merge!(other_hash) end
|
379
|
+
def inspect; "#<OrderedHash #{super}>" end
|
380
|
+
private
|
381
|
+
def sync_keys!() @keys.delete_if {|k| !has_key?(k)} end
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
class Hash; include ForHashes end
|
386
|
+
if RUBY_VERSION < '1.9'
|
387
|
+
class OrderedHash; include ForHashes end
|
388
|
+
end
|
389
|
+
|
390
|
+
class OStruct < OpenStruct
|
391
|
+
def initialize(hash=nil)
|
392
|
+
@table = OrderedHash.new
|
393
|
+
if hash
|
394
|
+
for k,v in hash
|
395
|
+
@table[k.to_sym] = v
|
396
|
+
new_ostruct_member(k)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
def to_h() self.marshal_dump end
|
402
|
+
def pp() marshal_dump.map{|k,v| "#{k.to_s.ljust(24)} = #{String===v ? v : v.pretty_inspect.strip}"}.join("\n") end
|
403
|
+
def |(other_ostruct) OStruct.new(marshal_dump.merge(other_ostruct.marshal_dump.reject{|k,v|v.nil?})) end
|
404
|
+
def to_sig() marshal_dump.to_sig end
|
405
|
+
end
|
406
|
+
|
407
|
+
class UnixPerm
|
408
|
+
def initialize(v) @v = v.numeric? ? (String===v ? (v.strip[0..0]=='0' ? eval(v) : eval('0'+v)) : v) : UnixPerm.desc_to_oct(v) end
|
409
|
+
def to_s() as_oct_str end
|
410
|
+
def inspect() as_desc_str end
|
411
|
+
def as_oct_str() '0' + @v.to_s(8) end
|
412
|
+
def as_desc_str(prefix='') UnixPerm.oct_to_desc(@v,prefix) end
|
413
|
+
|
414
|
+
def self.desc_to_oct(v) raise "not yet implemented. haven't needed it yet." end
|
415
|
+
def self.oct_to_desc(v, part_prefix='')
|
416
|
+
s = ('0'*(3*4) + v.to_s(2))[-12..-1].each_char.map{|c| c == '1'}
|
417
|
+
u = []; g = []; o = []; a = []
|
418
|
+
u<<'s' if s[0]; g<<'s' if s[1]; a<<'t' if s[2]
|
419
|
+
u<<'r' if s[3]; u<<'w' if s[4]; u<<'x' if s[5]
|
420
|
+
g<<'r' if s[6]; g<<'w' if s[7]; g<<'x' if s[8]
|
421
|
+
o<<'r' if s[9]; o<<'w' if s[10]; o<<'x' if s[11]
|
422
|
+
"#{part_prefix}a=#{a.join},#{part_prefix}u=#{u.join},#{part_prefix}g=#{g.join},#{part_prefix}o=#{o.join}"
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
class MatchData
|
427
|
+
def to_hash()
|
428
|
+
names = [] unless methods.include?(:names)
|
429
|
+
nm = names || []
|
430
|
+
nm.size > 0 ? OrderedHash[*(nm.amap(:to_sym).zip(captures) + [[0,self[0]]]).flatten] :
|
431
|
+
OrderedHash[*(0..size-1).to_a.zip(to_a).flatten]
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
class Time
|
436
|
+
def nmsec() Time.mktime(self.month == 12 ? self.year + 1 : self.year, ((self.month % 12) + 1), 1) - self end
|
437
|
+
def parts() [self.year, self.month, self.day, self.hour, self.min, self.sec, self.nmsec.round, self.wday, self.yday] end
|
438
|
+
end
|
data/notes.md
CHANGED
@@ -26,6 +26,43 @@ meta-exctl
|
|
26
26
|
- command for dumping information about the project - including known tasks etc.
|
27
27
|
|
28
28
|
|
29
|
+
commands
|
30
|
+
--------
|
31
|
+
|
32
|
+
- command
|
33
|
+
- description
|
34
|
+
- synopsis
|
35
|
+
- arguments
|
36
|
+
- name
|
37
|
+
- flag
|
38
|
+
- description
|
39
|
+
- value *
|
40
|
+
- default *
|
41
|
+
- validation-format *
|
42
|
+
- transform-format *
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
files
|
47
|
+
-----
|
48
|
+
|
49
|
+
PROJ-ROOT/<BIN-NAME> (required)
|
50
|
+
/.exctl/* (required)
|
51
|
+
/.commands (not required, but default one on init)
|
52
|
+
/scripts/<CMDS>/* (ones with manifests after hashbang are included)
|
53
|
+
|
54
|
+
|
55
|
+
eventually more seamless
|
56
|
+
------------------------
|
57
|
+
|
58
|
+
- Rakefile
|
59
|
+
- Gemfile
|
60
|
+
- Makefile
|
61
|
+
- rebar.config
|
62
|
+
- .rvmrc
|
63
|
+
- ...
|
64
|
+
|
65
|
+
|
29
66
|
aggregate features
|
30
67
|
------------------
|
31
68
|
|
@@ -0,0 +1 @@
|
|
1
|
+
#
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# If you move the dispatch executable relative to the project root, change this. For example, if you move it into the
|
4
|
+
# bin/ directory you'd want this to say '..'
|
5
|
+
#
|
6
|
+
RELATIVE_PROJ_ROOT_DIR = '.'
|
7
|
+
|
8
|
+
require 'pathname'
|
9
|
+
this_dir = Pathname.new(File.expand_path(File.dirname(__FILE__))).realpath
|
10
|
+
$PROJ_ROOT = File.expand_path(File.join(this_dir, RELATIVE_PROJ_ROOT_DIR))
|
11
|
+
$LOAD_PATH.unshift File.join(File.expand_path(File.dirname(__FILE__)), ".exctl")
|
12
|
+
require 'exctl'
|
13
|
+
require 'exctl/dispatch'
|
14
|
+
Exctl::Dispatch::dispatch(ARGV)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: exctl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-11-
|
12
|
+
date: 2013-11-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: shoulda
|
@@ -98,7 +98,11 @@ files:
|
|
98
98
|
- exctl.gemspec
|
99
99
|
- lib/exctl.rb
|
100
100
|
- lib/exctl/cli.rb
|
101
|
+
- lib/exctl/dispatch.rb
|
102
|
+
- lib/shorthand.rb
|
101
103
|
- notes.md
|
104
|
+
- templates/default-dot-commands.erb
|
105
|
+
- templates/dispatch-wrapper.erb
|
102
106
|
- test/helper.rb
|
103
107
|
- test/test_exctl.rb
|
104
108
|
homepage: http://github.com/josephwecker/exctl
|
@@ -116,7 +120,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
116
120
|
version: '0'
|
117
121
|
segments:
|
118
122
|
- 0
|
119
|
-
hash: -
|
123
|
+
hash: -1281049067078405370
|
120
124
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
121
125
|
none: false
|
122
126
|
requirements:
|