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 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: