optparse-lite 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/NEWS.md +4 -0
- data/README +37 -0
- data/Rakefile +57 -0
- data/VERSION +1 -0
- data/doc/high-concept.md +436 -0
- data/doc/high-concept/terms.md +23 -0
- data/doc/installation.md +29 -0
- data/doc/svg/not-funny.svg +24 -0
- data/doc/usage.md +240 -0
- data/lib/optparse-lite.rb +982 -0
- data/lib/optparse-lite/test/gentest/gentest.rb +322 -0
- data/lib/optparse-lite/test/gentest/tasklib.rb +12 -0
- data/lib/optparse-lite/test/gentest/tasks.rb +14 -0
- data/lib/optparse-lite/test/gentest/tasks/gentest.rb +44 -0
- data/lib/optparse-lite/test/gentest/tasks/ungen.rb +64 -0
- data/lib/optparse-lite/test/nandoc-custom-tags.rb +6 -0
- data/lib/optparse-lite/test/nandoc-custom-tags/app.rb +102 -0
- data/lib/optparse-lite/test/nandoc-custom-tags/playback.rb +63 -0
- data/lib/optparse-lite/test/setup.rb +131 -0
- data/lib/optparse-lite/treebis-extlib.rb +5 -0
- data/test/test.rb +963 -0
- metadata +87 -0
@@ -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
|