optparse-lite 0.0.0

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.
@@ -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