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