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 CHANGED
@@ -1 +1 @@
1
- 0.0.2
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.2"
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"
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
- @avail_cmds ||= (self.methods-Object.methods).map(&:to_s).select{|m|m[0..3]=='cmd_'}.map{|m|m[4..-1]}
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 "just kidding, not implemented yet."
45
- exit 1
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
@@ -0,0 +1,5 @@
1
+ module Exctl::Dispatch
2
+ def self.dispatch(args)
3
+
4
+ end
5
+ end
data/lib/exctl.rb CHANGED
@@ -1,7 +1,15 @@
1
1
  module Exctl
2
- VERSION = File.exist?(File.join(File.dirname(__FILE__),'..','VERSION')) ? File.read(File.join(File.dirname(__FILE__),'..','VERSION')) : ""
3
- def self.version() Exctl::VERSION end
4
- end
2
+ require 'shorthand'
5
3
 
6
- require 'exctl/cli'
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.2
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 00:00:00.000000000 Z
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: -3768507583342469896
123
+ hash: -1281049067078405370
120
124
  required_rubygems_version: !ruby/object:Gem::Requirement
121
125
  none: false
122
126
  requirements: