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