maui 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
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