nandoc 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/README +124 -0
  2. data/Rakefile +53 -0
  3. data/bin/nandoc +6 -0
  4. data/doc/CREDITS.md +6 -0
  5. data/doc/FAQ/why-not-wiki.md +20 -0
  6. data/doc/FAQ.md +68 -0
  7. data/doc/TODOs-and-BUGs.md +15 -0
  8. data/doc/bar/baz.md +4 -0
  9. data/doc/bar/bliff.md +8 -0
  10. data/doc/foo.md +5 -0
  11. data/doc/getting-started.rb +13 -0
  12. data/doc/svg/less-fonts.svg +21 -0
  13. data/lib/nandoc/commands/create-nandoc-site.rb +225 -0
  14. data/lib/nandoc/commands/diff.rb +279 -0
  15. data/lib/nandoc/config.rb +58 -0
  16. data/lib/nandoc/cri-hacks.rb +13 -0
  17. data/lib/nandoc/data-source.rb +239 -0
  18. data/lib/nandoc/filters.rb +661 -0
  19. data/lib/nandoc/helpers/menu-bouncy.rb +109 -0
  20. data/lib/nandoc/helpers/site-map.rb +157 -0
  21. data/lib/nandoc/helpers/top-nav.rb +47 -0
  22. data/lib/nandoc/helpers.rb +42 -0
  23. data/lib/nandoc/item-class-hacks.rb +57 -0
  24. data/lib/nandoc/nandoc.persistent.json +3 -0
  25. data/lib/nandoc/parse-readme.rb +95 -0
  26. data/lib/nandoc/spec-doc/mini-test/spec-instance-methods.rb +0 -0
  27. data/lib/nandoc/spec-doc/mini-test.rb +105 -0
  28. data/lib/nandoc/spec-doc/mock-prompt.rb +121 -0
  29. data/lib/nandoc/spec-doc/support-modules.rb +158 -0
  30. data/lib/nandoc/spec-doc/test-case-agent.rb +57 -0
  31. data/lib/nandoc/spec-doc/test-framework-dispatcher.rb +15 -0
  32. data/lib/nandoc/spec-doc/test-framework-proxy.rb +78 -0
  33. data/lib/nandoc/spec-doc.rb +46 -0
  34. data/lib/nandoc/support/diff-proxy.rb +113 -0
  35. data/lib/nandoc/support/orphanage.rb +77 -0
  36. data/lib/nandoc/support/path-tardo.rb +85 -0
  37. data/lib/nandoc/support/regexp-enhance.rb +76 -0
  38. data/lib/nandoc/support/site-diff.rb +46 -0
  39. data/lib/nandoc/support/site-merge.rb +62 -0
  40. data/lib/nandoc/support/site-methods.rb +69 -0
  41. data/lib/nandoc/support/stream-colorizer.rb +203 -0
  42. data/lib/nandoc/support-modules.rb +270 -0
  43. data/lib/nandoc/test/diff-to-string.rb +251 -0
  44. data/lib/nandoc/test/minitest-extlib.rb +53 -0
  45. data/lib/nandoc/treebis/NOGIT-DOCS/NEWS.md +5 -0
  46. data/lib/nandoc/treebis/NOGIT-README.md +65 -0
  47. data/lib/nandoc/treebis/nandoc.persistent.json +3 -0
  48. data/lib/nandoc.rb +48 -0
  49. data/proto/README.md +31 -0
  50. data/proto/default/Rakefile +1 -0
  51. data/proto/default/Rules +46 -0
  52. data/proto/default/config.yaml +57 -0
  53. data/proto/default/content/css/nanoc-dist-altered.css +213 -0
  54. data/proto/default/content/css/trollop-subset.css +116 -0
  55. data/proto/default/content/js/menu-bouncy.js +126 -0
  56. data/proto/default/content/stylesheet.css.diff +20 -0
  57. data/proto/default/content/vendor/jquery-1.3.js +4241 -0
  58. data/proto/default/content/vendor/jquery.easing.1.3.js +205 -0
  59. data/proto/default/layouts/default.html +70 -0
  60. data/proto/default/lib/default.orig.rb +2 -0
  61. data/proto/default/lib/default.rb +5 -0
  62. data/proto/default/treebis-task.rb +28 -0
  63. data/proto/misc/orphan-surrogate.md +6 -0
  64. data/test/test.rb +102 -0
  65. metadata +166 -0
