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.
Files changed (11) hide show
  1. checksums.yaml +7 -0
  2. data/GPL-3 +674 -0
  3. data/Makefile +17 -0
  4. data/Manifest.txt +9 -0
  5. data/README.fab +222 -0
  6. data/README.html +296 -0
  7. data/bin/maui +200 -0
  8. data/lib/mau/fabricator.rb +2071 -0
  9. data/maui.fab +3576 -0
  10. data/maui.gemspec +24 -0
  11. metadata +59 -0
@@ -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
+ '&' => '&amp;',
12
+ '<' => '&lt;',
13
+ '>' => '&gt;',
14
+ '"' => '&quot;',
15
+ "'" => '&apos;',
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 "&#xAB;"
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 "&#xBB;:"
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 "&#xAB;"
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 "&#xBB;"
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 '&nbsp;'
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