nandoc 0.0.1

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