@@ -0,0 +1,661 @@
1
+ require File.dirname(__FILE__)+'/support/regexp-enhance.rb'
2
+
3
+ module NanDoc::Filters
4
+ class General < ::Nanoc3::Filter
5
+ include Nanoc3::Helpers::HTMLEscape
6
+ alias_method :h, :html_escape
7
+
8
+ module TerminalColorToHtml; end
9
+ include TerminalColorToHtml
10
+
11
+ # Parses document contents for nanDoc enhancements to
12
+ # markdown-like markup.
13
+
14
+ # These are the supported tags:
15
+ # * a ruby "code fence" (indicated with 'ruby:')
16
+ # * a terminal "code fence" (indicated with 'from the command line:')
17
+ # * example injection from unit tests
18
+ #
19
+ # @example the parts in the 'code fence' will be highlighted as ruby:
20
+ #
21
+ # ## This Is A H2 Header Tag
22
+ # the following is some ruby.
23
+ # ruby:
24
+ # ~~~
25
+ # foo = Bar.new(:baz => 'biff')
26
+ # foo.run
27
+ # ~~~
28
+ #
29
+ # (The line that says "ruby:" should not appear in the output, nor
30
+ # should the triple-tilde "fences" demarcating the code block.)
31
+ #
32
+ # This is designed to degrade gracefully in other contexts, if you're
33
+ # running this only through a kramdown (or other markdown filter)
34
+ # without the nandoc processing.
35
+ #
36
+ # For example if ppl are viewing your README at github, you might
37
+ # want to indent the code inside the fence with 4 spaces so that
38
+ # Github-Flavored Markdown will render it in html <CODE> tags. You
39
+ # won't get the coloring but it will still hopefully look alright.
40
+ #
41
+ # (@todo in the future use 4-space indents instead of code fences?)
42
+ #
43
+ # This is comparable to Nanoc3::Filters::ColorizeSyntax but it works in
44
+ # markdown, not html contexts. (It doesn't really matter what 'type' of
45
+ # file it is in though, if the filter is used sufficiently early and its
46
+ # ouptut doesn't get altered by latter filters.)
47
+ #
48
+ # This has less options than the Nanoc3 ColorizeSyntax filter (zero
49
+ # being less than all positive integers): it uses
50
+ # the ruby gem 'syntax/convertors/html' and outputs html with spans that
51
+ # have different classes for the different types of ruby tokens.
52
+ # (inspired by Trollop documentation.)
53
+ #
54
+ # Create your own stylesheet or use the trollop-like one
55
+ # that is provided by the nanDoc site generator (see the proto/ directory
56
+ # for the actual stylesheet, likely added by default.)
57
+ #
58
+ # There is no magic escaping sequences provided. You simply cannot have
59
+ # three tildes in your code block, unless you write the html yourself.
60
+ #
61
+ # This was written to be indentation aware, so that your opening fence
62
+ # must have the same amount of indentation as your closing fence.
63
+ #
64
+ # (Plans for nested tags one day maybe.
65
+ # One day maybe, nested blocks will therefor be demarcated by
66
+ # fences with either more *or less* indenation than the demarcating
67
+ # fences in question.(?))
68
+ #
69
+ # Your fences and content can have any leading amount of indent, provided
70
+ # that the opening and closing fence have the same indent.
71
+ # Any leading indent from the fences will be stripped from each line of
72
+ # content provided that each line of content has at least as much indent
73
+ # as the fences.
74
+ #
75
+
76
+ include NanDoc::StringFormatting # oxford_comma
77
+
78
+
79
+ # @param [String] content The content to filter
80
+ # @param [Hash] params - not used
81
+ # @return [String] The filtered content
82
+ def run content, params={}
83
+ content = filter_fence(content) if ReFenceFind =~ content
84
+ content = filter_see_test(content) if ReSeeTest =~ content
85
+ content
86
+ end
87
+
88
+ def unindent_generic str
89
+ md = ReUnindent.match(str) or fail("no")
90
+ reindent_content(md)
91
+ end
92
+ private
93
+
94
+ RecognizedTags = ['ruby', 'from the command line']
95
+ ReFenceFind = /^([[:space:]]*)[ a-z]+:[[:space:]]*\n\1~~~[[:space:]]*\n/m
96
+ ReFenceCap = %r<
97
+ ^((?:[ ]|\t)*)((?:[a-z]|[ ])+):(?:[ ]|\t)*\n
98
+ \1~~~(?:[ ]|\t)*\n
99
+ (.*?)\n
100
+ \1~~~(?:[ ]|\t)*\n
101
+ >mx
102
+ NanDoc::RegexpEnhance.names(ReFenceCap, :indent, :tag_name, :content)
103
+
104
+ ReSeeTest = /\(see: ((?:spec|test)[^\)]+)\)/
105
+ NanDoc::RegexpEnhance.names(ReSeeTest, :content)
106
+
107
+ ReSeeTestParse =
108
+ /\A((?:spec|test).*\.rb) ?(?:--?|\/) ?['"]([^'"]+)['"](.+)?\Z/
109
+ NanDoc::RegexpEnhance.names(ReSeeTestParse, :testfile, :testname, :xtra)
110
+
111
+ ReUnindent = /\A([ \t]*)(.*)\Z/m
112
+ NanDoc::RegexpEnhance.names(ReUnindent, :indent, :content)
113
+
114
+ def initialize(*a)
115
+ super(*a)
116
+ @prompt_str = prompt_str_default
117
+ end
118
+
119
+ def advance_to_story sexp, md, idx
120
+ story = md[:xtra] =~ /\A *(?:--?|\/) *['"](.+)['"]\Z/ && $1 or
121
+ fail("couldn't parse out story name from #{md[:xtra].inspect}"<<
122
+ "expecting for e.g \" - 'blah blah'\"")
123
+ idx = sexp.index{|x| x.first == :story && x[1] == story } or
124
+ fail("couldn't find story #{story.inspect}. Known stories are: ("<<
125
+ sexp.select{|x| x.first == :story }.map{|x| "'#{x[1]}'"}.join(', ')<<
126
+ ")")
127
+ idx + 1
128
+ end
129
+
130
+ def converter_ruby
131
+ @converter_ruby ||= begin
132
+ require 'syntax/convertors/html'
133
+ ::Syntax::Convertors::HTML.for_syntax 'ruby'
134
+ end
135
+ end
136
+
137
+ def sexp_getter
138
+ @sexp_getter ||= begin
139
+ require File.dirname(__FILE__)+'/spec-doc.rb'
140
+ NanDoc::SpecDoc::
141
+ TestFrameworkDispatcher.new(current_project_root_hack)
142
+ end
143
+ end
144
+
145
+ #
146
+ # @todo this sucks. i didn't have time to think about how this should
147
+ # work when i wrote it. The problem is, given that we are a nanoc
148
+ # project, how do we know where live the tests we are spec-docing?
149
+ #
150
+ # For now we assume that the mysite folder is one level inside the root
151
+ # of the (gem-like) project folder and we assert that the assumed root
152
+ # looks right but this will be broken in the likely event that the nanoc
153
+ # project will live elsewhere relative to the gem-like root.
154
+ #
155
+ def current_project_root_hack
156
+ presumed_root = File.dirname(FileUtils.pwd)
157
+ thems = %w(spec test)
158
+ found = thems.detect{ |dir| File.directory?(presumed_root+'/'+dir) }
159
+ fail("couldn't find " << oxford_comma(thems,' or ', &quoted) <<
160
+ "in #{presumed_root}") unless found
161
+ presumed_root
162
+ end
163
+
164
+ def ellipsis_default
165
+ "..."
166
+ end
167
+
168
+ def filter_fence content
169
+ with_each_tag_in(content, ReFenceCap) do |md|
170
+ render_block_fence md
171
+ end
172
+ end
173
+
174
+ def filter_see_test content
175
+ with_each_tag_in(content, ReSeeTest) do |md|
176
+ render_block_see_test md
177
+ end
178
+ end
179
+
180
+ #
181
+ # if the string has anything that looks like colors in it,
182
+ # then just highlite the colors (as html), else
183
+ # highlight things that look like prompts (as html)
184
+ #
185
+ def lines_highlighted content
186
+ if colored_content = terminal_color_to_html(content)
187
+ [colored_content]
188
+ else
189
+ these = content.split("\n").map do |line|
190
+ line.sub( %r!\A(~[^>]*>)?(.*)\Z! ) do
191
+ tags = [];
192
+ tags.push "<span class='prompt'>#{h($1)}</span>" if $1
193
+ tags.push "<span class='normal'>#{h($2)}</span>" unless $2.empty?
194
+ tags.join('')
195
+ end
196
+ end
197
+ these
198
+ end
199
+ end
200
+
201
+ # @return the amount to advance the cursor by
202
+ def process_sexp_ellipsis lines, sexp, i
203
+ j = i
204
+ fail("need :out_begin") unless sexp[j].first == :out_begin
205
+ lines.push sexp[j][1].strip
206
+ j += 1
207
+ if sexp[j] && sexp[j].first == :cosmetic_ellipsis
208
+ lines.push sexp[j][1]
209
+ j += 1
210
+ else
211
+ lines.push ellipsis_default
212
+ end
213
+ fail("need :out_end") unless sexp[j] && sexp[j].first == :out_end
214
+ lines.push sexp[j][1]
215
+ ret = j - i
216
+ ret
217
+ end
218
+
219
+ def prompt_str_cd new_dir
220
+ /\A(.+) > \Z/ =~ prompt_str or
221
+ fail("can't determine cwd from #{prmpt_str.inspect}")
222
+ new_pwd = "#{$1}/#{new_dir}"
223
+ @old_prompts ||= []
224
+ @old_prompts.push prompt_str
225
+ @prompt_str = "#{new_pwd} > "
226
+ nil
227
+ end
228
+
229
+ def prompt_str_cd_pop
230
+ @prompt_str = @old_prompts.pop
231
+ end
232
+
233
+ def prompt_str_default
234
+ '~ > '
235
+ end
236
+
237
+ public # avoid warnings
238
+ attr_reader :prompt_str
239
+ private :prompt_str
240
+ private
241
+
242
+ def render_block_fence md
243
+ if ! RecognizedTags.include?(md[:tag_name])
244
+ fail("sorry, there has been a mistake. I shouldn't have parsed it "<<
245
+ "but i went ahead and parsed a #{md[:tag_name].inspect} tag even "<<
246
+ "though i don't know what to do with it. Known tags are "<<
247
+ oxford_comma(RecognizedTags,&quoted)
248
+ )
249
+ end
250
+ case md[:tag_name]
251
+ when 'ruby'; render_block_fence_ruby md
252
+ when 'from the command line'; render_block_fence_terminal md
253
+ end
254
+ end
255
+
256
+ def render_block_see_test md
257
+ md2 = ReSeeTestParse.match(md[:content]) or
258
+ fail("couldn't parse see_test string: #{md[:content].inspect} -- "<<
259
+ 'string must look like: \'(see: test_file.rb/"some test name")\''
260
+ )
261
+ getter = sexp_getter
262
+ sexp = getter.get_sexp md2[:testfile], md2[:testname]
263
+ sexp.first.first == :method or fail("i'm done with this kind of sexp")
264
+ methname = sexp.first[1]
265
+ idx = 0
266
+ last = sexp.length - 1
267
+ if md2[:xtra]
268
+ idx = advance_to_story(sexp, md2, idx)
269
+ end
270
+ idx += 1 if sexp[0] && sexp[0].first == :method && idx == 0
271
+ catch(:done) do
272
+ while idx <= last
273
+ node = sexp[idx]
274
+ type = node.first
275
+ case type
276
+ when :cd, :command
277
+ idx += process_sexp_command sexp, idx
278
+ when :method
279
+ if node[1] != methname
280
+ fail("i don't like this kind of sexp anymore -- "<<
281
+ "#{methname.inspect} != #{node[1].inspect}"
282
+ )
283
+ end
284
+ when :note
285
+ process_sexp_note sexp, idx
286
+ when :record_ruby
287
+ idx += ProcessSnippet.new(self, sexp, idx).process
288
+ when :story
289
+ # always stop at next story?
290
+ throw :done
291
+ else
292
+ fail("unexpected sexp node here: #{type.inspect} idx: #{idx}")
293
+ end
294
+ idx += 1
295
+ end
296
+ end
297
+ end
298
+
299
+ def render_block_fence_terminal md
300
+ content = reindent_content md
301
+ render_terminal_div_highlighted content
302
+ end
303
+
304
+ def render_block_fence_ruby md
305
+ content = reindent_content md
306
+ content2 = content.strip # i think
307
+ hilited = converter_ruby.convert(content2, false)
308
+ pre_block = "\n<pre class='ruby'>\n#{hilited}\n</pre>\n"
309
+ @chunks.push pre_block
310
+ end
311
+ public :render_block_fence_ruby # visitors
312
+
313
+ def render_command_div content
314
+ fake_command = "#{prompt_str}#{content}"
315
+ render_terminal_div_highlighted fake_command
316
+ end
317
+
318
+ # @return [integer] how much to advance the cursor by
319
+ # when you either hit the end or hit an unrecognized node
320
+ # adds to rendered output a div with the command and output
321
+ def process_sexp_command sexp, i
322
+ lines = []
323
+ j = i
324
+ last = sexp.length - 1
325
+ catch(:done) do
326
+ while j <= last
327
+ type = sexp[j].first
328
+ case type
329
+ when :cd; lines.push "#{prompt_str}cd #{sexp[j][1]}"
330
+ prompt_str_cd sexp[j][1]
331
+ when :cd_end; prompt_str_cd_pop
332
+ when :command; lines.push "#{prompt_str}#{sexp[j][1]}"
333
+ when :out; lines.push sexp[j][1].strip
334
+ when :out_begin
335
+ j += process_sexp_ellipsis lines, sexp, j
336
+ else;
337
+ j -= 1 unless j == i
338
+ throw :done
339
+ end
340
+ j += 1
341
+ end
342
+ end
343
+ raw_content = lines.join("\n")
344
+ render_terminal_div_highlighted raw_content
345
+ ret = j-i
346
+ ret
347
+ end
348
+
349
+ # @return [nil] adds the output raw to the blickle blackle
350
+ def process_sexp_note sexp, idx
351
+ chunk = sexp[idx][1].call
352
+ @chunks.push chunk
353
+ end
354
+
355
+ def render_terminal_div_highlighted content
356
+ lines = ['<pre class="terminal">']
357
+ lines.concat lines_highlighted(content)
358
+ lines.push '</pre>'
359
+ html = lines * "\n" + "\n"
360
+ @chunks.push html
361
+ end
362
+
363
+ def render_unparsed start, last
364
+ chunk = @content[start..last]
365
+ @chunks.push chunk
366
+ end
367
+
368
+ def reindent_content md
369
+ raw_content = md[:content]
370
+ indent = md[:indent]
371
+ return raw_content if indent == ''
372
+ # of all non-blank lines find the minimum indent.
373
+ x = raw_content.scan(/^[[:space:]]+(?=[^[:space:]])/).map(&:length).min
374
+ # if for some reason the content has less indent than the fences,
375
+ # don't alter any of it.
376
+ return raw_content if x < indent.length
377
+ re = /^#{Regexp.new(indent)}/
378
+ unindented_content = raw_content.gsub(re, '')
379
+ unindented_content
380
+ end
381
+
382
+ # ignoring multiline crap for now
383
+ def ruby_snippet_inspect inspect
384
+ "\n#=> #{inspect}"
385
+ end
386
+
387
+ def with_each_tag_in content, regexp, &block
388
+ @content = content
389
+ @chunks = []
390
+ unparsed_start = 0
391
+ offset = 0
392
+ last = content.length - 1
393
+ while offset <= last && md = regexp.match(content[offset..-1])
394
+ unparsed_end = md.offset(0)[0] - 1 + offset
395
+ offset += md.offset(0)[1]+1
396
+ render_unparsed(unparsed_start, unparsed_end)
397
+ unparsed_start = offset # offset is where we start searching next
398
+ block.call(md)
399
+ end
400
+ render_unparsed(unparsed_start, -1)
401
+ @chunks.join('')
402
+ end
403
+
404
+ require 'strscan'
405
+ module TerminalColorToHtml
406
+ # this sucks.
407
+ #
408
+ def terminal_color_to_html str
409
+ return nil unless str.index("\e[") # save a whale
410
+ scn = StringScanner.new(str)
411
+ sexp = []
412
+ while true
413
+ foo = scn.scan(/(.*?)(?=\e\[)/m)
414
+ if ! foo
415
+ blork = scn.scan_until(/\Z/m) or fail("worglebezoik")
416
+ sexp.push([:content, blork]) unless blork.empty?
417
+ break;
418
+ end
419
+ foo or fail("oopfsh")
420
+ sexp.push([:content, foo]) unless foo.empty?
421
+ bar = scn.scan(/\e\[/) or fail("goff")
422
+ baz = scn.scan(/\d+(?:;\d+)*/)
423
+ baz or fail("narghh")
424
+ if '0'==baz
425
+ sexp.push([:pop])
426
+ else
427
+ sexp.push([:push, *baz.split(';')])
428
+ end
429
+ biff = scn.scan(/m/) or fail("noiflphh")
430
+ end
431
+ html = terminal_colorized_sexp_to_html sexp
432
+ html
433
+ end
434
+ private
435
+ # the other side of these associations lives in trollop-subset.css
436
+ Code2CssClass = {
437
+ '1' => 'bright', '30' => 'black', '31' => 'red', '32' => 'green',
438
+ '33' => 'yellow', '34' => 'blue', '35' => 'magenta', '36' => 'cyan',
439
+ '37' => 'white'
440
+ }
441
+ def terminal_code_to_css_class code
442
+ Code2CssClass[code] or
443
+ fail("sorry, no known code for #{code.inspect}. "<<
444
+ "(maybe you should make one?)")
445
+ end
446
+ def terminal_colorized_sexp_to_html sexp
447
+ i = -1;
448
+ last = sexp.length - 1;
449
+ parts = []
450
+ catch(:done) do
451
+ while (i+=1) <= last
452
+ codes = nil
453
+ while i <= last && sexp[i].first == :push
454
+ codes ||= []
455
+ codes.concat sexp[i][1..-1]
456
+ i += 1
457
+ end
458
+ if codes
459
+ classes = codes.map{|c| terminal_code_to_css_class(c) }*' '
460
+ parts.push "<span class='#{classes}'>"
461
+ end
462
+ throw :done if i > last
463
+ case sexp[i].first
464
+ when :content; parts.push(sexp[i][1])
465
+ when :pop; parts.push('</span>')
466
+ else; fail('fook');
467
+ end
468
+ end
469
+ end
470
+ html = parts*''
471
+ html
472
+ end
473
+ end
474
+
475
+ module HackHelpers
476
+ # stand-in for ruby-2-ruby
477
+ #
478
+
479
+ def leading_indent str
480
+ /\A([ \t]*)/ =~ str && $1
481
+ end
482
+ def re_for_line_with_same_indent_as str
483
+ ind = leading_indent(str)
484
+ re = /\A#{Regexp.escape(ind)}(?=[^ \t])/
485
+ re
486
+ end
487
+ def re_for_here here
488
+ /\A[ \t]*#{Regexp.escape(here)}[ \t]*\n?\Z/
489
+ end
490
+ def re_for_unindent_gsub indent
491
+ re = /\A#{Regexp.escape(indent)}/
492
+ re
493
+ end
494
+ def string_diff_assert long, short
495
+ idx = long.index(short) or fail("short not found in long -- "<<
496
+ "#{short.inspect} in #{long.inspect}")
497
+ head = long[0,idx] # usu. ''
498
+ tail = long[idx + short.length..-1]
499
+ head + tail
500
+ end
501
+ end
502
+
503
+ class ProcessSnippet
504
+ include HackHelpers
505
+
506
+ def initialize filter, sexp, idx
507
+ @prefix = '# => '
508
+ sexp[idx].first == :record_ruby or fail("must be record_ruby")
509
+ @filter, @sexp, @idx = filter, sexp, idx
510
+ end
511
+ ReInspect = /\A([ \t]*)(?:nandoc\.inspect[ \t]*)(.+)\Z/
512
+ NanDoc::RegexpEnhance.names(ReInspect, :indent, :tail)
513
+ ReOut = /\A[ \t]*nandoc\.out\([ \t]*<<-'?([A-Z]+)/
514
+ ReUntilDo = /\A[ \t]*\)[ \t]*do[ \t]*\Z/
515
+
516
+ ReHere = /\A(.*)(?:, ?<<-'?([A-Z]+))/
517
+ NanDoc::RegexpEnhance.names(ReHere, :keep, :here)
518
+
519
+ ConsumeThese = [:inspect, :out]
520
+
521
+ def process
522
+ j = @idx
523
+ snip = @sexp[j][1]
524
+ @lines = snip.file_lines.dup # we will be changing values
525
+ @inspects = []
526
+ while @sexp[j+1] && ConsumeThese.include?(@sexp[j+1].first)
527
+ j += 1
528
+ @inspects.push @sexp[j]
529
+ end
530
+ inspects_interpolate if @inspects.any?
531
+ unindented_lines = @lines[snip.line_start..snip.line_stop-2]
532
+ fake_unindented_ruby = unindented_lines.compact.join('')
533
+ indent = /\A([ \t]*)/ =~ fake_unindented_ruby && $1
534
+ fake_md = {:indent => indent, :content => fake_unindented_ruby}
535
+ @filter.render_block_fence_ruby(fake_md)
536
+ advance_by = j - @idx
537
+ advance_by
538
+ end
539
+ private
540
+ def erase_to_here offset, here
541
+ re = /\A[ \t]*#{Regexp.escape(here)}\Z/
542
+ found = (offset+1..@lines.length-1).detect{|idx| @lines[idx] =~ re }
543
+ found or fail("heredoc hack failed. no #{re} found.")
544
+ (offset+1..found).each{ |idx| @lines[idx] = nil }
545
+ nil
546
+ end
547
+
548
+ # whether with a nandoc.out or nandoc.inspect, keeping the indentation
549
+ # that's in the sourcefile, substitute real code for pretty code,
550
+ # without changing the offsets of the lines. fragile hack.
551
+ # @todo ruby2ruby?
552
+ #
553
+ def inspects_interpolate
554
+ @inspects.each do |ins|
555
+ # each call below will alter @lines (without shifting them)
556
+ # accordingly, replacing actual code with pretty code (and comments)
557
+ case ins[0]
558
+ when :inspect; process_inspect(ins)
559
+ when :out; process_out(ins)
560
+ else fail("no: #{ins[0].inspect}")
561
+ end
562
+ end
563
+ end
564
+
565
+ # the block content gets output as if it's bare ruby,
566
+ # the output (whether it was expected or actual we don't care)
567
+ # gets output with leading comment markers. All of this nonsense is
568
+ # bs proof of concept that needs to get blown away by ruby2ruby or
569
+ # something
570
+ #
571
+ def process_out sexp
572
+ call_offset = sexp[2][:line] - 1
573
+ first = @lines[call_offset]
574
+ ReOut =~ first or fail("DocSpec hack fail: Didn't look like "<<
575
+ "nandoc.out line: #{first.inspect}\nExpecting line to match "<<
576
+ " #{reOut}")
577
+ j = call_offset + 1
578
+ last = @lines.length - 1
579
+ j += 1 until j > last || ReUntilDo =~ @lines[j]
580
+ j <= last or fail("DocSpec hack fail: Couldn't find do block "<<
581
+ "anywhere before EOF with #{ReUntilDo}")
582
+ do_line = @lines[j]
583
+ re = re_for_line_with_same_indent_as(do_line)
584
+ repl_lines = []
585
+ j += 1
586
+ repl_from_here = j
587
+ j += 1 until j > last || re =~ @lines[j]
588
+ j <= last or fail("DocSpec hack fail: Couldn't find end of do "<<
589
+ "block anywhere before EOF with #{re}")
590
+ offset_of_line_with_end = j
591
+ repl_lines = @lines[repl_from_here..offset_of_line_with_end-1]
592
+ repl_lines.any? or fail("DocSpec hack fail -- no lines")
593
+ ind_short = leading_indent(do_line)
594
+ ind_long = leading_indent(repl_lines.first)
595
+ ind_diff = string_diff_assert(ind_long, ind_short)
596
+ unindent = re_for_unindent_gsub(ind_diff)
597
+ repl_lines.map{ |x| x.sub!(unindent, '') } # this changes @lines val
598
+ (0..repl_lines.length-1).each do |l|
599
+ actual_offset = call_offset + l
600
+ @lines[actual_offset] = repl_lines[l]
601
+ end
602
+ erase_from_here = call_offset + repl_lines.length
603
+ (erase_from_here..offset_of_line_with_end).each do |l|
604
+ @lines[l] = nil
605
+ end
606
+ # this is so fragile, it requires multiline blah blah
607
+ commented_content = sexp[1].gsub(/^/m, "#{ind_short}# ")
608
+ @lines[erase_from_here] = commented_content
609
+ nil
610
+ end
611
+
612
+ #
613
+ # comments at process_out apply to this too
614
+ #
615
+ def process_inspect sexp
616
+ offset = sexp[2][:line] - 1
617
+ act = @lines[offset]
618
+ md = ReInspect.match(act) or
619
+ fail("hack fail of nandoc.inspect near #{act.inspect}")
620
+ md = md.to_hash
621
+ tail = md[:tail]
622
+ my_lines = []
623
+ md2 = ReHere.match(tail)
624
+ return process_inspect_oneline(sexp) unless md2
625
+ md2 = md2.to_hash
626
+ ind = leading_indent(@lines[offset])
627
+ my_lines.push "#{ind}#{md2[:keep]}\n"
628
+ re = re_for_here(md2[:here])
629
+ j = offset + 1
630
+ last = @lines.length - 1
631
+ until re =~ @lines[j] || j > last
632
+ back_one = @lines[j].sub(/\A(?: |\t)/,'')
633
+ my_lines.push back_one.sub(/\A([\t ]*)/){ "#{$1}#{@prefix}" }
634
+ j += 1
635
+ end
636
+ j > last && fail("DocSpec hack fail: #{md2[:here]} not found "<<
637
+ "anywhere before EOF")
638
+ (offset..j).each do |k|
639
+ @lines[k] = nil
640
+ end
641
+ (0..my_lines.length-1).each do |k|
642
+ l = offset+k
643
+ @lines[l] = my_lines[k]
644
+ end
645
+ end
646
+
647
+ def process_inspect_oneline sexp
648
+ offset = sexp[2][:line] - 1
649
+ line = @lines[offset]
650
+ /\A([ \t]*)nandoc.inspect *([^,]+), *([^,]+)\n\Z/ =~ line or fail(
651
+ "DocSpec hack fail: Why can't we parse this inspect "<<
652
+ " line?\n#{line.inspect}")
653
+ ind, keep, val = $1, $2, $3 # actually we don't want $3
654
+ replace_with = "#{ind}#{keep}\n#{ind}#{@prefix}#{sexp[1]}\n"
655
+ # assume no newlines in value when the whole thing was oneline
656
+ @lines[offset] = replace_with
657
+ nil
658
+ end
659
+ end
660
+ end
661
+ end