nandoc 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README +124 -0
- data/Rakefile +53 -0
- data/bin/nandoc +6 -0
- data/doc/CREDITS.md +6 -0
- data/doc/FAQ/why-not-wiki.md +20 -0
- data/doc/FAQ.md +68 -0
- data/doc/TODOs-and-BUGs.md +15 -0
- data/doc/bar/baz.md +4 -0
- data/doc/bar/bliff.md +8 -0
- data/doc/foo.md +5 -0
- data/doc/getting-started.rb +13 -0
- data/doc/svg/less-fonts.svg +21 -0
- data/lib/nandoc/commands/create-nandoc-site.rb +225 -0
- data/lib/nandoc/commands/diff.rb +279 -0
- data/lib/nandoc/config.rb +58 -0
- data/lib/nandoc/cri-hacks.rb +13 -0
- data/lib/nandoc/data-source.rb +239 -0
- data/lib/nandoc/filters.rb +661 -0
- data/lib/nandoc/helpers/menu-bouncy.rb +109 -0
- data/lib/nandoc/helpers/site-map.rb +157 -0
- data/lib/nandoc/helpers/top-nav.rb +47 -0
- data/lib/nandoc/helpers.rb +42 -0
- data/lib/nandoc/item-class-hacks.rb +57 -0
- data/lib/nandoc/nandoc.persistent.json +3 -0
- data/lib/nandoc/parse-readme.rb +95 -0
- data/lib/nandoc/spec-doc/mini-test/spec-instance-methods.rb +0 -0
- data/lib/nandoc/spec-doc/mini-test.rb +105 -0
- data/lib/nandoc/spec-doc/mock-prompt.rb +121 -0
- data/lib/nandoc/spec-doc/support-modules.rb +158 -0
- data/lib/nandoc/spec-doc/test-case-agent.rb +57 -0
- data/lib/nandoc/spec-doc/test-framework-dispatcher.rb +15 -0
- data/lib/nandoc/spec-doc/test-framework-proxy.rb +78 -0
- data/lib/nandoc/spec-doc.rb +46 -0
- data/lib/nandoc/support/diff-proxy.rb +113 -0
- data/lib/nandoc/support/orphanage.rb +77 -0
- data/lib/nandoc/support/path-tardo.rb +85 -0
- data/lib/nandoc/support/regexp-enhance.rb +76 -0
- data/lib/nandoc/support/site-diff.rb +46 -0
- data/lib/nandoc/support/site-merge.rb +62 -0
- data/lib/nandoc/support/site-methods.rb +69 -0
- data/lib/nandoc/support/stream-colorizer.rb +203 -0
- data/lib/nandoc/support-modules.rb +270 -0
- data/lib/nandoc/test/diff-to-string.rb +251 -0
- data/lib/nandoc/test/minitest-extlib.rb +53 -0
- data/lib/nandoc/treebis/NOGIT-DOCS/NEWS.md +5 -0
- data/lib/nandoc/treebis/NOGIT-README.md +65 -0
- data/lib/nandoc/treebis/nandoc.persistent.json +3 -0
- data/lib/nandoc.rb +48 -0
- data/proto/README.md +31 -0
- data/proto/default/Rakefile +1 -0
- data/proto/default/Rules +46 -0
- data/proto/default/config.yaml +57 -0
- data/proto/default/content/css/nanoc-dist-altered.css +213 -0
- data/proto/default/content/css/trollop-subset.css +116 -0
- data/proto/default/content/js/menu-bouncy.js +126 -0
- data/proto/default/content/stylesheet.css.diff +20 -0
- data/proto/default/content/vendor/jquery-1.3.js +4241 -0
- data/proto/default/content/vendor/jquery.easing.1.3.js +205 -0
- data/proto/default/layouts/default.html +70 -0
- data/proto/default/lib/default.orig.rb +2 -0
- data/proto/default/lib/default.rb +5 -0
- data/proto/default/treebis-task.rb +28 -0
- data/proto/misc/orphan-surrogate.md +6 -0
- data/test/test.rb +102 -0
- 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 ', "ed) <<
|
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,"ed)
|
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
|