optparse-lite 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,322 @@
1
+ module Hipe; end
2
+ module Hipe::GenTest
3
+ extend self # cheap way to get module_function s from everything
4
+
5
+ def gentest argv
6
+ argv = argv.dup
7
+ @both = false
8
+ process_opts(argv) if /^-/ =~ argv.first
9
+
10
+ @service_controller = deduce_services_controller
11
+ @ui = Hipe::IndentingStream.new($stdout,'')
12
+ file = argv.shift
13
+ mod = deduce_module_from_file file
14
+ mod.spec.invocation_name = File.basename(file)
15
+ get_actuals mod, argv
16
+ @ui.indent!.indent!
17
+ go_app(mod, file)
18
+ go_desc(mod, file) do
19
+ go_exp
20
+ go_act(mod, argv)
21
+ end
22
+ exit(0) # rake is annoying
23
+ end
24
+
25
+ private
26
+
27
+ def assert_filename mod, file
28
+ name = no_ext(file)
29
+ shorter = name.split('-')[0..-2].join('-')
30
+ chemaux = camelize(shorter)
31
+ unless chemaux == mod.to_s
32
+ fail("expecting the module (\"#{mod}\") defined in "<<
33
+ "\"#{File.basename(file)}\" with extension-free basename "<<
34
+ "\"#{shorter}\" "<<
35
+ "to define an app called #{chemaux}, not #{mod}, so please "<<
36
+ "name the file \"#{uncamelize(mod.to_s)}-app.rb\" or change the "<<
37
+ "name of the module from \"#{mod}\" to \"#{chemaux}\""
38
+ )
39
+ end
40
+ end
41
+
42
+ def camelize str
43
+ str.gsub(/(?:^|-)(.)/){|x| $1.upcase}
44
+ end
45
+
46
+ def uncamelize str
47
+ str.gsub(/([a-z])([A-Z])/){|x| "#{$1}-#{$2}"}.downcase
48
+ end
49
+
50
+ def deduce_module_from_file file
51
+ before = Object.constants;
52
+ @service_controller.suppress_run!
53
+ require file
54
+ @service_controller.enable_run!
55
+ diff = Object.constants - before
56
+ go_diff diff, file
57
+ end
58
+
59
+ def deduce_services_controller
60
+ # this is a ridiculous attempt to avoid hard-coding the name of
61
+ # the services module for use in disabling service applications.
62
+ # It wants the thing to be in only one file in /lib/, and it deduces
63
+ # the module name from the filename. (note this is for the 'base module'
64
+ # library module that serves this crap, this is not the application service itself.)
65
+ #
66
+ # dir = File.expand_path('../../../..',__FILE__)
67
+ dir = FileUtils.pwd+'/lib'
68
+ it = Dir["#{dir}/*.rb"]
69
+ fail("no '*.rb' files in dir \"#{dir}\"") if it.size == 0
70
+ fail("too many files in #{dir}: "+it.map{|x| File.basename(x)}*',') if
71
+ it.size != 1
72
+ it = it.first
73
+ it = camelize(no_ext(it))
74
+ mod = Object.const_get(it)
75
+ fail("Couldn't find module #{it}") unless mod
76
+ mod
77
+ end
78
+
79
+ def get_actuals mod, argv
80
+ if @both
81
+ @act_out, @act_err = run2(mod){ run argv }
82
+ else
83
+ @act = run(mod){ run argv }
84
+ end
85
+ end
86
+
87
+ def go_desc(mod, file)
88
+ @ui.puts "describe #{mod} do"
89
+ @ui.indent!.puts("it '#{File.basename(file)} must work' do").indent!
90
+ yield
91
+ @ui.dedent!.puts('end').dedent!.puts('end')
92
+ end
93
+
94
+ def go_diff diff, file
95
+ if diff.empty?
96
+ fail("sorry, hack didn't work. No new top-level constants"<<
97
+ "were added by #{file}"
98
+ )
99
+ end
100
+ svc = @service_controller
101
+ these = diff.map do |name|
102
+ const = Object.const_get(name)
103
+ (const.kind_of?(Module) &&
104
+ # for e.g. class Foo; include Trollip, the latter, etc
105
+ (const.kind_of?(svc) || const.ancestors.include?(svc))
106
+ ) ? const : nil
107
+ end.compact
108
+ case these.size;
109
+ when 0:
110
+ fail("Couldn't find any classes or modules that were #{svc} among "<<
111
+ diff.join(' or ')
112
+ )
113
+ when 1; mod = these.first
114
+ else
115
+ fail("#{const.join(' and ')} are #{svc}s in that file. "<<
116
+ "I need only one to generate something."
117
+ )
118
+ end
119
+ assert_filename(mod, file)
120
+ mod
121
+ end
122
+
123
+ def go_act mod, args
124
+ return go_act2(mod, args) if @both
125
+ @ui.puts("act = capture{ run #{args.inspect} }")
126
+ @ui.puts('assert_no_diff(exp, act)')
127
+ end
128
+
129
+ def go_act2 mod, args
130
+ @ui.puts("act_out, act_err = capture2{ run #{args.inspect} }")
131
+ @ui.puts('assert_no_diff(exp_out, act_out)')
132
+ @ui.puts('assert_no_diff(exp_err, act_err)')
133
+ end
134
+
135
+ def go_app mod, file
136
+ @ui.puts File.read(file).split("\n")[2..-3]
137
+ @ui.puts("#{mod}.spec.invocation_name = "<<
138
+ "#{mod.spec.invocation_name.inspect}")
139
+ @ui.puts
140
+ end
141
+
142
+ def go_exp
143
+ return go_exp2 if @both
144
+ act = @act
145
+ @ui.puts('exp = <<-HERE.noindent')
146
+ @ui.indent!
147
+ @ui.puts act.to_s.inspect.gsub('\n',"\n").gsub(/(\A"| *"\Z)/,'')
148
+ @ui.dedent!
149
+ @ui.puts 'HERE'
150
+ end
151
+
152
+ def go_exp2
153
+ act_out, act_err = @act_out, @act_err
154
+ @ui.puts('exp_out = <<-HERE.noindent')
155
+ @ui.indent!
156
+ @ui.puts act_out.to_s.inspect.gsub('\n',"\n").gsub(/(\A"| *"\Z)/,'')
157
+ @ui.dedent!
158
+ @ui.puts 'HERE'
159
+ @ui.puts('exp_err = <<-HERE.noindent')
160
+ @ui.indent!
161
+ @ui.puts act_err.to_s.inspect.gsub('\n',"\n").gsub(/(\A"| *"\Z)/,'')
162
+ @ui.dedent!
163
+ @ui.puts 'HERE'
164
+ end
165
+
166
+ def no_ext str
167
+ File.basename(str).match(/^(.*)\.rb$/)[1]
168
+ end
169
+
170
+ def process_opts argv
171
+ fail("expecting '--out=2', not #{argv.first}") unless
172
+ /^--out=2$/ =~ argv.first
173
+ @both = true
174
+ argv.shift
175
+ nil
176
+ end
177
+
178
+ def run mod, &block
179
+ ui = mod.send(:ui) # it's public on service class, private on mod. singles
180
+ ui.push
181
+ _ = mod.instance_eval(&block)
182
+ ui.pop
183
+ end
184
+
185
+ def run2 mod, &block
186
+ mod.ui.push
187
+ _ = mod.instance_eval(&block)
188
+ mod.ui.pop(true)
189
+ end
190
+ end
191
+
192
+ module Hipe::GenTest
193
+ def ungentest_list test_file
194
+ ungen(test_file).list
195
+ end
196
+ def ungentest test_file, chunk_name
197
+ ungen(test_file).ungen chunk_name
198
+ end
199
+ def ungen test_file
200
+ Ungen.new(test_file)
201
+ end
202
+ class Ungen
203
+ def initialize test_file
204
+ @test_file = test_file
205
+ @ui = $stdout
206
+ end
207
+ def list
208
+ tree = parse_file @test_file
209
+ @ui.puts tree.chunk_names
210
+ end
211
+ def ungen name
212
+ @tree = parse_file @test_file
213
+ name = find_one_loudly name
214
+ return unless name
215
+ lines = @tree.get_code_lines(name)
216
+ lines.map!{|x| x.gsub(/^ /,'') }
217
+ @ui.puts lines
218
+ end
219
+ private
220
+ def find_one_loudly name
221
+ re = /^#{Regexp.escape(name)}/
222
+ items = @tree.chunk_names.grep(re)
223
+ if items.size != items.uniq.size
224
+ fail("can't work with multiple entries of the same name in list: "<<
225
+ items.map{|x| "\"#{x}\""}.join(', ')
226
+ )
227
+ end
228
+ if items.size == 0
229
+ @ui.puts("coudn't find any items matching \"#{name}\" in list. "<<
230
+ "please see -list for list of available items"
231
+ )
232
+ return
233
+ end
234
+ if items.size > 1
235
+ if items.include? name
236
+ return name
237
+ else
238
+ @ui.puts("which one did you mean? "<<items.join(' or '))
239
+ return
240
+ end
241
+ end
242
+ return items.first
243
+ end
244
+ def parse_file test_file
245
+ Indexes.new(test_file)
246
+ end
247
+ class Indexes < Array
248
+ def initialize test_file
249
+ fail("file not found: #{test_file}") unless File.exist?(test_file)
250
+ lines = File.read(test_file).split("\n")
251
+ class << self; self end.send(:define_method, :lines){lines}
252
+ lines.each_with_index do |line, idx|
253
+ case line
254
+ when /^ class ([a-z0-9:]+)/i; push [:class, idx, $1]
255
+ when /^ module ([a-z0-9:]+)/i; push [:module, idx, $1]
256
+ when /^ describe\b/; push [:describe, idx]
257
+ when /^ +it ['"]([a-z0-9]+(?:-[a-z0-9]+)*-app\.rb)/
258
+ if last && last[2] != $1
259
+ push [:it, idx, $1]
260
+ end
261
+ end
262
+ end
263
+ end
264
+ def chunk_names
265
+ select{|x| x.first == :it }.map{|x| x[2]}
266
+ end
267
+ def get_code_lines name
268
+ start_offset, end_offset = get_offsets name
269
+ lines = self.lines[start_offset..end_offset]
270
+ # the below two are the only really optparselite-specific things here
271
+ lines.unshift "require 'optparse-lite' unless defined? OptparseLite"
272
+ lines.unshift "#!/usr/bin/env ruby"
273
+ lines.push " #{@last_mod}.run" if @last_mod
274
+ lines
275
+ end
276
+ def get_offsets name
277
+ idx = index{|x| x.first==:it && x[2]==name}
278
+ inf = self[idx]
279
+ fail("no") unless self[idx-1].first == :describe
280
+ # the last line of the chunk before the describe
281
+ end_offset = self[idx-1][1] - 1
282
+ start_cur = idx - 2
283
+ cur = start_cur
284
+ @last_mod = nil
285
+ while cur > -1 && [:module, :class].include?(self[cur].first)
286
+ @last_mod ||= self[cur][2]
287
+ cur -= 1
288
+ end
289
+ cur += 1
290
+ if cur > start_cur
291
+ fail("classes or modules not found for #{name}")
292
+ end
293
+ start_offset = self[cur][1]
294
+ [start_offset, end_offset]
295
+ end
296
+ end
297
+ end
298
+ end
299
+
300
+ module Hipe
301
+ class IndentingStream
302
+ def initialize io, indent
303
+ @io = io
304
+ @indent = indent
305
+ end
306
+ def indent!
307
+ @indent << ' '
308
+ self
309
+ end
310
+ def dedent!
311
+ @indent.sub!(/ $/,'')
312
+ self
313
+ end
314
+ def puts m=nil
315
+ return @io.puts if m.nil?
316
+ m = m.split("\n") if m.kind_of? String
317
+ m = [m] unless m.kind_of? Array
318
+ @io.puts m.map{|x| "#{@indent}#{x}"}
319
+ self
320
+ end
321
+ end
322
+ end
@@ -0,0 +1,12 @@
1
+ module Hipe
2
+ module GenTest
3
+ module TaskLib
4
+ # quick little hack for colored formatting @todo windows
5
+ # to give rake tasks colored/styles help screens
6
+ #
7
+ private
8
+ def hdr(x); "\e[32;m#{x}\e[0m" end
9
+ def cmd(x); "\e[4;m#{x}\e[0m" end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ if ! Object.const_defined?(:Hipe) || ! ::Hipe.const_defined?(:GenTest)
2
+ require File.expand_path('../gentest.rb', __FILE__)
3
+ end
4
+
5
+ module Hipe::GenTest
6
+ def task_prefix
7
+ "\e[35mgentest\e[0m "
8
+ end
9
+ end
10
+
11
+ here = File.dirname(__FILE__)
12
+
13
+ require here + '/tasks/gentest.rb'
14
+ require here + '/tasks/ungen.rb'
@@ -0,0 +1,44 @@
1
+ require File.expand_path('../../tasklib.rb', __FILE__)
2
+
3
+ module Hipe
4
+ module GenTest
5
+ class GenTestTask
6
+ include Hipe::GenTest::TaskLib
7
+ def initialize name=:gentest, argv=ARGV
8
+ @argv = argv
9
+ @desc = "#{GenTest.task_prefix}try it and see!"
10
+ @name = name
11
+ yield self if block_given?
12
+ define
13
+ end
14
+ attr_accessor :name
15
+ attr_writer :desc
16
+ private
17
+ def define
18
+ desc @desc
19
+ task(@name){ run @argv }
20
+ end
21
+ def run argv
22
+ require File.expand_path('../../gentest.rb',__FILE__)
23
+ fail('huh?') unless argv.shift == @name.to_s
24
+ use_argv = argv.dup
25
+ argv.clear # don't let rake see (?)
26
+ argv = use_argv
27
+ if argv.empty?
28
+ puts <<-HERE.gsub(/^ /,'')
29
+ #{hdr 'Usage:'} #{cmd 'rake gentest'} -- [--out=2] ./your-app.rb --foo='bar' --baz='jazz'
30
+ #{hdr 'Description:'} output to STDOUT a code snippet fer yer test
31
+ #{hdr 'Options:'}
32
+ --out=2 pseudo option. if present it must equal '2', it will
33
+ create a test that captures both stdout and stderr streams,
34
+ turns them to strings, and returns them both for assertion
35
+ against the two recorded strings. Default is to ignore stderr.
36
+ \n\n
37
+ HERE
38
+ exit # why it complains on return i don't get
39
+ end
40
+ GenTest::gentest(use_argv)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,64 @@
1
+ require File.expand_path('../../tasklib.rb', __FILE__)
2
+
3
+ module Hipe
4
+ module GenTest
5
+ class UnGenTask
6
+ include Hipe::GenTest::TaskLib
7
+ def initialize name=:ungen, argv=ARGV
8
+ @argv = argv
9
+ @desc = "#{GenTest.task_prefix}(ungentest) -- try it and see!"
10
+ @name = name
11
+ yield self if block_given?
12
+ define
13
+ end
14
+ attr_writer :desc
15
+ private
16
+ def define
17
+ desc @desc
18
+ task(@name){ run }
19
+ end
20
+ def run
21
+ require File.expand_path('../../gentest.rb', __FILE__)
22
+ fail('huh?') unless @argv.shift == @name.to_s
23
+ use_argv = @argv.dup
24
+ @argv.clear # don't let rake see (?)
25
+ argv = use_argv
26
+ if argv.size > 1
27
+ puts "too many arguments: #{argv.inspect}"
28
+ arg = nil
29
+ elsif argv.empty?
30
+ arg = nil
31
+ elsif /^-(.+)/ =~ argv.first
32
+ if '-list' != argv.first
33
+ puts "unrecognized option: #{argv.first}"
34
+ else
35
+ arg = argv.first
36
+ end
37
+ else
38
+ arg = argv.first
39
+ end
40
+ unless arg
41
+ puts <<-HERE.gsub(/^ /,'')
42
+
43
+ #{hdr 'Usage:'} #{cmd 'rake ungen'} -- <some-app-name.rb>
44
+ #{cmd 'rake ungen'} -- -list
45
+
46
+ #{hdr 'Description:'} Ungentest. write to stdout a chunk of
47
+ code in the test file corresponding to the name.
48
+ (for now the testfile is hard-coded as "test/test.rb")
49
+
50
+ The second form lists available code chunks found in the file.
51
+ \n\n
52
+ HERE
53
+ end
54
+
55
+ if arg=='-list'
56
+ GenTest::ungentest_list 'test/test.rb'
57
+ elsif arg
58
+ GenTest::ungentest 'test/test.rb', arg
59
+ end
60
+ exit(0) # rake is annoying
61
+ end
62
+ end
63
+ end
64
+ end