maui 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/GPL-3 +674 -0
- data/Makefile +17 -0
- data/Manifest.txt +9 -0
- data/README.fab +222 -0
- data/README.html +296 -0
- data/bin/maui +200 -0
- data/lib/mau/fabricator.rb +2071 -0
- data/maui.fab +3576 -0
- data/maui.gemspec +24 -0
- metadata +59 -0
data/bin/maui
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
#! /usr/bin/ruby -rubygems
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require 'getoptlong'
|
5
|
+
require 'mau/fabricator'
|
6
|
+
|
7
|
+
$0 = 'maui' # for [[GetoptLong]] error reporting
|
8
|
+
begin
|
9
|
+
$cmdline = OpenStruct.new
|
10
|
+
$cmdline.pseudographics = Fabricator::UNICODE_PSEUDOGRAPHICS
|
11
|
+
$cmdline.output_width = 80
|
12
|
+
$cmdline.chunk_size_limit = 24
|
13
|
+
$cmdline.link_css = []
|
14
|
+
|
15
|
+
GetoptLong.new(
|
16
|
+
['--output-width', GetoptLong::REQUIRED_ARGUMENT],
|
17
|
+
|
18
|
+
['--chunk-size-limit', GetoptLong::REQUIRED_ARGUMENT],
|
19
|
+
|
20
|
+
['--link-css', GetoptLong::REQUIRED_ARGUMENT],
|
21
|
+
|
22
|
+
['--unicode-boxes', GetoptLong::NO_ARGUMENT],
|
23
|
+
|
24
|
+
['--ascii-boxes', GetoptLong::NO_ARGUMENT],
|
25
|
+
|
26
|
+
['--help', GetoptLong::NO_ARGUMENT],
|
27
|
+
|
28
|
+
['--version', GetoptLong::NO_ARGUMENT],
|
29
|
+
).each do |opt, arg|
|
30
|
+
case opt
|
31
|
+
when '--output-width' then
|
32
|
+
unless arg =~ /\A\d+\Z/ then
|
33
|
+
$stderr.puts "maui: --output-width requires a number"
|
34
|
+
exit 1
|
35
|
+
end
|
36
|
+
$cmdline.output_width = arg.to_i
|
37
|
+
|
38
|
+
when '--chunk-size-limit' then
|
39
|
+
unless arg =~ /\A\d+\Z/ then
|
40
|
+
$stderr.puts "maui: --chunk-size-limit " +
|
41
|
+
"requires a number"
|
42
|
+
exit 1
|
43
|
+
end
|
44
|
+
arg = arg.to_i
|
45
|
+
arg = nil if arg <= 0
|
46
|
+
$cmdline.chunk_size_limit = arg
|
47
|
+
|
48
|
+
when '--link-css' then
|
49
|
+
$cmdline.link_css.push arg
|
50
|
+
|
51
|
+
when '--unicode-boxes' then
|
52
|
+
$cmdline.pseudographics =
|
53
|
+
Fabricator::UNICODE_PSEUDOGRAPHICS
|
54
|
+
|
55
|
+
when '--ascii-boxes' then
|
56
|
+
$cmdline.pseudographics = Fabricator::ASCII_PSEUDOGRAPHICS
|
57
|
+
|
58
|
+
when '--help' then
|
59
|
+
puts "Usage: maui [options] fabric-file
|
60
|
+
|
61
|
+
Process the given Mau fabric, tangle its files and weave its
|
62
|
+
narrative into both HTML and coloured text.
|
63
|
+
|
64
|
+
--output-width=N
|
65
|
+
Word-wrap the woven ctxt at this width.
|
66
|
+
|
67
|
+
--chunk-size-limit=LINE-COUNT
|
68
|
+
Consider chunks longer than this many lines warnably long.
|
69
|
+
Chunks longer than twice this many lines will be
|
70
|
+
considered warnably very long.
|
71
|
+
|
72
|
+
--link-css=URL
|
73
|
+
Specify a stylesheet to be applied to the woven HTML.
|
74
|
+
Availability of the target CSS to the browser and the
|
75
|
+
relativity of the link are user's responsibility. If used
|
76
|
+
multiple times, all these links will be used, and their
|
77
|
+
order is preserved.
|
78
|
+
|
79
|
+
Usage of this option suppresses including the default,
|
80
|
+
built-in stylesheet in the output.
|
81
|
+
|
82
|
+
--help
|
83
|
+
Print this usage.
|
84
|
+
|
85
|
+
--version
|
86
|
+
Show version data.
|
87
|
+
|
88
|
+
Report bugs to: <dig@mirky.net>"
|
89
|
+
puts
|
90
|
+
exit 0
|
91
|
+
|
92
|
+
when '--version' then
|
93
|
+
puts "Mau Independent Fabricator 3.1.0
|
94
|
+
Copyright (C) 2003-2014 Andres Soolo
|
95
|
+
Copyright (C) 2013-2014 Knitten Development Ltd.
|
96
|
+
|
97
|
+
Licensed under GPLv3+: GNU GPL version 3 or later
|
98
|
+
<http://gnu.org/licenses/gpl.html>
|
99
|
+
|
100
|
+
This is free software: you are free to change and
|
101
|
+
redistribute it.
|
102
|
+
|
103
|
+
There is NO WARRANTY, to the extent permitted by law."
|
104
|
+
puts
|
105
|
+
exit 0
|
106
|
+
else
|
107
|
+
raise 'assertion failed'
|
108
|
+
end
|
109
|
+
end
|
110
|
+
if ARGV.empty? then
|
111
|
+
$stderr.puts "maui: no fabric filename given"
|
112
|
+
exit 1
|
113
|
+
end
|
114
|
+
$cmdline.fabric_filename = ARGV.shift
|
115
|
+
rescue GetoptLong::Error => e
|
116
|
+
# no need to display; it has already been reported
|
117
|
+
exit 1
|
118
|
+
end
|
119
|
+
|
120
|
+
fabric = open $cmdline.fabric_filename, 'r' do |port|
|
121
|
+
Fabricator.load_fabric port,
|
122
|
+
chunk_size_limit: $cmdline.chunk_size_limit
|
123
|
+
end
|
124
|
+
|
125
|
+
writeout_plan = {}
|
126
|
+
fabric.tangles.each_value do |results|
|
127
|
+
writeout_plan[results.filename] =
|
128
|
+
Fabricator.plan_to_write_out(results)
|
129
|
+
end
|
130
|
+
[
|
131
|
+
OpenStruct.new(
|
132
|
+
suffix: '.html',
|
133
|
+
description: 'HTML weaving',
|
134
|
+
generator: proc do |filename|
|
135
|
+
open filename, 'w' do |port|
|
136
|
+
port.set_encoding 'utf-8'
|
137
|
+
Fabricator.weave_html fabric, port,
|
138
|
+
title: $cmdline.fabric_filename,
|
139
|
+
link_css: $cmdline.link_css
|
140
|
+
end
|
141
|
+
puts "Weaved #{filename}"
|
142
|
+
end,
|
143
|
+
),
|
144
|
+
|
145
|
+
OpenStruct.new(
|
146
|
+
suffix: '.ctxt',
|
147
|
+
description: 'ctxt weaving',
|
148
|
+
generator: proc do |filename|
|
149
|
+
open filename, 'w' do |port|
|
150
|
+
Fabricator.weave_ctxt fabric, port,
|
151
|
+
width: $cmdline.output_width,
|
152
|
+
pseudographics: $cmdline.pseudographics
|
153
|
+
end
|
154
|
+
puts "Weaved #{filename}"
|
155
|
+
end,
|
156
|
+
),
|
157
|
+
].each do |special|
|
158
|
+
filename = File.basename($cmdline.fabric_filename).
|
159
|
+
sub(/(\.fab)?$/i, special.suffix)
|
160
|
+
if writeout_plan.has_key? filename then
|
161
|
+
number = fabric.warnings.length + 1
|
162
|
+
first_header = fabric.chunks_by_name[filename].
|
163
|
+
headers.first
|
164
|
+
warning = OpenStruct.new(
|
165
|
+
loc: first_header.header_loc,
|
166
|
+
message: "name clash with #{special.description}",
|
167
|
+
number: number,
|
168
|
+
inline: true,
|
169
|
+
)
|
170
|
+
fabric.warnings.push warning
|
171
|
+
(first_header.warnings ||= []).push warning
|
172
|
+
# For ordering purposes, we'll delete the old value before
|
173
|
+
# adding the new one at the same key.
|
174
|
+
writeout_plan.delete filename
|
175
|
+
end
|
176
|
+
writeout_plan[filename] = special.generator
|
177
|
+
end
|
178
|
+
|
179
|
+
Fabricator.show_warnings fabric
|
180
|
+
|
181
|
+
exit_code = 0
|
182
|
+
(ARGV.empty? ? writeout_plan.keys : ARGV.uniq).
|
183
|
+
each do |filename|
|
184
|
+
if thunk = writeout_plan[filename] then
|
185
|
+
path = filename.split '/'
|
186
|
+
(0 .. path.length - 2).each do |i|
|
187
|
+
dirname = path[0 .. i].join '/'
|
188
|
+
begin
|
189
|
+
Dir.mkdir dirname
|
190
|
+
puts "Created directory #{dirname}"
|
191
|
+
rescue Errno::EEXIST
|
192
|
+
end
|
193
|
+
end
|
194
|
+
thunk.call filename
|
195
|
+
else
|
196
|
+
$stderr.puts "maui: #{filename}: unknown output file"
|
197
|
+
exit_code = 1
|
198
|
+
end
|
199
|
+
end
|
200
|
+
exit exit_code
|
@@ -0,0 +1,2071 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
require 'rbconfig'
|
5
|
+
require 'set'
|
6
|
+
require 'stringio'
|
7
|
+
|
8
|
+
class ::String
|
9
|
+
# Local enclosed variable for [[#to_xml]]
|
10
|
+
char_entities = {
|
11
|
+
'&' => '&',
|
12
|
+
'<' => '<',
|
13
|
+
'>' => '>',
|
14
|
+
'"' => '"',
|
15
|
+
"'" => ''',
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
define_method :to_xml do ||
|
19
|
+
return gsub(/[&<>'"]/){char_entities[$&]}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module Fabricator
|
24
|
+
class Vertical_Peeker
|
25
|
+
def get_indented_lines_with_skip
|
26
|
+
indent = nil; lines = []
|
27
|
+
while peek_line =~ /^\s+/ or
|
28
|
+
(peek_line == '' and
|
29
|
+
!lines.empty? and
|
30
|
+
peek_line(1) =~ /^\s+/) do
|
31
|
+
# If the line ahead is not indented but we passed the
|
32
|
+
# test, then [[get_line]] will return [[""]] and [[$&]]
|
33
|
+
# is the __following__ line's indentation.
|
34
|
+
indent = $&.length if indent.nil? or $&.length < indent
|
35
|
+
lines.push get_line
|
36
|
+
end
|
37
|
+
return nil if lines.empty?
|
38
|
+
lines.each{|l| l[0 ... indent] = ''}
|
39
|
+
return OpenStruct.new(lines: lines, indent: indent)
|
40
|
+
end
|
41
|
+
|
42
|
+
def initialize port
|
43
|
+
super()
|
44
|
+
@port = port
|
45
|
+
if @port.respond_to? :path then
|
46
|
+
@filename = @port.path
|
47
|
+
elsif @port == $stdin then
|
48
|
+
@filename = '(stdin)'
|
49
|
+
else
|
50
|
+
@filename = '(unknown)'
|
51
|
+
end
|
52
|
+
@buffer = []
|
53
|
+
@line_number = 1 # number of the first line in the buffer
|
54
|
+
@eof_seen = false
|
55
|
+
return
|
56
|
+
end
|
57
|
+
|
58
|
+
def peek_line ahead = 0
|
59
|
+
raise 'invalid argument' unless ahead >= 0
|
60
|
+
until @buffer.length > ahead or @eof_seen do
|
61
|
+
line = @port.gets
|
62
|
+
if line then
|
63
|
+
line.rstrip!
|
64
|
+
@buffer.push line
|
65
|
+
else
|
66
|
+
@eof_seen = true
|
67
|
+
end
|
68
|
+
end
|
69
|
+
return @buffer[ahead] # nil if past eof
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_line
|
73
|
+
# ensure that if a line is available, it's in [[@buffer]]
|
74
|
+
peek_line
|
75
|
+
|
76
|
+
@line_number += 1 unless @buffer.empty?
|
77
|
+
return @buffer.shift
|
78
|
+
end
|
79
|
+
|
80
|
+
def eof?
|
81
|
+
return peek_line.nil?
|
82
|
+
end
|
83
|
+
|
84
|
+
def lineno_ahead
|
85
|
+
return @line_number + (@line_consumed ? 1 : 0)
|
86
|
+
end
|
87
|
+
|
88
|
+
def location_ahead
|
89
|
+
return OpenStruct.new(
|
90
|
+
filename: @filename, line: lineno_ahead)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class Integrator
|
95
|
+
def check_root_type_consistency
|
96
|
+
@output.roots.each do |name|
|
97
|
+
cbn_entry = @output.chunks_by_name[name]
|
98
|
+
effective_root_type = cbn_entry.root_type
|
99
|
+
cbn_entry.headers.each do |element|
|
100
|
+
unless element.root_type == effective_root_type then
|
101
|
+
warn element.header_loc,
|
102
|
+
"inconsistent root type, assuming %s" %
|
103
|
+
effective_root_type
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
return
|
108
|
+
end
|
109
|
+
|
110
|
+
attr_reader :output
|
111
|
+
|
112
|
+
def initialize
|
113
|
+
super()
|
114
|
+
@output = OpenStruct.new(
|
115
|
+
warnings: [],
|
116
|
+
presentation: [], # list of titles and sections
|
117
|
+
toc: [],
|
118
|
+
chunks_by_name: {},
|
119
|
+
# canonical_name => {
|
120
|
+
# root_type: String,
|
121
|
+
# chunks: list of :chunk/:diverted_chunk records,
|
122
|
+
# headers: list of :chunk/:divert records,
|
123
|
+
# }
|
124
|
+
roots: [], # list of canonical names
|
125
|
+
)
|
126
|
+
@cursec = nil # The current section if started
|
127
|
+
@section_count = 0 # The number of last section
|
128
|
+
@title_counters = [0]
|
129
|
+
@curdivert = nil # The current diversion if active
|
130
|
+
@last_divertee = nil
|
131
|
+
# last chunk diverted by [[@curdivert]]
|
132
|
+
@list_stack = nil
|
133
|
+
@in_code = false
|
134
|
+
@last_title_level = 0
|
135
|
+
@warning_counter = 0
|
136
|
+
return
|
137
|
+
end
|
138
|
+
|
139
|
+
def integrate element
|
140
|
+
if element.type == :title then
|
141
|
+
# Check the title's level restriction
|
142
|
+
if element.level > @last_title_level + 1 then
|
143
|
+
warn element.loc, "title level too deep"
|
144
|
+
element.level = @last_title_level + 1
|
145
|
+
end
|
146
|
+
@last_title_level = element.level
|
147
|
+
|
148
|
+
# Number the title
|
149
|
+
while @title_counters.length > element.level do
|
150
|
+
@title_counters.pop
|
151
|
+
end
|
152
|
+
if @title_counters.length < element.level then
|
153
|
+
@title_counters.push 0
|
154
|
+
end
|
155
|
+
@title_counters[-1] += 1
|
156
|
+
element.number = @title_counters.join '.'
|
157
|
+
|
158
|
+
# Append the node to [[presentation]] and [[toc]]
|
159
|
+
force_section_break
|
160
|
+
@output.presentation.push element
|
161
|
+
@output.toc.push element
|
162
|
+
|
163
|
+
# Enforce (sub(sub))chapter-locality of diversions
|
164
|
+
clear_diversion
|
165
|
+
else
|
166
|
+
if element.type == :block and @curdivert then
|
167
|
+
element.type = :diverted_chunk
|
168
|
+
element.name = @curdivert.name
|
169
|
+
element.divert = @curdivert
|
170
|
+
|
171
|
+
element.initial = true if @last_divertee.nil?
|
172
|
+
@last_divertee = element
|
173
|
+
end
|
174
|
+
if [:divert, :chunk].include? element.type then
|
175
|
+
clear_diversion
|
176
|
+
end
|
177
|
+
if (@cursec and element.type == :rubric) or
|
178
|
+
(@in_code and
|
179
|
+
[:paragraph, :block, :item].include?(
|
180
|
+
element.type)) then
|
181
|
+
(@cursec.warnings ||= []).push \
|
182
|
+
warn(element.loc,
|
183
|
+
"silent section break",
|
184
|
+
inline: true)
|
185
|
+
force_section_break
|
186
|
+
end
|
187
|
+
if @cursec.nil? then
|
188
|
+
@cursec = OpenStruct.new(
|
189
|
+
type: :section,
|
190
|
+
section_number: (@section_count += 1),
|
191
|
+
elements: [])
|
192
|
+
@output.presentation.push @cursec
|
193
|
+
end
|
194
|
+
if element.type == :rubric then
|
195
|
+
element.section_number = @cursec.section_number
|
196
|
+
@output.toc.push element
|
197
|
+
end
|
198
|
+
if element.type == :divert then
|
199
|
+
@curdivert = element
|
200
|
+
raise 'assertion failed' unless @last_divertee.nil?
|
201
|
+
end
|
202
|
+
|
203
|
+
if element.type == :item then
|
204
|
+
# Is this a top-level or descendant item?
|
205
|
+
unless @list_stack then
|
206
|
+
raise 'assertion failed' unless element.indent == 0
|
207
|
+
|
208
|
+
# Create a new [[list]] node.
|
209
|
+
new_list = OpenStruct.new(
|
210
|
+
type: :list,
|
211
|
+
items: [],
|
212
|
+
indent: element.indent)
|
213
|
+
@cursec.elements.push new_list
|
214
|
+
@list_stack = [new_list]
|
215
|
+
else
|
216
|
+
while @list_stack.last.indent > element.indent do
|
217
|
+
if @list_stack[-2].indent < element.indent then
|
218
|
+
# Unexpected de-dent, like this:
|
219
|
+
# - master list
|
220
|
+
# - child 1
|
221
|
+
# - child 2
|
222
|
+
@list_stack.last.indent = element.indent
|
223
|
+
(element.warnings ||= []).push \
|
224
|
+
warn(element.loc,
|
225
|
+
"unexpected dedent", inline: true)
|
226
|
+
break
|
227
|
+
end
|
228
|
+
@list_stack.pop
|
229
|
+
end
|
230
|
+
if @list_stack.last.indent < element.indent then
|
231
|
+
if @list_stack.last.sublist then
|
232
|
+
raise 'assertion failed'
|
233
|
+
end
|
234
|
+
new_list = OpenStruct.new(
|
235
|
+
type: :list,
|
236
|
+
items: [],
|
237
|
+
indent: element.indent)
|
238
|
+
@list_stack.last.items.last.sublist = new_list
|
239
|
+
@list_stack.push new_list
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# The list structure has been prepared. Append the
|
244
|
+
# new element to the innermost list in progress.
|
245
|
+
@list_stack.last.items.push element
|
246
|
+
else
|
247
|
+
@cursec.elements.push element
|
248
|
+
|
249
|
+
if [:chunk, :diverted_chunk].
|
250
|
+
include?(element.type) then
|
251
|
+
element.section_number = @cursec.section_number
|
252
|
+
@in_code = true
|
253
|
+
# so we can generate a section break if a
|
254
|
+
# narrative-type element follows
|
255
|
+
element.content = []
|
256
|
+
element.lines.each_with_index do
|
257
|
+
|line, lineno_in_chunk|
|
258
|
+
unless lineno_in_chunk.zero? then
|
259
|
+
element.content.push \
|
260
|
+
OpenStruct.new(type: :newline)
|
261
|
+
end
|
262
|
+
column = 1 + element.indent
|
263
|
+
line.split(/(<<\s*
|
264
|
+
(?:
|
265
|
+
\[\[.*?\]*\]\]
|
266
|
+
| .
|
267
|
+
)+?
|
268
|
+
\s*>>)/x, -1).each_with_index do
|
269
|
+
|raw_piece, piece_index|
|
270
|
+
node = nil
|
271
|
+
if piece_index.odd? then
|
272
|
+
name = raw_piece[2 ... -2].strip
|
273
|
+
# discard the surrounding double brokets
|
274
|
+
# together with adjacent whitespace
|
275
|
+
node = OpenStruct.new(type: :use,
|
276
|
+
name: nil,
|
277
|
+
# for ordering; will be replaced below
|
278
|
+
raw: raw_piece,
|
279
|
+
loc: OpenStruct.new(
|
280
|
+
filename: element.body_loc.filename,
|
281
|
+
line: element.body_loc.line +
|
282
|
+
lineno_in_chunk,
|
283
|
+
column: column)
|
284
|
+
)
|
285
|
+
if name =~ /(?:^|\s+)(\|[\w>-]+)$/ and
|
286
|
+
Fabricator::POSTPROCESSES.has_key? $1 then
|
287
|
+
node.postprocess = $1; name = $`
|
288
|
+
end
|
289
|
+
if name =~ /(?:^|\s+)(\.dense)$/ then
|
290
|
+
node.vertical_separation = $1; name = $`
|
291
|
+
end
|
292
|
+
if name =~ /^(\.clearindent)(?:\s+|$)/ then
|
293
|
+
node.clearindent = true; name = $'
|
294
|
+
end
|
295
|
+
if !name.empty? then
|
296
|
+
node.name =
|
297
|
+
Fabricator.canonicalise_chunk_name(name)
|
298
|
+
else
|
299
|
+
# not a proper reference, after all
|
300
|
+
node = nil
|
301
|
+
end
|
302
|
+
# If failed, [[node]] is still [[nil]].
|
303
|
+
end
|
304
|
+
if node.nil? and !raw_piece.empty? then
|
305
|
+
node = OpenStruct.new(
|
306
|
+
type: :verbatim,
|
307
|
+
data: raw_piece)
|
308
|
+
end
|
309
|
+
element.content.push node if node
|
310
|
+
column += raw_piece.length
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
314
|
+
if [:chunk, :diverted_chunk, :divert].include?(
|
315
|
+
element.type) then
|
316
|
+
cbn_record =
|
317
|
+
@output.chunks_by_name[element.name] ||=
|
318
|
+
OpenStruct.new(chunks: [], headers: [])
|
319
|
+
if [:chunk, :diverted_chunk].include?(
|
320
|
+
element.type) then
|
321
|
+
cbn_record.chunks.push element
|
322
|
+
end
|
323
|
+
if [:chunk, :divert].include? element.type then
|
324
|
+
cbn_record.headers.push element
|
325
|
+
end
|
326
|
+
|
327
|
+
if element.root_type then
|
328
|
+
# check the filename's reasonability
|
329
|
+
bad_name = false
|
330
|
+
parts = element.name.split '/'
|
331
|
+
if ['', '.', '..'].any?{|d| parts.include? d} then
|
332
|
+
bad_name = true
|
333
|
+
end
|
334
|
+
unless parts.all?{|p| p =~ /\A[\w.-]+\Z/} then
|
335
|
+
bad_name = true
|
336
|
+
end
|
337
|
+
if bad_name then
|
338
|
+
(element.warnings ||= []).push \
|
339
|
+
warn(element.header_loc,
|
340
|
+
"unuseable filename",
|
341
|
+
inline: true)
|
342
|
+
element.root_type = nil
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
# The :chunks_by_name record will hold the highest
|
347
|
+
# root_type for chunks of this name, with the order
|
348
|
+
# defined as [[nil]] < [['.file']] < [['.script']].
|
349
|
+
if element.root_type and
|
350
|
+
cbn_record.root_type.nil? then
|
351
|
+
cbn_record.root_type = element.root_type
|
352
|
+
@output.roots.push element.name
|
353
|
+
end
|
354
|
+
if element.root_type == '.script' then
|
355
|
+
cbn_record.root_type = element.root_type
|
356
|
+
end
|
357
|
+
end
|
358
|
+
@list_stack = nil
|
359
|
+
end
|
360
|
+
end
|
361
|
+
return
|
362
|
+
end
|
363
|
+
|
364
|
+
def force_section_break
|
365
|
+
@cursec = nil
|
366
|
+
@list_stack = nil
|
367
|
+
@in_code = false
|
368
|
+
return
|
369
|
+
end
|
370
|
+
|
371
|
+
def clear_diversion
|
372
|
+
if @curdivert then
|
373
|
+
if !@last_divertee then
|
374
|
+
(@curdivert.warnings ||= []).push \
|
375
|
+
warn(@curdivert.header_loc,
|
376
|
+
"unused diversion",
|
377
|
+
inline: true)
|
378
|
+
elsif @last_divertee.initial then
|
379
|
+
(@curdivert.warnings ||= []).push \
|
380
|
+
warn(@curdivert.header_loc,
|
381
|
+
"single-use diversion",
|
382
|
+
inline: true)
|
383
|
+
end
|
384
|
+
@curdivert = nil
|
385
|
+
@last_divertee.final = true if @last_divertee
|
386
|
+
@last_divertee = nil
|
387
|
+
end
|
388
|
+
return
|
389
|
+
end
|
390
|
+
|
391
|
+
def check_chunk_sizes limit
|
392
|
+
return unless limit
|
393
|
+
@output.presentation.each do |node|
|
394
|
+
next unless node.type == :section
|
395
|
+
node.elements.each do |element|
|
396
|
+
next unless element.type == :chunk
|
397
|
+
if element.lines.length > limit then
|
398
|
+
if element.lines.length > limit * 2 then
|
399
|
+
assessment, factor = "very long chunk", 2
|
400
|
+
else
|
401
|
+
assessment, factor = "long chunk", 1
|
402
|
+
end
|
403
|
+
limit_loc = element.body_loc.dup
|
404
|
+
limit_loc.column = nil
|
405
|
+
limit_loc.line += limit * factor
|
406
|
+
(element.warnings ||= []).push \
|
407
|
+
warn(limit_loc, "%s (%i lines)" %
|
408
|
+
[assessment, element.lines.length],
|
409
|
+
inline: true)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
return
|
414
|
+
end
|
415
|
+
|
416
|
+
def warn location, message, inline: false
|
417
|
+
record = OpenStruct.new(
|
418
|
+
loc: location,
|
419
|
+
message: message,
|
420
|
+
number: @warning_counter += 1,
|
421
|
+
inline: inline)
|
422
|
+
@output.warnings.push record
|
423
|
+
return record # so it can also be attached elsewhere
|
424
|
+
end
|
425
|
+
|
426
|
+
def tangle_chunks cbn_entry, sink, trace, vsep = 2
|
427
|
+
chain_start_loc = nil
|
428
|
+
cbn_entry.chunks.each_with_index do |chunk, i|
|
429
|
+
vsep.times{sink.newline} unless i.zero?
|
430
|
+
if chunk.divert and chunk.initial then
|
431
|
+
raise 'assertion failed' if chain_start_loc
|
432
|
+
chain_start_loc = sink.location_ahead
|
433
|
+
end
|
434
|
+
start_location = sink.location_ahead
|
435
|
+
chunk.content.each do |node|
|
436
|
+
case node.type
|
437
|
+
when :verbatim then
|
438
|
+
sink.write node.data
|
439
|
+
when :newline then
|
440
|
+
sink.newline
|
441
|
+
when :use then
|
442
|
+
tangle_transclusion node, sink, trace, chunk
|
443
|
+
else raise 'data structure error'
|
444
|
+
end
|
445
|
+
end
|
446
|
+
end_location = sink.location_behind
|
447
|
+
|
448
|
+
# Both endpoints are inclusive.
|
449
|
+
(chunk.tangle_locs ||= []).push OpenStruct.new(
|
450
|
+
from: start_location,
|
451
|
+
to: end_location)
|
452
|
+
if chunk.divert and chunk.final then
|
453
|
+
raise 'assertion failed' unless chain_start_loc
|
454
|
+
(chunk.divert.chain_tangle_locs ||= []).push \
|
455
|
+
OpenStruct.new(
|
456
|
+
from: chain_start_loc,
|
457
|
+
to: sink.location_behind)
|
458
|
+
chain_start_loc = nil
|
459
|
+
end
|
460
|
+
end
|
461
|
+
return
|
462
|
+
end
|
463
|
+
|
464
|
+
def tangle_transclusion node, sink, trace, referrer
|
465
|
+
name = node.name
|
466
|
+
if trace.include? name then
|
467
|
+
warn node.loc, "circular reference"
|
468
|
+
sink.write node.raw
|
469
|
+
else
|
470
|
+
cbn_entry = @output.chunks_by_name[name]
|
471
|
+
if cbn_entry.nil? or cbn_entry.chunks.empty? then
|
472
|
+
warn node.loc, "dangling reference"
|
473
|
+
sink.write node.raw
|
474
|
+
else
|
475
|
+
(cbn_entry.transcluders ||= []).push(
|
476
|
+
OpenStruct.new(
|
477
|
+
name: referrer.name,
|
478
|
+
section_number: referrer.section_number,
|
479
|
+
))
|
480
|
+
trace.add name
|
481
|
+
if node.postprocess then
|
482
|
+
# redirect the tangler
|
483
|
+
outer_sink = sink
|
484
|
+
inner_sport = StringIO.new
|
485
|
+
sink = Fabricator::Tangling_Sink.new '(pipe)',
|
486
|
+
inner_sport
|
487
|
+
end
|
488
|
+
sink.pin_indent node.clearindent ? 0 : nil do
|
489
|
+
tangle_chunks cbn_entry, sink, trace,
|
490
|
+
node.vertical_separation == '.dense' ? 1 : 2
|
491
|
+
end
|
492
|
+
if node.postprocess then
|
493
|
+
# revert the redirect and apply the filter
|
494
|
+
sink.newline
|
495
|
+
filter_output =
|
496
|
+
Fabricator::POSTPROCESSES[node.postprocess].
|
497
|
+
call(inner_sport.string)
|
498
|
+
sink = outer_sink
|
499
|
+
sink.pin_indent node.clearindent ? 0 : nil do
|
500
|
+
sink.write_long filter_output
|
501
|
+
end
|
502
|
+
end
|
503
|
+
trace.delete name
|
504
|
+
end
|
505
|
+
end
|
506
|
+
return
|
507
|
+
end
|
508
|
+
|
509
|
+
def tangle_roots
|
510
|
+
return if @output.tangles
|
511
|
+
@output.tangles = {}
|
512
|
+
@output.roots.each do |name|
|
513
|
+
sport = StringIO.new
|
514
|
+
sink = Fabricator::Tangling_Sink.new name, sport
|
515
|
+
cbn_entry = @output.chunks_by_name[name]
|
516
|
+
# We can assume that [[cbn_entry]] is not [[nil]], for
|
517
|
+
# otherwise there wouldn't be a [[roots]] entry.
|
518
|
+
tangle_chunks cbn_entry, sink, Set.new([name])
|
519
|
+
sink.newline
|
520
|
+
@output.tangles[name] = OpenStruct.new(
|
521
|
+
filename: name,
|
522
|
+
root_type: cbn_entry.root_type,
|
523
|
+
content: sport.string,
|
524
|
+
line_count: sink.line_count,
|
525
|
+
nonblank_line_count: sink.nonblank_line_count,
|
526
|
+
longest_line_length: sink.longest_line_length,
|
527
|
+
)
|
528
|
+
end
|
529
|
+
return
|
530
|
+
end
|
531
|
+
|
532
|
+
attr_reader :section_count
|
533
|
+
end
|
534
|
+
|
535
|
+
class Markup_Parser_Stack < Array
|
536
|
+
def initialize suppress_modes = 0
|
537
|
+
super()
|
538
|
+
push OpenStruct.new(
|
539
|
+
content: [],
|
540
|
+
mode: Fabricator::MF::DEFAULTS & ~suppress_modes,
|
541
|
+
term_type: 0,
|
542
|
+
)
|
543
|
+
return
|
544
|
+
end
|
545
|
+
|
546
|
+
def spawn face, start_flag, end_flag
|
547
|
+
self.push OpenStruct.new(
|
548
|
+
face: face,
|
549
|
+
content: [],
|
550
|
+
mode: self.last.mode & ~start_flag | end_flag,
|
551
|
+
term_type: end_flag,
|
552
|
+
)
|
553
|
+
return
|
554
|
+
end
|
555
|
+
|
556
|
+
def unspawn
|
557
|
+
raise 'assertion failed' unless length >= 2
|
558
|
+
top = self.pop
|
559
|
+
self.last.content.push OpenStruct.new(
|
560
|
+
type: :plain,
|
561
|
+
data: top.face,
|
562
|
+
), *top.content
|
563
|
+
return
|
564
|
+
end
|
565
|
+
|
566
|
+
def ennode node_type, frame_type
|
567
|
+
while self.last.term_type != frame_type do
|
568
|
+
self.unspawn
|
569
|
+
end
|
570
|
+
top = self.pop
|
571
|
+
node = OpenStruct.new(
|
572
|
+
type: node_type,
|
573
|
+
content: top.content,
|
574
|
+
)
|
575
|
+
self.last.content.push node
|
576
|
+
return node # for possible further manipulation
|
577
|
+
end
|
578
|
+
|
579
|
+
def cancel_link
|
580
|
+
i = self.length
|
581
|
+
begin
|
582
|
+
i -= 1
|
583
|
+
self[i].mode &= ~Fabricator::MF::END_LINK
|
584
|
+
self[i].mode |= Fabricator::MF::LINK
|
585
|
+
end until self[i].term_type == Fabricator::MF::END_LINK
|
586
|
+
self[i].term_type = 0
|
587
|
+
return
|
588
|
+
end
|
589
|
+
end
|
590
|
+
|
591
|
+
module MF
|
592
|
+
BOLD = 0x01
|
593
|
+
END_BOLD = 0x02
|
594
|
+
ITALIC = 0x04
|
595
|
+
END_ITALIC = 0x08
|
596
|
+
UNDERSCORE = 0x10
|
597
|
+
END_UNDERSCORE = 0x20
|
598
|
+
LINK = 0x40
|
599
|
+
END_LINK = 0x80
|
600
|
+
|
601
|
+
DEFAULTS = BOLD | ITALIC | UNDERSCORE | LINK
|
602
|
+
end
|
603
|
+
|
604
|
+
class Pointered_String < String
|
605
|
+
def initialize value
|
606
|
+
super value
|
607
|
+
@pointer = 0
|
608
|
+
return
|
609
|
+
end
|
610
|
+
|
611
|
+
attr_accessor :pointer
|
612
|
+
|
613
|
+
def biu_starter? c
|
614
|
+
return char_ahead == c &&
|
615
|
+
char_ahead(-1) != c &&
|
616
|
+
![?\s, c].include?(char_ahead(1))
|
617
|
+
end
|
618
|
+
|
619
|
+
def biu_terminator? c
|
620
|
+
return char_ahead == c &&
|
621
|
+
char_ahead(1) != c &&
|
622
|
+
![?\s, c].include?(char_ahead(-1))
|
623
|
+
end
|
624
|
+
|
625
|
+
def ahead length
|
626
|
+
return self[@pointer, length]
|
627
|
+
end
|
628
|
+
|
629
|
+
def char_ahead delta = 0
|
630
|
+
offset = @pointer + delta
|
631
|
+
return offset >= 0 ? self[offset] : nil
|
632
|
+
end
|
633
|
+
|
634
|
+
def at? etalon
|
635
|
+
return ahead(etalon.length) == etalon
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
639
|
+
class Markup_Constructor < Array
|
640
|
+
def node type, **attr
|
641
|
+
return push(OpenStruct.new(type: type, **attr))
|
642
|
+
# [[Array#push]] will return self, allowing [[node]] calls
|
643
|
+
# to be chained.
|
644
|
+
end
|
645
|
+
|
646
|
+
def plain data
|
647
|
+
return node(:plain, data: data)
|
648
|
+
end
|
649
|
+
|
650
|
+
def space data = nil
|
651
|
+
return node(:space, data: data)
|
652
|
+
end
|
653
|
+
|
654
|
+
def words s
|
655
|
+
s.split(/(\s+)/, -1).each_with_index do |part, i|
|
656
|
+
node(i.even? ? :plain : :space, data: part)
|
657
|
+
end
|
658
|
+
return self
|
659
|
+
end
|
660
|
+
end
|
661
|
+
|
662
|
+
POSTPROCESSES = {
|
663
|
+
'|scss->css' => proc do |input|
|
664
|
+
require 'sass'
|
665
|
+
Sass::Engine.new(input,
|
666
|
+
syntax: :scss,
|
667
|
+
load_paths: [],
|
668
|
+
filename: '(pipe)').render
|
669
|
+
end,
|
670
|
+
|
671
|
+
'|sass->css' => proc do |input|
|
672
|
+
require 'sass'
|
673
|
+
Sass::Engine.new(input,
|
674
|
+
syntax: :sass,
|
675
|
+
load_paths: [],
|
676
|
+
filename: '(pipe)').render
|
677
|
+
end,
|
678
|
+
}
|
679
|
+
|
680
|
+
WINDOWS_HOSTED_P =
|
681
|
+
(RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/)
|
682
|
+
|
683
|
+
class Tangling_Sink
|
684
|
+
def initialize filename, port
|
685
|
+
super()
|
686
|
+
@filename = filename
|
687
|
+
@port = port
|
688
|
+
@lineno = 1
|
689
|
+
@line = ''
|
690
|
+
@indent = 0
|
691
|
+
@nonblank_line_count = 0
|
692
|
+
|
693
|
+
@longest_line_length = 0
|
694
|
+
return
|
695
|
+
end
|
696
|
+
|
697
|
+
def write s
|
698
|
+
@line << s
|
699
|
+
return
|
700
|
+
end
|
701
|
+
|
702
|
+
def newline
|
703
|
+
@line.rstrip!
|
704
|
+
@port.puts @line
|
705
|
+
@lineno += 1
|
706
|
+
@nonblank_line_count += 1 unless @line.empty?
|
707
|
+
|
708
|
+
@longest_line_length = @line.length \
|
709
|
+
if @line.length > @longest_line_length
|
710
|
+
@line = ' ' * @indent
|
711
|
+
return
|
712
|
+
end
|
713
|
+
|
714
|
+
def pin_indent level = nil
|
715
|
+
previous_indent = @indent
|
716
|
+
begin
|
717
|
+
@indent = level || @line.length
|
718
|
+
yield
|
719
|
+
ensure
|
720
|
+
@indent = previous_indent
|
721
|
+
end
|
722
|
+
return
|
723
|
+
end
|
724
|
+
|
725
|
+
def write_long s
|
726
|
+
s.split(/\n/).each_with_index do |line, i|
|
727
|
+
newline unless i.zero?
|
728
|
+
write line
|
729
|
+
end
|
730
|
+
return
|
731
|
+
end
|
732
|
+
|
733
|
+
def location_ahead
|
734
|
+
return OpenStruct.new(
|
735
|
+
filename: @filename,
|
736
|
+
line: @lineno,
|
737
|
+
column: @line.length + 1)
|
738
|
+
end
|
739
|
+
|
740
|
+
def location_behind
|
741
|
+
return OpenStruct.new(
|
742
|
+
filename: @filename,
|
743
|
+
line: @lineno,
|
744
|
+
column: @line.length)
|
745
|
+
end
|
746
|
+
|
747
|
+
def line_count
|
748
|
+
return @lineno - 1
|
749
|
+
end
|
750
|
+
|
751
|
+
attr_reader :nonblank_line_count
|
752
|
+
|
753
|
+
attr_reader :longest_line_length
|
754
|
+
end
|
755
|
+
|
756
|
+
class Text_Wrapper
|
757
|
+
def initialize port = $stdout,
|
758
|
+
width: 80,
|
759
|
+
pseudographics: UNICODE_PSEUDOGRAPHICS,
|
760
|
+
palette: DEFAULT_PALETTE
|
761
|
+
super()
|
762
|
+
@port = port
|
763
|
+
@width = width
|
764
|
+
@pseudographics = pseudographics
|
765
|
+
@palette = palette
|
766
|
+
@hangindent = 0
|
767
|
+
@curpos = 0
|
768
|
+
@curspace = nil
|
769
|
+
@curword = OpenStruct.new(
|
770
|
+
prepared_output: '',
|
771
|
+
width: 0)
|
772
|
+
@curmode = @palette.null
|
773
|
+
return
|
774
|
+
end
|
775
|
+
|
776
|
+
def add_plain data
|
777
|
+
if @curspace and @curpos + data.length > @width then
|
778
|
+
# the space becomes a linebreak
|
779
|
+
@port.puts @palette.null
|
780
|
+
@port.print ' ' * @hangindent + @curmode
|
781
|
+
@curspace = nil
|
782
|
+
@curpos = @hangindent + @curword.width
|
783
|
+
end
|
784
|
+
@curword.prepared_output << data
|
785
|
+
@curpos += data.length
|
786
|
+
return
|
787
|
+
end
|
788
|
+
|
789
|
+
def add_space data = ' '
|
790
|
+
@port.print @curspace.prepared_output if @curspace
|
791
|
+
@port.print @curword.prepared_output
|
792
|
+
@curspace = OpenStruct.new(
|
793
|
+
prepared_output: data,
|
794
|
+
width: data.length)
|
795
|
+
@curword = OpenStruct.new(
|
796
|
+
prepared_output: '',
|
797
|
+
width: 0)
|
798
|
+
@curpos += data.length
|
799
|
+
return
|
800
|
+
end
|
801
|
+
|
802
|
+
def linebreak
|
803
|
+
@port.print @curspace.prepared_output if @curspace
|
804
|
+
@port.print @curword.prepared_output
|
805
|
+
@port.puts @palette.null
|
806
|
+
@port.print ' ' * @hangindent + @curmode
|
807
|
+
@curspace = nil
|
808
|
+
@curword = OpenStruct.new(
|
809
|
+
prepared_output: '',
|
810
|
+
width: 0)
|
811
|
+
@curpos = @hangindent
|
812
|
+
return
|
813
|
+
end
|
814
|
+
|
815
|
+
def add_node node
|
816
|
+
case node.type
|
817
|
+
when :plain then
|
818
|
+
add_plain node.data
|
819
|
+
when :space then
|
820
|
+
add_space node.data || ' '
|
821
|
+
when :nbsp then
|
822
|
+
add_plain ' '
|
823
|
+
when :monospace, :bold, :italic, :underscore then
|
824
|
+
styled node.type do
|
825
|
+
add_nodes node.content
|
826
|
+
end
|
827
|
+
when :mention_chunk then
|
828
|
+
add_pseudographics :before_chunk_name
|
829
|
+
add_nodes(
|
830
|
+
Fabricator.parse_markup(node.name,
|
831
|
+
Fabricator::MF::LINK))
|
832
|
+
add_pseudographics :after_chunk_name
|
833
|
+
when :link then
|
834
|
+
if node.implicit_face then
|
835
|
+
styled :link do
|
836
|
+
add_plain '<'
|
837
|
+
add_nodes node.content
|
838
|
+
add_plain '>'
|
839
|
+
end
|
840
|
+
else
|
841
|
+
add_plain '<'
|
842
|
+
add_nodes node.content
|
843
|
+
unless node.implicit_face then
|
844
|
+
add_space ' '
|
845
|
+
styled :link do
|
846
|
+
add_plain node.target
|
847
|
+
end
|
848
|
+
end
|
849
|
+
add_plain '>'
|
850
|
+
end
|
851
|
+
else
|
852
|
+
# Uh-oh, a bug: the parser generated a node of a type
|
853
|
+
# unknown to the weaver.
|
854
|
+
raise 'invalid node type'
|
855
|
+
end
|
856
|
+
return
|
857
|
+
end
|
858
|
+
|
859
|
+
def add_nodes nodes
|
860
|
+
nodes.each do |node|
|
861
|
+
add_node node
|
862
|
+
end
|
863
|
+
return
|
864
|
+
end
|
865
|
+
|
866
|
+
def hang
|
867
|
+
# convert the preceding whitespace, if any, into 'hard'
|
868
|
+
# space not subject to future wrapping
|
869
|
+
if @curspace then
|
870
|
+
@port.print @curspace.prepared_output
|
871
|
+
@curspace = nil
|
872
|
+
end
|
873
|
+
prev_hangindent = @hangindent
|
874
|
+
begin
|
875
|
+
@hangindent = @curpos
|
876
|
+
yield
|
877
|
+
ensure
|
878
|
+
@hangindent = prev_hangindent
|
879
|
+
end
|
880
|
+
return
|
881
|
+
end
|
882
|
+
|
883
|
+
def styled sequence_name
|
884
|
+
sequence = @palette[sequence_name]
|
885
|
+
raise 'unknown palette entry' unless sequence
|
886
|
+
prev_mode = @curmode
|
887
|
+
begin
|
888
|
+
@curmode = sequence
|
889
|
+
@curword.prepared_output << sequence
|
890
|
+
yield
|
891
|
+
ensure
|
892
|
+
@curmode = prev_mode
|
893
|
+
@curword.prepared_output << prev_mode
|
894
|
+
end
|
895
|
+
return
|
896
|
+
end
|
897
|
+
|
898
|
+
def add_pseudographics name
|
899
|
+
seq = @pseudographics[name]
|
900
|
+
raise 'unknown pseudographics item' unless seq
|
901
|
+
add_plain seq
|
902
|
+
return
|
903
|
+
end
|
904
|
+
end
|
905
|
+
|
906
|
+
UNICODE_PSEUDOGRAPHICS = OpenStruct.new(
|
907
|
+
bullet: [0x2022].pack('U*'),
|
908
|
+
before_chunk_name: [0x00AB].pack('U*'),
|
909
|
+
after_chunk_name: [0x00BB].pack('U*'),
|
910
|
+
initial_chunk_margin: [0x2500, 0x2510].pack('U*'),
|
911
|
+
chunk_margin: [0x0020, 0x2502].pack('U*'),
|
912
|
+
block_margin: " ",
|
913
|
+
final_chunk_marker:
|
914
|
+
([0x0020, 0x2514] + [0x2500] * 3).pack('U*'),
|
915
|
+
)
|
916
|
+
|
917
|
+
ASCII_PSEUDOGRAPHICS = OpenStruct.new(
|
918
|
+
bullet: "-",
|
919
|
+
before_chunk_name: "<<",
|
920
|
+
after_chunk_name: ">>",
|
921
|
+
initial_chunk_margin: "+ ",
|
922
|
+
chunk_margin: "| ",
|
923
|
+
block_margin: " ",
|
924
|
+
final_chunk_marker: "----",
|
925
|
+
)
|
926
|
+
|
927
|
+
DEFAULT_PALETTE = OpenStruct.new(
|
928
|
+
monospace: "\e[38;5;71m",
|
929
|
+
bold: "\e[1m",
|
930
|
+
italic: "\e[3m",
|
931
|
+
underscore: "\e[4m",
|
932
|
+
root_type: "\e[4m",
|
933
|
+
chunk_frame: "\e[38;5;59m",
|
934
|
+
block_frame: "",
|
935
|
+
chunk_xref: "\e[38;5;59;3m",
|
936
|
+
section_title: "\e[1;48;5;17m",
|
937
|
+
# unspecified intense on dark blue background
|
938
|
+
rubric: "\e[31;1m",
|
939
|
+
section_number: "\e[0;1m",
|
940
|
+
chunk_header: "\e[0;33;1m",
|
941
|
+
use: "\e[34;1m",
|
942
|
+
null: "\e[0m",
|
943
|
+
inline_warning: "\e[31m",
|
944
|
+
link: "\e[38;5;32m",
|
945
|
+
)
|
946
|
+
|
947
|
+
MARKUP2HTML = {
|
948
|
+
:monospace => 'code',
|
949
|
+
:bold => 'b',
|
950
|
+
:italic => 'i',
|
951
|
+
:underscore => 'u',
|
952
|
+
}
|
953
|
+
end
|
954
|
+
|
955
|
+
class << Fabricator
|
956
|
+
def show_warnings fabric
|
957
|
+
fabric.warnings.each do |warning|
|
958
|
+
$stderr.puts "%s: %s" %
|
959
|
+
[format_location(warning.loc), warning.message]
|
960
|
+
end
|
961
|
+
return
|
962
|
+
end
|
963
|
+
|
964
|
+
def format_location h
|
965
|
+
if h.column then
|
966
|
+
return "%s:%i.%i" % [h.filename, h.line, h.column]
|
967
|
+
else
|
968
|
+
return "%s:%i" % [h.filename, h.line]
|
969
|
+
end
|
970
|
+
end
|
971
|
+
|
972
|
+
def format_location_range h, dash: "-"
|
973
|
+
if h.from.filename != h.to.filename then
|
974
|
+
return format_location(h.from) + dash +
|
975
|
+
format_location(h.to)
|
976
|
+
else
|
977
|
+
if h.from.line != h.to.line then
|
978
|
+
result = h.from.filename + ":"
|
979
|
+
result << h.from.line.to_s
|
980
|
+
result << "." << h.from.column.to_s if h.from.column
|
981
|
+
result << dash
|
982
|
+
result << h.to.line.to_s
|
983
|
+
result << "." << h.to.column.to_s if h.to.column
|
984
|
+
else
|
985
|
+
result = h.from.filename + ":"
|
986
|
+
result << h.from.line.to_s
|
987
|
+
if h.from.column or h.to.column then
|
988
|
+
result << "." <<
|
989
|
+
h.from.column.to_s << dash << h.to.column.to_s
|
990
|
+
end
|
991
|
+
end
|
992
|
+
return result
|
993
|
+
end
|
994
|
+
end
|
995
|
+
|
996
|
+
def canonicalise_chunk_name raw_name
|
997
|
+
name = ''
|
998
|
+
raw_name.strip.split(/(\[\[.*?\]*\]\])/, -1).
|
999
|
+
each_with_index do |part, i|
|
1000
|
+
part.gsub! /\s+/, ' ' if i.even?
|
1001
|
+
name << part
|
1002
|
+
end
|
1003
|
+
return name
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
def parse_markup s, suppress_modes = 0
|
1007
|
+
ps = Fabricator::Pointered_String.new s
|
1008
|
+
stack = Fabricator::Markup_Parser_Stack.new suppress_modes
|
1009
|
+
while ps.pointer < s.length do
|
1010
|
+
if ps.at? "[[" and
|
1011
|
+
end_offset = s.index("]]", ps.pointer + 2) then
|
1012
|
+
while ps[end_offset + 2] == ?] do
|
1013
|
+
end_offset += 1
|
1014
|
+
end
|
1015
|
+
monospaced_content = []
|
1016
|
+
ps[ps.pointer + 2 ... end_offset].split(/(\s+)/).
|
1017
|
+
each_with_index do |part, i|
|
1018
|
+
monospaced_content.push OpenStruct.new(
|
1019
|
+
type: i.even? ? :plain : :space,
|
1020
|
+
data: part
|
1021
|
+
)
|
1022
|
+
end
|
1023
|
+
stack.last.content.push OpenStruct.new(
|
1024
|
+
type: :monospace,
|
1025
|
+
content: monospaced_content)
|
1026
|
+
ps.pointer = end_offset + 2
|
1027
|
+
|
1028
|
+
elsif stack.last.mode & Fabricator::MF::BOLD != 0 and
|
1029
|
+
ps.biu_starter? ?* then
|
1030
|
+
stack.spawn '*',
|
1031
|
+
Fabricator::MF::BOLD,
|
1032
|
+
Fabricator::MF::END_BOLD
|
1033
|
+
ps.pointer += 1
|
1034
|
+
|
1035
|
+
elsif stack.last.mode & Fabricator::MF::ITALIC != 0 and
|
1036
|
+
ps.biu_starter? ?/ then
|
1037
|
+
stack.spawn '/',
|
1038
|
+
Fabricator::MF::ITALIC,
|
1039
|
+
Fabricator::MF::END_ITALIC
|
1040
|
+
ps.pointer += 1
|
1041
|
+
|
1042
|
+
elsif stack.last.mode & Fabricator::MF::UNDERSCORE \
|
1043
|
+
!= 0 and
|
1044
|
+
ps.biu_starter? ?_ then
|
1045
|
+
stack.spawn '_',
|
1046
|
+
Fabricator::MF::UNDERSCORE,
|
1047
|
+
Fabricator::MF::END_UNDERSCORE
|
1048
|
+
ps.pointer += 1
|
1049
|
+
|
1050
|
+
elsif stack.last.mode & Fabricator::MF::END_BOLD != 0 and
|
1051
|
+
ps.biu_terminator? ?* then
|
1052
|
+
stack.ennode :bold, Fabricator::MF::END_BOLD
|
1053
|
+
ps.pointer += 1
|
1054
|
+
|
1055
|
+
elsif stack.last.mode & Fabricator::MF::END_ITALIC \
|
1056
|
+
!= 0 and
|
1057
|
+
ps.biu_terminator? ?/ then
|
1058
|
+
stack.ennode :italic, Fabricator::MF::END_ITALIC
|
1059
|
+
ps.pointer += 1
|
1060
|
+
|
1061
|
+
elsif stack.last.mode & Fabricator::MF::END_UNDERSCORE \
|
1062
|
+
!= 0 and
|
1063
|
+
ps.biu_terminator? ?_ then
|
1064
|
+
stack.ennode :underscore, Fabricator::MF::END_UNDERSCORE
|
1065
|
+
ps.pointer += 1
|
1066
|
+
|
1067
|
+
elsif stack.last.mode & Fabricator::MF::LINK != 0 and
|
1068
|
+
ps.biu_starter? ?< then
|
1069
|
+
stack.spawn '<',
|
1070
|
+
Fabricator::MF::LINK,
|
1071
|
+
Fabricator::MF::END_LINK
|
1072
|
+
stack.last.start_offset = ps.pointer
|
1073
|
+
ps.pointer += 1
|
1074
|
+
|
1075
|
+
elsif stack.last.mode & Fabricator::MF::END_LINK != 0 and
|
1076
|
+
ps.at? '|' and
|
1077
|
+
end_offset = s.index(?>, ps.pointer + 1) then
|
1078
|
+
target = ps[ps.pointer + 1 ... end_offset]
|
1079
|
+
if link_like? target then
|
1080
|
+
stack.ennode(:link,
|
1081
|
+
Fabricator::MF::END_LINK).target = target
|
1082
|
+
ps.pointer = end_offset + 1
|
1083
|
+
else
|
1084
|
+
# False alarm: this is not a link, after all.
|
1085
|
+
stack.cancel_link
|
1086
|
+
stack.last.content.push OpenStruct.new(
|
1087
|
+
type: :plain,
|
1088
|
+
data: '|',
|
1089
|
+
)
|
1090
|
+
ps.pointer += 1
|
1091
|
+
end
|
1092
|
+
|
1093
|
+
elsif stack.last.mode & Fabricator::MF::END_LINK != 0 and
|
1094
|
+
ps.at? '>' then
|
1095
|
+
j = stack.rindex do |x|
|
1096
|
+
x.term_type == Fabricator::MF::END_LINK
|
1097
|
+
end
|
1098
|
+
target = ps[stack[j].start_offset + 1 ... ps.pointer]
|
1099
|
+
if link_like? target then
|
1100
|
+
stack[j .. -1] = []
|
1101
|
+
stack.last.content.push OpenStruct.new(
|
1102
|
+
type: :link,
|
1103
|
+
implicit_face: true,
|
1104
|
+
target: target,
|
1105
|
+
content: [OpenStruct.new(
|
1106
|
+
type: :plain,
|
1107
|
+
data: target,
|
1108
|
+
)],
|
1109
|
+
)
|
1110
|
+
else
|
1111
|
+
# False alarm: this is not a link, after all.
|
1112
|
+
stack.cancel_link
|
1113
|
+
stack.last.content.push OpenStruct.new(
|
1114
|
+
type: :plain,
|
1115
|
+
data: '>',
|
1116
|
+
)
|
1117
|
+
end
|
1118
|
+
ps.pointer += 1
|
1119
|
+
|
1120
|
+
elsif ps.at? ' ' then
|
1121
|
+
ps.pointer += 1
|
1122
|
+
while ps.at? ' ' do
|
1123
|
+
ps.pointer += 1
|
1124
|
+
end
|
1125
|
+
stack.last.content.push OpenStruct.new(type: :space)
|
1126
|
+
|
1127
|
+
elsif ps.at? "\u00A0" then
|
1128
|
+
stack.last.content.push OpenStruct.new(type: :nbsp)
|
1129
|
+
ps.pointer += 1
|
1130
|
+
|
1131
|
+
else
|
1132
|
+
j = ps.pointer + 1
|
1133
|
+
while j < s.length and !" */<>[_|".include? ps[j] do
|
1134
|
+
j += 1
|
1135
|
+
end
|
1136
|
+
stack.last.content.push OpenStruct.new(
|
1137
|
+
type: :plain,
|
1138
|
+
data: String.new(ps[ps.pointer ... j]),
|
1139
|
+
)
|
1140
|
+
ps.pointer = j
|
1141
|
+
end
|
1142
|
+
end
|
1143
|
+
while stack.length > 1 do
|
1144
|
+
stack.unspawn
|
1145
|
+
end
|
1146
|
+
return stack.last.content
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
def link_like? s
|
1150
|
+
return !!(s =~ /\A(?:#\s*)?[[:alnum:]]/)
|
1151
|
+
end
|
1152
|
+
|
1153
|
+
def markup
|
1154
|
+
return Fabricator::Markup_Constructor.new
|
1155
|
+
end
|
1156
|
+
|
1157
|
+
# Take a [[results]] record from tangling and construct a
|
1158
|
+
# matching [[proc]] to be stored in the [[writeout_plan]].
|
1159
|
+
def plan_to_write_out results
|
1160
|
+
return proc do |output_filename|
|
1161
|
+
File.write output_filename, results.content
|
1162
|
+
puts "Tangled #{results.filename},"
|
1163
|
+
if results.line_count != 1 then
|
1164
|
+
print " #{results.line_count} lines"
|
1165
|
+
else
|
1166
|
+
print " #{results.line_count} line"
|
1167
|
+
end
|
1168
|
+
puts " (#{results.nonblank_line_count} non-blank),"
|
1169
|
+
if results.longest_line_length != 1 then
|
1170
|
+
puts " longest #{results.longest_line_length} chars."
|
1171
|
+
else
|
1172
|
+
puts " longest #{results.longest_line_length} char."
|
1173
|
+
end
|
1174
|
+
if results.root_type == '.script' and
|
1175
|
+
!Fabricator::WINDOWS_HOSTED_P then
|
1176
|
+
stat = File.stat output_filename
|
1177
|
+
m = stat.mode
|
1178
|
+
uc = ""
|
1179
|
+
[(m |= 0o100), (uc << "u")] if m & 0o400 != 0
|
1180
|
+
[(m |= 0o010), (uc << "g")] if m & 0o040 != 0
|
1181
|
+
[(m |= 0o001), (uc << "o")] if m & 0o004 != 0
|
1182
|
+
File.chmod m, output_filename
|
1183
|
+
puts "Set %s+x on %s, resulting in %03o" % [
|
1184
|
+
uc,
|
1185
|
+
output_filename,
|
1186
|
+
m & 0o777,
|
1187
|
+
]
|
1188
|
+
end
|
1189
|
+
end
|
1190
|
+
end
|
1191
|
+
|
1192
|
+
def load_fabric input, chunk_size_limit: 24
|
1193
|
+
vp = Fabricator::Vertical_Peeker.new input
|
1194
|
+
integrator = Fabricator::Integrator.new
|
1195
|
+
|
1196
|
+
in_list = false
|
1197
|
+
loop do
|
1198
|
+
vertical_separation = 0
|
1199
|
+
while vp.peek_line == '' do
|
1200
|
+
if vertical_separation == 2 then
|
1201
|
+
integrator.warn vp.location_ahead,
|
1202
|
+
"more than two consecutive blank lines"
|
1203
|
+
end
|
1204
|
+
vertical_separation += 1
|
1205
|
+
vp.get_line
|
1206
|
+
end
|
1207
|
+
break if vp.eof?
|
1208
|
+
if vertical_separation >= 2 then
|
1209
|
+
integrator.force_section_break
|
1210
|
+
in_list = false
|
1211
|
+
end
|
1212
|
+
element_location = vp.location_ahead
|
1213
|
+
case vp.peek_line
|
1214
|
+
when /^\s+/ then
|
1215
|
+
if !in_list or
|
1216
|
+
vp.peek_line !~ /^
|
1217
|
+
(?<margin> \s+ )
|
1218
|
+
- (?<separator> \s+ )
|
1219
|
+
/x then
|
1220
|
+
body_location = vp.location_ahead
|
1221
|
+
element = vp.get_indented_lines_with_skip
|
1222
|
+
element.type = :block
|
1223
|
+
element.body_loc = element_location
|
1224
|
+
else
|
1225
|
+
margin = $~['margin']
|
1226
|
+
lines = [$~['separator'] + $']
|
1227
|
+
vp.get_line
|
1228
|
+
while !vp.eof? and
|
1229
|
+
vp.peek_line.start_with? margin and
|
1230
|
+
vp.peek_line !~ /^\s*-\s/ do
|
1231
|
+
lines.push vp.get_line[margin.length .. -1]
|
1232
|
+
end
|
1233
|
+
element = OpenStruct.new(
|
1234
|
+
type: :item,
|
1235
|
+
lines: lines,
|
1236
|
+
content: parse_markup(lines.map(&:strip).join ' '),
|
1237
|
+
indent: margin.length,
|
1238
|
+
loc: element_location)
|
1239
|
+
end
|
1240
|
+
|
1241
|
+
when /^<<\s*
|
1242
|
+
(?: (?<root-type> \.file|\.script)\s+ )?
|
1243
|
+
(?<raw-name> [^\s].*?)
|
1244
|
+
\s*>>:$/x then
|
1245
|
+
name = canonicalise_chunk_name $~['raw-name']
|
1246
|
+
vp.get_line
|
1247
|
+
element = OpenStruct.new(
|
1248
|
+
type: :divert,
|
1249
|
+
root_type: $~['root-type'],
|
1250
|
+
name: name,
|
1251
|
+
header_loc: element_location)
|
1252
|
+
|
1253
|
+
body_location = vp.location_ahead
|
1254
|
+
body = vp.get_indented_lines_with_skip
|
1255
|
+
if body then
|
1256
|
+
element.type = :chunk
|
1257
|
+
element.lines = body.lines
|
1258
|
+
element.indent = body.indent
|
1259
|
+
element.body_loc = body_location
|
1260
|
+
element.initial = element.final = true
|
1261
|
+
end
|
1262
|
+
|
1263
|
+
when /^-\s/ then
|
1264
|
+
# We'll discard the leading dash but save the following
|
1265
|
+
# whitespace.
|
1266
|
+
lines = [vp.get_line[1 .. -1]]
|
1267
|
+
while !vp.eof? and
|
1268
|
+
vp.peek_line != '' and
|
1269
|
+
vp.peek_line !~ /^\s*-\s/ do
|
1270
|
+
lines.push vp.get_line
|
1271
|
+
end
|
1272
|
+
element = OpenStruct.new(
|
1273
|
+
type: :item,
|
1274
|
+
lines: lines,
|
1275
|
+
content: parse_markup(lines.map(&:strip).join ' '),
|
1276
|
+
indent: 0,
|
1277
|
+
loc: element_location)
|
1278
|
+
|
1279
|
+
when /^[^\s]/ then
|
1280
|
+
lines = []
|
1281
|
+
while vp.peek_line =~ /^[^\s]/ and
|
1282
|
+
vp.peek_line !~ /^-\s/ do
|
1283
|
+
lines.push vp.get_line
|
1284
|
+
end
|
1285
|
+
mode_flags_to_suppress = 0
|
1286
|
+
case lines[0]
|
1287
|
+
when /^(==+)(\s+)/ then
|
1288
|
+
lines[0] = $2 + $'
|
1289
|
+
element = OpenStruct.new(
|
1290
|
+
type: :title,
|
1291
|
+
level: $1.length - 1,
|
1292
|
+
loc: element_location)
|
1293
|
+
mode_flags_to_suppress |= Fabricator::MF::LINK
|
1294
|
+
|
1295
|
+
when /^\*\s+/ then
|
1296
|
+
lines[0] = $'
|
1297
|
+
element = OpenStruct.new(
|
1298
|
+
type: :rubric,
|
1299
|
+
loc: element_location)
|
1300
|
+
|
1301
|
+
else
|
1302
|
+
element = OpenStruct.new(
|
1303
|
+
type: :paragraph,
|
1304
|
+
loc: element_location)
|
1305
|
+
end
|
1306
|
+
element.lines = lines
|
1307
|
+
element.content =
|
1308
|
+
parse_markup(lines.map(&:strip).join(' '),
|
1309
|
+
mode_flags_to_suppress)
|
1310
|
+
else raise 'assertion failed'
|
1311
|
+
end
|
1312
|
+
integrator.integrate element
|
1313
|
+
in_list = element.type == :item
|
1314
|
+
end
|
1315
|
+
integrator.clear_diversion
|
1316
|
+
|
1317
|
+
integrator.check_root_type_consistency
|
1318
|
+
integrator.check_chunk_sizes(chunk_size_limit)
|
1319
|
+
integrator.tangle_roots
|
1320
|
+
return integrator.output
|
1321
|
+
end
|
1322
|
+
|
1323
|
+
def weave_ctxt fabric, port,
|
1324
|
+
width: 80,
|
1325
|
+
pseudographics: Fabricator::UNICODE_PSEUDOGRAPHICS
|
1326
|
+
wr = Fabricator::Text_Wrapper.new port,
|
1327
|
+
width: width,
|
1328
|
+
pseudographics: pseudographics
|
1329
|
+
unless fabric.warnings.empty? then
|
1330
|
+
wr.styled :section_title do
|
1331
|
+
wr.add_plain 'Warnings'
|
1332
|
+
end
|
1333
|
+
wr.linebreak
|
1334
|
+
wr.linebreak
|
1335
|
+
weave_ctxt_warning_list fabric.warnings, wr
|
1336
|
+
wr.linebreak
|
1337
|
+
end
|
1338
|
+
toc_generated = false
|
1339
|
+
fabric.presentation.each do |element|
|
1340
|
+
case element.type
|
1341
|
+
when :title then
|
1342
|
+
if !toc_generated then
|
1343
|
+
weave_ctxt_toc fabric.toc, wr
|
1344
|
+
toc_generated = true
|
1345
|
+
end
|
1346
|
+
wr.styled :section_title do
|
1347
|
+
wr.add_plain "#{element.number}."
|
1348
|
+
wr.add_space
|
1349
|
+
wr.hang do
|
1350
|
+
wr.add_nodes element.content
|
1351
|
+
end
|
1352
|
+
end
|
1353
|
+
wr.linebreak
|
1354
|
+
wr.linebreak
|
1355
|
+
when :section then
|
1356
|
+
rubricated = element.elements[0].type == :rubric
|
1357
|
+
# If we're encountering the first rubric/title, output
|
1358
|
+
# the table of contents.
|
1359
|
+
if rubricated and !toc_generated then
|
1360
|
+
weave_ctxt_toc fabric.toc, wr
|
1361
|
+
toc_generated = true
|
1362
|
+
end
|
1363
|
+
|
1364
|
+
start_index = 0 # index of the first non-special child
|
1365
|
+
if rubricated then
|
1366
|
+
start_index += 1
|
1367
|
+
wr.styled :rubric do
|
1368
|
+
wr.add_plain "§%i." % element.section_number
|
1369
|
+
wr.add_space
|
1370
|
+
wr.add_nodes element.elements.first.content
|
1371
|
+
end
|
1372
|
+
else
|
1373
|
+
wr.styled :section_number do
|
1374
|
+
wr.add_plain "§%i." % element.section_number
|
1375
|
+
end
|
1376
|
+
end
|
1377
|
+
|
1378
|
+
# If the rubric or the section sign is followed by a
|
1379
|
+
# paragraph, a chunk header, or a divert, we'll output
|
1380
|
+
# it in the same paragraph.
|
1381
|
+
starter = element.elements[start_index]
|
1382
|
+
if starter then
|
1383
|
+
case starter.type
|
1384
|
+
when :paragraph, :divert, :chunk then
|
1385
|
+
wr.add_space
|
1386
|
+
weave_ctxt_section_part starter, fabric, wr
|
1387
|
+
start_index += 1
|
1388
|
+
else
|
1389
|
+
wr.linebreak
|
1390
|
+
end
|
1391
|
+
end
|
1392
|
+
|
1393
|
+
# Finally, the blank line that separates the special
|
1394
|
+
# paragraph from the section's body, if any.
|
1395
|
+
wr.linebreak
|
1396
|
+
|
1397
|
+
element.elements[start_index .. -1].each do |child|
|
1398
|
+
weave_ctxt_section_part child, fabric, wr
|
1399
|
+
wr.linebreak
|
1400
|
+
end
|
1401
|
+
|
1402
|
+
unless (element.warnings || []).empty? then
|
1403
|
+
weave_ctxt_warning_list element.warnings, wr,
|
1404
|
+
inline: true, indent: false
|
1405
|
+
wr.linebreak
|
1406
|
+
end
|
1407
|
+
else raise 'data structure error'
|
1408
|
+
end
|
1409
|
+
end
|
1410
|
+
return
|
1411
|
+
end
|
1412
|
+
|
1413
|
+
def weave_ctxt_warning_list list, wr, inline: false,
|
1414
|
+
indent: true
|
1415
|
+
list.to_a.each do |warning|
|
1416
|
+
wr.styled inline ? :inline_warning : :null do
|
1417
|
+
wr.add_plain (indent ? ' ' : '') + '!!! ' if inline
|
1418
|
+
wr.add_plain format_location(warning.loc)
|
1419
|
+
wr.add_plain ':'
|
1420
|
+
wr.add_space
|
1421
|
+
wr.hang do
|
1422
|
+
warning.message.split(/(\s+)/).
|
1423
|
+
each_with_index do |part, i|
|
1424
|
+
if i.even? then
|
1425
|
+
wr.add_plain part
|
1426
|
+
else
|
1427
|
+
wr.add_space part
|
1428
|
+
end
|
1429
|
+
end
|
1430
|
+
end
|
1431
|
+
end
|
1432
|
+
wr.linebreak
|
1433
|
+
end
|
1434
|
+
return
|
1435
|
+
end
|
1436
|
+
|
1437
|
+
def weave_ctxt_section_part element, fabric, wr
|
1438
|
+
case element.type
|
1439
|
+
when :paragraph then
|
1440
|
+
wr.add_nodes element.content
|
1441
|
+
wr.linebreak
|
1442
|
+
|
1443
|
+
when :divert, :chunk, :diverted_chunk then
|
1444
|
+
if [:divert, :chunk].include? element.type then
|
1445
|
+
weave_ctxt_chunk_header element, wr
|
1446
|
+
weave_ctxt_warning_list element.warnings, wr,
|
1447
|
+
inline: true
|
1448
|
+
end
|
1449
|
+
if [:chunk, :diverted_chunk].include? element.type then
|
1450
|
+
wr.styled :chunk_frame do
|
1451
|
+
wr.add_pseudographics element.initial ?
|
1452
|
+
:initial_chunk_margin :
|
1453
|
+
:chunk_margin
|
1454
|
+
end
|
1455
|
+
wr.styled :monospace do
|
1456
|
+
element.content.each do |node|
|
1457
|
+
case node.type
|
1458
|
+
when :verbatim then
|
1459
|
+
wr.add_plain node.data
|
1460
|
+
when :newline then
|
1461
|
+
wr.linebreak
|
1462
|
+
wr.styled :chunk_frame do
|
1463
|
+
wr.add_pseudographics :chunk_margin
|
1464
|
+
end
|
1465
|
+
when :use then
|
1466
|
+
weave_ctxt_use node, wr
|
1467
|
+
else raise 'data structure error'
|
1468
|
+
end
|
1469
|
+
end
|
1470
|
+
end
|
1471
|
+
wr.linebreak
|
1472
|
+
if element.final then
|
1473
|
+
wr.styled :chunk_frame do
|
1474
|
+
wr.add_pseudographics :final_chunk_marker
|
1475
|
+
end
|
1476
|
+
wr.linebreak
|
1477
|
+
end
|
1478
|
+
weave_ctxt_warning_list element.warnings, wr,
|
1479
|
+
inline: true
|
1480
|
+
if element.final then
|
1481
|
+
wr.styled :chunk_xref do
|
1482
|
+
wr.add_nodes xref_chain(element, fabric)
|
1483
|
+
end
|
1484
|
+
wr.linebreak
|
1485
|
+
end
|
1486
|
+
end
|
1487
|
+
|
1488
|
+
when :list then
|
1489
|
+
weave_ctxt_list element.items, wr
|
1490
|
+
|
1491
|
+
when :block then
|
1492
|
+
weave_ctxt_block element, wr
|
1493
|
+
else
|
1494
|
+
raise 'data structure error'
|
1495
|
+
end
|
1496
|
+
return
|
1497
|
+
end
|
1498
|
+
|
1499
|
+
def weave_ctxt_chunk_header element, wr
|
1500
|
+
wr.styled :chunk_header do
|
1501
|
+
wr.add_pseudographics :before_chunk_name
|
1502
|
+
if element.root_type then
|
1503
|
+
wr.styled :root_type do
|
1504
|
+
wr.add_plain element.root_type
|
1505
|
+
end
|
1506
|
+
wr.add_space
|
1507
|
+
end
|
1508
|
+
wr.add_nodes(
|
1509
|
+
parse_markup(element.name, Fabricator::MF::LINK))
|
1510
|
+
wr.add_pseudographics :after_chunk_name
|
1511
|
+
wr.add_plain ":"
|
1512
|
+
end
|
1513
|
+
wr.linebreak
|
1514
|
+
return
|
1515
|
+
end
|
1516
|
+
|
1517
|
+
def weave_ctxt_block element, wr
|
1518
|
+
element.lines.each do |line|
|
1519
|
+
wr.styled :block_frame do
|
1520
|
+
wr.add_pseudographics :block_margin
|
1521
|
+
end
|
1522
|
+
wr.styled :monospace do
|
1523
|
+
wr.add_plain line
|
1524
|
+
end
|
1525
|
+
wr.linebreak
|
1526
|
+
end
|
1527
|
+
return
|
1528
|
+
end
|
1529
|
+
|
1530
|
+
def weave_ctxt_use node, wr
|
1531
|
+
wr.styled :use do
|
1532
|
+
wr.add_pseudographics :before_chunk_name
|
1533
|
+
if node.clearindent then
|
1534
|
+
wr.add_plain ".clearindent "
|
1535
|
+
end
|
1536
|
+
wr.add_nodes parse_markup(node.name, Fabricator::MF::LINK)
|
1537
|
+
if node.vertical_separation then
|
1538
|
+
wr.add_plain " " + node.vertical_separation
|
1539
|
+
end
|
1540
|
+
if node.postprocess then
|
1541
|
+
wr.add_plain " " + node.postprocess
|
1542
|
+
end
|
1543
|
+
wr.add_pseudographics :after_chunk_name
|
1544
|
+
end
|
1545
|
+
return
|
1546
|
+
end
|
1547
|
+
|
1548
|
+
# Given a chunk, prepare its transclusion summary as a list of
|
1549
|
+
# markup nodes. Should only be used on chunks that are the
|
1550
|
+
# last in a chunk chain (i.e., that have [[final]] set).
|
1551
|
+
def xref_chain element, fabric, dash: "-"
|
1552
|
+
xref = markup
|
1553
|
+
if element.initial then
|
1554
|
+
xref.words "This chunk is "
|
1555
|
+
else
|
1556
|
+
xref.words "These chunks are "
|
1557
|
+
end
|
1558
|
+
cbn_entry = fabric.chunks_by_name[element.name]
|
1559
|
+
transcluders = cbn_entry.transcluders
|
1560
|
+
if transcluders then
|
1561
|
+
xref.words "transcluded by "
|
1562
|
+
xref.push *commatise_oxfordly(
|
1563
|
+
transcluders.map{|ref| markup.
|
1564
|
+
node(:mention_chunk, name: ref.name).
|
1565
|
+
space.
|
1566
|
+
plain("(§%i)" % ref.section_number)
|
1567
|
+
})
|
1568
|
+
else
|
1569
|
+
if cbn_entry.root_type then
|
1570
|
+
xref.words "solely a transclusion root"
|
1571
|
+
else
|
1572
|
+
xref.words "never transcluded"
|
1573
|
+
end
|
1574
|
+
end
|
1575
|
+
xref.words " and "
|
1576
|
+
tlocs = element.divert ?
|
1577
|
+
element.divert.chain_tangle_locs :
|
1578
|
+
element.tangle_locs
|
1579
|
+
if tlocs then
|
1580
|
+
xref.
|
1581
|
+
words("tangled to ").
|
1582
|
+
push(*commatise_oxfordly(
|
1583
|
+
tlocs.map{|range| markup.
|
1584
|
+
plain(format_location_range(range, dash: dash))
|
1585
|
+
})).
|
1586
|
+
plain(".")
|
1587
|
+
else
|
1588
|
+
xref.words "never tangled."
|
1589
|
+
end
|
1590
|
+
return xref
|
1591
|
+
end
|
1592
|
+
|
1593
|
+
def commatise_oxfordly items
|
1594
|
+
result = []
|
1595
|
+
items.each_with_index do |item, i|
|
1596
|
+
unless i.zero? then
|
1597
|
+
unless items.length == 2 then
|
1598
|
+
result.push OpenStruct.new(:type => :plain,
|
1599
|
+
:data => ',')
|
1600
|
+
end
|
1601
|
+
result.push OpenStruct.new(:type => :space)
|
1602
|
+
if i == items.length - 1 then
|
1603
|
+
result.push OpenStruct.new(:type => :plain,
|
1604
|
+
:data => 'and')
|
1605
|
+
result.push OpenStruct.new(:type => :space)
|
1606
|
+
end
|
1607
|
+
end
|
1608
|
+
result.push *item
|
1609
|
+
end
|
1610
|
+
return result
|
1611
|
+
end
|
1612
|
+
|
1613
|
+
def weave_ctxt_list items, wr
|
1614
|
+
items.each do |item|
|
1615
|
+
wr.add_pseudographics :bullet
|
1616
|
+
wr.add_plain " "
|
1617
|
+
wr.hang do
|
1618
|
+
wr.add_nodes item.content
|
1619
|
+
end
|
1620
|
+
wr.linebreak
|
1621
|
+
unless (item.warnings || []).empty? then
|
1622
|
+
wr.hang do
|
1623
|
+
weave_ctxt_warning_list item.warnings, wr,
|
1624
|
+
inline: true
|
1625
|
+
end
|
1626
|
+
end
|
1627
|
+
if item.sublist then
|
1628
|
+
wr.add_plain " "
|
1629
|
+
wr.hang do
|
1630
|
+
weave_ctxt_list item.sublist.items, wr
|
1631
|
+
end
|
1632
|
+
end
|
1633
|
+
end
|
1634
|
+
return
|
1635
|
+
end
|
1636
|
+
|
1637
|
+
def weave_ctxt_toc toc, wr
|
1638
|
+
if toc.length >= 2 then
|
1639
|
+
wr.styled :section_title do
|
1640
|
+
wr.add_plain 'Contents'
|
1641
|
+
end
|
1642
|
+
wr.linebreak; wr.linebreak
|
1643
|
+
rubric_level = 0
|
1644
|
+
toc.each do |entry|
|
1645
|
+
case entry.type
|
1646
|
+
when :title then
|
1647
|
+
rubric_level = entry.level - 1 + 1
|
1648
|
+
wr.add_plain ' ' * (entry.level - 1)
|
1649
|
+
wr.add_plain entry.number + '.'
|
1650
|
+
wr.add_space
|
1651
|
+
wr.hang do
|
1652
|
+
wr.add_nodes entry.content
|
1653
|
+
end
|
1654
|
+
|
1655
|
+
when :rubric then
|
1656
|
+
wr.add_plain ' ' * rubric_level
|
1657
|
+
wr.add_plain '§%i.' % entry.section_number
|
1658
|
+
wr.add_space
|
1659
|
+
wr.hang do
|
1660
|
+
wr.add_nodes entry.content
|
1661
|
+
end
|
1662
|
+
|
1663
|
+
else
|
1664
|
+
raise 'assertion failed'
|
1665
|
+
end
|
1666
|
+
wr.linebreak
|
1667
|
+
end
|
1668
|
+
wr.linebreak
|
1669
|
+
end
|
1670
|
+
return
|
1671
|
+
end
|
1672
|
+
|
1673
|
+
def weave_html fabric, port,
|
1674
|
+
title: nil,
|
1675
|
+
link_css: []
|
1676
|
+
title ||= "(Untitled)"
|
1677
|
+
port.puts '<!doctype html>'
|
1678
|
+
port.puts '<html>'
|
1679
|
+
port.puts '<head>'
|
1680
|
+
port.puts "<meta http-equiv='Content-type' " +
|
1681
|
+
"content='text/html; charset=utf-8' />"
|
1682
|
+
port.puts "<title>#{title.to_xml}</title>"
|
1683
|
+
if link_css.empty? then
|
1684
|
+
port.puts "<style type='text/css'>"
|
1685
|
+
port.puts '/**** Fonts ****/
|
1686
|
+
@import url("http://fonts.googleapis.com/css?family=Roboto");
|
1687
|
+
@import url("http://fonts.googleapis.com/css?family=Cousine");
|
1688
|
+
/**** Rules ****/
|
1689
|
+
body, .maui-transclude {
|
1690
|
+
font-family: "Roboto", sans-serif; }
|
1691
|
+
|
1692
|
+
pre, tt, code {
|
1693
|
+
font-family: "Cousine", monospace; }
|
1694
|
+
|
1695
|
+
body {
|
1696
|
+
colour: black;
|
1697
|
+
background: white; }
|
1698
|
+
|
1699
|
+
tt, code {
|
1700
|
+
color: forestgreen; }
|
1701
|
+
|
1702
|
+
.maui-inline-warnings {
|
1703
|
+
color: red; }
|
1704
|
+
|
1705
|
+
.maui-warnings tt {
|
1706
|
+
color: inherit; }
|
1707
|
+
|
1708
|
+
.maui-rubric {
|
1709
|
+
color: crimson; }
|
1710
|
+
|
1711
|
+
ul.maui-warnings {
|
1712
|
+
padding-left: 0; }
|
1713
|
+
ul.maui-warnings > li {
|
1714
|
+
list-style: none; }
|
1715
|
+
|
1716
|
+
.maui-chunk-body {
|
1717
|
+
margin-left: 20px;
|
1718
|
+
border-left: 2px solid #cccccc;
|
1719
|
+
padding-left: 5px; }
|
1720
|
+
|
1721
|
+
.maui-initial-chunk > .maui-chunk-body:before {
|
1722
|
+
content: "";
|
1723
|
+
display: block;
|
1724
|
+
width: 22px;
|
1725
|
+
border-top: solid 2px #cccccc;
|
1726
|
+
margin-left: -27px; }
|
1727
|
+
|
1728
|
+
.maui-final-chunk > .maui-chunk-body:after {
|
1729
|
+
content: "";
|
1730
|
+
display: block;
|
1731
|
+
margin-left: -7px;
|
1732
|
+
width: 40px;
|
1733
|
+
border-bottom: solid 2px #cccccc; }
|
1734
|
+
|
1735
|
+
.maui-chunk-body, .maui-chunk > .maui-warnings {
|
1736
|
+
margin-top: 0;
|
1737
|
+
margin-bottom: 0; }
|
1738
|
+
|
1739
|
+
.maui-chunk {
|
1740
|
+
margin-top: 16px;
|
1741
|
+
margin-bottom: 16px; }
|
1742
|
+
|
1743
|
+
.maui-chunk-xref {
|
1744
|
+
font-size: small;
|
1745
|
+
font-style: italic;
|
1746
|
+
margin-left: 22px; }
|
1747
|
+
|
1748
|
+
/* Backwards compatibility with pre-HTML5 browsers */
|
1749
|
+
section {
|
1750
|
+
display: block; }'
|
1751
|
+
port.puts "</style>"
|
1752
|
+
else
|
1753
|
+
link_css.each do |link|
|
1754
|
+
port.puts ("<link rel='stylesheet' type='text/css' " +
|
1755
|
+
"href='%s' />") % link.to_xml
|
1756
|
+
end
|
1757
|
+
end
|
1758
|
+
port.puts '</head>'
|
1759
|
+
port.puts '<body>'
|
1760
|
+
port.puts
|
1761
|
+
port.puts "<h1>#{title.to_xml}</h1>"
|
1762
|
+
unless fabric.warnings.empty? then
|
1763
|
+
port.puts "<h2>Warnings</h2>"
|
1764
|
+
port.puts
|
1765
|
+
weave_html_warning_list fabric.warnings, port
|
1766
|
+
port.puts
|
1767
|
+
end
|
1768
|
+
toc_generated = false
|
1769
|
+
fabric.presentation.each do |element|
|
1770
|
+
case element.type
|
1771
|
+
when :title then
|
1772
|
+
if !toc_generated then
|
1773
|
+
weave_html_toc fabric.toc, port
|
1774
|
+
toc_generated = true
|
1775
|
+
end
|
1776
|
+
port.print '<h%i' % (element.level + 1)
|
1777
|
+
port.print " id='%s'" % "T.#{element.number}"
|
1778
|
+
port.print '>'
|
1779
|
+
port.print "#{element.number}. "
|
1780
|
+
htmlify element.content, port
|
1781
|
+
port.puts '</h%i>' % (element.level + 1)
|
1782
|
+
when :section then
|
1783
|
+
rubricated = element.elements[0].type == :rubric
|
1784
|
+
# If we're encountering the first rubric/title, output
|
1785
|
+
# the table of contents.
|
1786
|
+
if rubricated and !toc_generated then
|
1787
|
+
weave_html_toc fabric.toc, port
|
1788
|
+
toc_generated = true
|
1789
|
+
end
|
1790
|
+
|
1791
|
+
start_index = 0
|
1792
|
+
port.puts "<section class='maui-section' id='%s'>" %
|
1793
|
+
"S.#{element.section_number}"
|
1794
|
+
port.puts
|
1795
|
+
port.print "<p>"
|
1796
|
+
port.print "<b class='%s'>" %
|
1797
|
+
(rubricated ? 'maui-rubric' : 'maui-section-number')
|
1798
|
+
port.print "\u00A7#{element.section_number}."
|
1799
|
+
if rubricated then
|
1800
|
+
port.print " "
|
1801
|
+
htmlify element.elements[start_index].content, port
|
1802
|
+
start_index += 1
|
1803
|
+
end
|
1804
|
+
port.print "</b>"
|
1805
|
+
subelement = element.elements[start_index]
|
1806
|
+
warnings = nil
|
1807
|
+
case subelement && subelement.type
|
1808
|
+
when :paragraph then
|
1809
|
+
port.print " "
|
1810
|
+
htmlify subelement.content, port
|
1811
|
+
start_index += 1
|
1812
|
+
when :divert then
|
1813
|
+
port.print " "
|
1814
|
+
weave_html_chunk_header subelement, 'maui-divert',
|
1815
|
+
port, tag: 'span'
|
1816
|
+
warnings = subelement.warnings
|
1817
|
+
start_index += 1
|
1818
|
+
end
|
1819
|
+
port.puts "</p>"
|
1820
|
+
if warnings then
|
1821
|
+
weave_html_warning_list warnings, port, inline: true
|
1822
|
+
end
|
1823
|
+
port.puts
|
1824
|
+
element.elements[start_index .. -1].each do |child|
|
1825
|
+
weave_html_section_part child, fabric, port
|
1826
|
+
port.puts
|
1827
|
+
end
|
1828
|
+
unless (element.warnings || []).empty? then
|
1829
|
+
weave_html_warning_list element.warnings, port,
|
1830
|
+
inline: true
|
1831
|
+
port.puts
|
1832
|
+
end
|
1833
|
+
port.puts "</section>"
|
1834
|
+
else raise 'data structure error'
|
1835
|
+
end
|
1836
|
+
port.puts
|
1837
|
+
end
|
1838
|
+
port.puts '</html>'
|
1839
|
+
port.puts '</body>'
|
1840
|
+
port.puts '</html>'
|
1841
|
+
return
|
1842
|
+
end
|
1843
|
+
|
1844
|
+
def weave_html_section_part element, fabric, port
|
1845
|
+
case element.type
|
1846
|
+
when :paragraph then
|
1847
|
+
port.print "<p>"
|
1848
|
+
htmlify element.content, port
|
1849
|
+
port.puts "</p>"
|
1850
|
+
|
1851
|
+
when :list then
|
1852
|
+
weave_html_list element.items, port
|
1853
|
+
|
1854
|
+
when :divert then
|
1855
|
+
weave_html_chunk_header element, 'maui-divert',
|
1856
|
+
port
|
1857
|
+
port.puts
|
1858
|
+
weave_html_warning_list element.warnings, port,
|
1859
|
+
inline: true
|
1860
|
+
|
1861
|
+
when :chunk, :diverted_chunk then
|
1862
|
+
port.print "<div class='maui-chunk"
|
1863
|
+
port.print " maui-initial-chunk" if element.initial
|
1864
|
+
port.print " maui-final-chunk" if element.final
|
1865
|
+
port.print "'>"
|
1866
|
+
if element.type == :chunk then
|
1867
|
+
weave_html_chunk_header element, 'maui-chunk-header',
|
1868
|
+
port
|
1869
|
+
port.puts
|
1870
|
+
end
|
1871
|
+
weave_html_chunk_body element, port
|
1872
|
+
unless (element.warnings || []).empty? then
|
1873
|
+
weave_html_warning_list element.warnings, port,
|
1874
|
+
inline: true
|
1875
|
+
end
|
1876
|
+
if element.final then
|
1877
|
+
port.print "<div class='maui-chunk-xref'>"
|
1878
|
+
htmlify(
|
1879
|
+
xref_chain(element, fabric, dash: "\u2013"),
|
1880
|
+
port)
|
1881
|
+
port.puts "</div>"
|
1882
|
+
end
|
1883
|
+
port.puts "</div>"
|
1884
|
+
|
1885
|
+
when :block then
|
1886
|
+
port.print "<pre class='maui-block'>"
|
1887
|
+
element.lines.each_with_index do |line, i|
|
1888
|
+
port.puts unless i.zero?
|
1889
|
+
port.print line.to_xml
|
1890
|
+
end
|
1891
|
+
port.puts "</pre>"
|
1892
|
+
else
|
1893
|
+
raise 'data structure error'
|
1894
|
+
end
|
1895
|
+
return
|
1896
|
+
end
|
1897
|
+
|
1898
|
+
def weave_html_toc toc, port
|
1899
|
+
if toc.length >= 2 then
|
1900
|
+
port.puts "<h2>Contents</h2>"
|
1901
|
+
port.puts
|
1902
|
+
last_level = 0
|
1903
|
+
# What level should the rubrics in the current
|
1904
|
+
# (sub(sub))chapter appear at?
|
1905
|
+
rubric_level = 1
|
1906
|
+
toc.each do |entry|
|
1907
|
+
if entry.type == :rubric then
|
1908
|
+
level = rubric_level
|
1909
|
+
else
|
1910
|
+
level = entry.level
|
1911
|
+
rubric_level = entry.level + 1
|
1912
|
+
end
|
1913
|
+
if level > last_level then
|
1914
|
+
raise 'assertion failed' \
|
1915
|
+
unless level == last_level + 1
|
1916
|
+
port.print "\n<ul><li>"
|
1917
|
+
elsif level == last_level then
|
1918
|
+
port.print "</li>\n<li>"
|
1919
|
+
else
|
1920
|
+
port.print "</li></ul>" * (last_level - level) +
|
1921
|
+
"\n<li>"
|
1922
|
+
end
|
1923
|
+
case entry.type
|
1924
|
+
when :title then
|
1925
|
+
port.print "#{entry.number}. "
|
1926
|
+
port.print "<a href='#T.#{entry.number}'>"
|
1927
|
+
htmlify entry.content, port
|
1928
|
+
port.print "</a>"
|
1929
|
+
when :rubric then
|
1930
|
+
port.print "\u00A7#{entry.section_number}. "
|
1931
|
+
port.print "<a href='#S.#{entry.section_number}'>"
|
1932
|
+
htmlify entry.content, port
|
1933
|
+
port.print "</a>"
|
1934
|
+
else
|
1935
|
+
raise 'assertion failed'
|
1936
|
+
end
|
1937
|
+
last_level = level
|
1938
|
+
end
|
1939
|
+
port.puts "</li></ul>" * last_level
|
1940
|
+
port.puts
|
1941
|
+
end
|
1942
|
+
return
|
1943
|
+
end
|
1944
|
+
|
1945
|
+
def weave_html_list items, port
|
1946
|
+
port.puts "<ul>"
|
1947
|
+
items.each do |item|
|
1948
|
+
port.print "<li>"
|
1949
|
+
htmlify item.content, port
|
1950
|
+
if item.sublist then
|
1951
|
+
port.puts
|
1952
|
+
weave_html_list item.sublist.items, port
|
1953
|
+
end
|
1954
|
+
unless (item.warnings || []).empty? then
|
1955
|
+
port.puts
|
1956
|
+
weave_html_warning_list item.warnings, port,
|
1957
|
+
inline: true
|
1958
|
+
end
|
1959
|
+
port.puts "</li>"
|
1960
|
+
end
|
1961
|
+
port.puts "</ul>"
|
1962
|
+
return
|
1963
|
+
end
|
1964
|
+
|
1965
|
+
def weave_html_chunk_header element, cls, port, tag: 'div'
|
1966
|
+
port.print "<#{tag} class='%s'>" % cls
|
1967
|
+
port.print "«"
|
1968
|
+
if element.root_type then
|
1969
|
+
port.print "<u>%s</u> " % element.root_type.to_xml
|
1970
|
+
end
|
1971
|
+
htmlify(
|
1972
|
+
parse_markup(element.name, Fabricator::MF::LINK),
|
1973
|
+
port)
|
1974
|
+
port.print "»:"
|
1975
|
+
port.print "</#{tag}>"
|
1976
|
+
# Note that we won't output a trailing linebreak here.
|
1977
|
+
return
|
1978
|
+
end
|
1979
|
+
|
1980
|
+
def weave_html_chunk_body element, port
|
1981
|
+
port.print "<pre class='maui-chunk-body'>"
|
1982
|
+
element.content.each do |node|
|
1983
|
+
case node.type
|
1984
|
+
when :verbatim then
|
1985
|
+
port.print node.data.to_xml
|
1986
|
+
when :newline then
|
1987
|
+
port.puts
|
1988
|
+
when :use then
|
1989
|
+
port.print "<span class='maui-transclude'>"
|
1990
|
+
port.print "«"
|
1991
|
+
if node.clearindent then
|
1992
|
+
port.print ".clearindent "
|
1993
|
+
end
|
1994
|
+
htmlify(
|
1995
|
+
parse_markup(node.name, Fabricator::MF::LINK),
|
1996
|
+
port)
|
1997
|
+
if node.vertical_separation then
|
1998
|
+
port.print " " + node.vertical_separation.to_xml
|
1999
|
+
end
|
2000
|
+
if node.postprocess then
|
2001
|
+
port.print " " + node.postprocess.to_xml
|
2002
|
+
end
|
2003
|
+
port.print "»"
|
2004
|
+
port.print "</span>"
|
2005
|
+
else raise 'data structure error'
|
2006
|
+
end
|
2007
|
+
end
|
2008
|
+
port.puts "</pre>"
|
2009
|
+
return
|
2010
|
+
end
|
2011
|
+
|
2012
|
+
def weave_html_warning_list list, port, inline: false
|
2013
|
+
if list and !list.empty? then
|
2014
|
+
port.print "<ul class='maui-warnings"
|
2015
|
+
port.print " maui-inline-warnings" if inline
|
2016
|
+
port.puts "'>"
|
2017
|
+
list.each do |warning|
|
2018
|
+
port.print "<li"
|
2019
|
+
port.print " id='W.#{warning.number}'" if inline
|
2020
|
+
port.print ">"
|
2021
|
+
port.print "!!! " if inline
|
2022
|
+
if !inline and warning.inline then
|
2023
|
+
port.print "<a href='#W.%i'>" % warning.number
|
2024
|
+
end
|
2025
|
+
port.print "<tt>%s</tt>" %
|
2026
|
+
format_location(warning.loc).to_xml
|
2027
|
+
port.print ": " + warning.message
|
2028
|
+
port.print "</a>" if !inline and warning.inline
|
2029
|
+
port.puts "</li>"
|
2030
|
+
end
|
2031
|
+
port.puts "</ul>"
|
2032
|
+
end
|
2033
|
+
return
|
2034
|
+
end
|
2035
|
+
|
2036
|
+
def htmlify nodes, port
|
2037
|
+
nodes.each do |node|
|
2038
|
+
case node.type
|
2039
|
+
when :plain then
|
2040
|
+
port.print node.data.to_xml
|
2041
|
+
|
2042
|
+
when :space then
|
2043
|
+
port.print((node.data || ' ').to_xml)
|
2044
|
+
|
2045
|
+
when :nbsp then
|
2046
|
+
port.print ' '
|
2047
|
+
|
2048
|
+
when :monospace, :bold, :italic, :underscore then
|
2049
|
+
html_tag = Fabricator::MARKUP2HTML[node.type]
|
2050
|
+
port.print "<%s>" % html_tag
|
2051
|
+
htmlify node.content, port
|
2052
|
+
port.print "</%s>" % html_tag
|
2053
|
+
|
2054
|
+
when :mention_chunk then
|
2055
|
+
port.print "<span class='maui-chunk-mention'>\u00AB"
|
2056
|
+
htmlify(
|
2057
|
+
parse_markup(node.name, Fabricator::MF::LINK),
|
2058
|
+
port)
|
2059
|
+
port.print "\u00BB</span>"
|
2060
|
+
|
2061
|
+
when :link then
|
2062
|
+
port.print "<a href='#{node.target.to_xml}'>"
|
2063
|
+
htmlify node.content, port
|
2064
|
+
port.print "</a>"
|
2065
|
+
else
|
2066
|
+
raise 'invalid node type'
|
2067
|
+
end
|
2068
|
+
end
|
2069
|
+
return
|
2070
|
+
end
|
2071
|
+
end
|