class Fabricator::Vertical_Peeker::Integrator
Attributes
output[R]
section_count[R]
Public Class Methods
new()
click to toggle source
Calls superclass method
# File lib/mau/fabricator.rb, line 112 def initialize super() @output = OpenStruct.new( warnings: [], presentation: [], # list of titles and sections toc: [], chunks_by_name: {}, # canonical_name => { # root_type: String, # chunks: list of :chunk/:diverted_chunk records, # headers: list of :chunk/:divert records, # } roots: [], # list of canonical names ) @cursec = nil # The current section if started @section_count = 0 # The number of last section @title_counters = [0] @curdivert = nil # The current diversion if active @last_divertee = nil # last chunk diverted by [[@curdivert]] @list_stack = nil @in_code = false @last_title_level = 0 @warning_counter = 0 return end
Public Instance Methods
check_chunk_sizes(limit)
click to toggle source
# File lib/mau/fabricator.rb, line 391 def check_chunk_sizes limit return unless limit @output.presentation.each do |node| next unless node.type == :section node.elements.each do |element| next unless element.type == :chunk if element.lines.length > limit then if element.lines.length > limit * 2 then assessment, factor = "very long chunk", 2 else assessment, factor = "long chunk", 1 end limit_loc = element.body_loc.dup limit_loc.column = nil limit_loc.line += limit * factor (element.warnings ||= []).push \ warn(limit_loc, "%s (%i lines)" % [assessment, element.lines.length], inline: true) end end end return end
check_root_type_consistency()
click to toggle source
# File lib/mau/fabricator.rb, line 95 def check_root_type_consistency @output.roots.each do |name| cbn_entry = @output.chunks_by_name[name] effective_root_type = cbn_entry.root_type cbn_entry.headers.each do |element| unless element.root_type == effective_root_type then warn element.header_loc, "inconsistent root type, assuming %s" % effective_root_type end end end return end
clear_diversion()
click to toggle source
# File lib/mau/fabricator.rb, line 371 def clear_diversion if @curdivert then if !@last_divertee then (@curdivert.warnings ||= []).push \ warn(@curdivert.header_loc, "unused diversion", inline: true) elsif @last_divertee.initial then (@curdivert.warnings ||= []).push \ warn(@curdivert.header_loc, "single-use diversion", inline: true) end @curdivert = nil @last_divertee.final = true if @last_divertee @last_divertee = nil end return end
force_section_break()
click to toggle source
# File lib/mau/fabricator.rb, line 364 def force_section_break @cursec = nil @list_stack = nil @in_code = false return end
integrate(element)
click to toggle source
# File lib/mau/fabricator.rb, line 139 def integrate element if element.type == :title then # Check the title's level restriction if element.level > @last_title_level + 1 then warn element.loc, "title level too deep" element.level = @last_title_level + 1 end @last_title_level = element.level # Number the title while @title_counters.length > element.level do @title_counters.pop end if @title_counters.length < element.level then @title_counters.push 0 end @title_counters[-1] += 1 element.number = @title_counters.join '.' # Append the node to [[presentation]] and [[toc]] force_section_break @output.presentation.push element @output.toc.push element # Enforce (sub(sub))chapter-locality of diversions clear_diversion else if element.type == :block and @curdivert then element.type = :diverted_chunk element.name = @curdivert.name element.divert = @curdivert element.initial = true if @last_divertee.nil? @last_divertee = element end if [:divert, :chunk].include? element.type then clear_diversion end if (@cursec and element.type == :rubric) or (@in_code and [:paragraph, :block, :item].include?( element.type)) then (@cursec.warnings ||= []).push \ warn(element.loc, "silent section break", inline: true) force_section_break end if @cursec.nil? then @cursec = OpenStruct.new( type: :section, section_number: (@section_count += 1), elements: []) @output.presentation.push @cursec end if element.type == :rubric then element.section_number = @cursec.section_number @output.toc.push element end if element.type == :divert then @curdivert = element raise 'assertion failed' unless @last_divertee.nil? end if element.type == :item then # Is this a top-level or descendant item? unless @list_stack then raise 'assertion failed' unless element.indent == 0 # Create a new [[list]] node. new_list = OpenStruct.new( type: :list, items: [], indent: element.indent) @cursec.elements.push new_list @list_stack = [new_list] else while @list_stack.last.indent > element.indent do if @list_stack[-2].indent < element.indent then # Unexpected de-dent, like this: # - master list # - child 1 # - child 2 @list_stack.last.indent = element.indent (element.warnings ||= []).push \ warn(element.loc, "unexpected dedent", inline: true) break end @list_stack.pop end if @list_stack.last.indent < element.indent then if @list_stack.last.sublist then raise 'assertion failed' end new_list = OpenStruct.new( type: :list, items: [], indent: element.indent) @list_stack.last.items.last.sublist = new_list @list_stack.push new_list end end # The list structure has been prepared. Append the # new element to the innermost list in progress. @list_stack.last.items.push element else @cursec.elements.push element if [:chunk, :diverted_chunk]. include?(element.type) then element.section_number = @cursec.section_number @in_code = true # so we can generate a section break if a # narrative-type element follows element.content = [] element.lines.each_with_index do |line, lineno_in_chunk| unless lineno_in_chunk.zero? then element.content.push \ OpenStruct.new(type: :newline) end column = 1 + element.indent line.split(/(<<\s* (?: \[\[.*?\]*\]\] | . )+? \s*>>)/x, -1).each_with_index do |raw_piece, piece_index| node = nil if piece_index.odd? then name = raw_piece[2 ... -2].strip # discard the surrounding double brokets # together with adjacent whitespace node = OpenStruct.new(type: :use, name: nil, # for ordering; will be replaced below raw: raw_piece, loc: OpenStruct.new( filename: element.body_loc.filename, line: element.body_loc.line + lineno_in_chunk, column: column) ) if name =~ /(?:^|\s+)(\|[\w>-]+)$/ and Fabricator::POSTPROCESSES.has_key? $1 then node.postprocess = $1; name = $` end if name =~ /(?:^|\s+)(\.dense)$/ then node.vertical_separation = $1; name = $` end if name =~ /^(\.clearindent)(?:\s+|$)/ then node.clearindent = true; name = $' end if !name.empty? then node.name = Fabricator.canonicalise_chunk_name(name) else # not a proper reference, after all node = nil end # If failed, [[node]] is still [[nil]]. end if node.nil? and !raw_piece.empty? then node = OpenStruct.new( type: :verbatim, data: raw_piece) end element.content.push node if node column += raw_piece.length end end end if [:chunk, :diverted_chunk, :divert].include?( element.type) then cbn_record = @output.chunks_by_name[element.name] ||= OpenStruct.new(chunks: [], headers: []) if [:chunk, :diverted_chunk].include?( element.type) then cbn_record.chunks.push element end if [:chunk, :divert].include? element.type then cbn_record.headers.push element end if element.root_type then # check the filename's reasonability bad_name = false parts = element.name.split '/' if ['', '.', '..'].any?{|d| parts.include? d} then bad_name = true end unless parts.all?{|p| p =~ /\A[\w.-]+\Z/} then bad_name = true end if bad_name then (element.warnings ||= []).push \ warn(element.header_loc, "unuseable filename", inline: true) element.root_type = nil end end # The :chunks_by_name record will hold the highest # root_type for chunks of this name, with the order # defined as [[nil]] < [['.file']] < [['.script']]. if element.root_type and cbn_record.root_type.nil? then cbn_record.root_type = element.root_type @output.roots.push element.name end if element.root_type == '.script' then cbn_record.root_type = element.root_type end end @list_stack = nil end end return end
tangle_chunks(cbn_entry, sink, trace, vsep = 2)
click to toggle source
# File lib/mau/fabricator.rb, line 426 def tangle_chunks cbn_entry, sink, trace, vsep = 2 chain_start_loc = nil cbn_entry.chunks.each_with_index do |chunk, i| vsep.times{sink.newline} unless i.zero? if chunk.divert and chunk.initial then raise 'assertion failed' if chain_start_loc chain_start_loc = sink.location_ahead end start_location = sink.location_ahead chunk.content.each do |node| case node.type when :verbatim then sink.write node.data when :newline then sink.newline when :use then tangle_transclusion node, sink, trace, chunk else raise 'data structure error' end end end_location = sink.location_behind # Both endpoints are inclusive. (chunk.tangle_locs ||= []).push OpenStruct.new( from: start_location, to: end_location) if chunk.divert and chunk.final then raise 'assertion failed' unless chain_start_loc (chunk.divert.chain_tangle_locs ||= []).push \ OpenStruct.new( from: chain_start_loc, to: sink.location_behind) chain_start_loc = nil end end return end
tangle_roots()
click to toggle source
# File lib/mau/fabricator.rb, line 509 def tangle_roots return if @output.tangles @output.tangles = {} @output.roots.each do |name| sport = StringIO.new sink = Fabricator::Tangling_Sink.new name, sport cbn_entry = @output.chunks_by_name[name] # We can assume that [[cbn_entry]] is not [[nil]], for # otherwise there wouldn't be a [[roots]] entry. tangle_chunks cbn_entry, sink, Set.new([name]) sink.newline @output.tangles[name] = OpenStruct.new( filename: name, root_type: cbn_entry.root_type, content: sport.string, line_count: sink.line_count, nonblank_line_count: sink.nonblank_line_count, longest_line_length: sink.longest_line_length, ) end return end
tangle_transclusion(node, sink, trace, referrer)
click to toggle source
# File lib/mau/fabricator.rb, line 464 def tangle_transclusion node, sink, trace, referrer name = node.name if trace.include? name then warn node.loc, "circular reference" sink.write node.raw else cbn_entry = @output.chunks_by_name[name] if cbn_entry.nil? or cbn_entry.chunks.empty? then warn node.loc, "dangling reference" sink.write node.raw else (cbn_entry.transcluders ||= []).push( OpenStruct.new( name: referrer.name, section_number: referrer.section_number, )) trace.add name if node.postprocess then # redirect the tangler outer_sink = sink inner_sport = StringIO.new sink = Fabricator::Tangling_Sink.new '(pipe)', inner_sport end sink.pin_indent node.clearindent ? 0 : nil do tangle_chunks cbn_entry, sink, trace, node.vertical_separation == '.dense' ? 1 : 2 end if node.postprocess then # revert the redirect and apply the filter sink.newline filter_output = Fabricator::POSTPROCESSES[node.postprocess]. call(inner_sport.string) sink = outer_sink sink.pin_indent node.clearindent ? 0 : nil do sink.write_long filter_output end end trace.delete name end end return end
warn(location, message, inline: false)
click to toggle source
# File lib/mau/fabricator.rb, line 416 def warn location, message, inline: false record = OpenStruct.new( loc: location, message: message, number: @warning_counter += 1, inline: inline) @output.warnings.push record return record # so it can also be attached elsewhere end