exctl 0.0.2 → 0.0.3
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.
- 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:
|