module Fabricator

Public Class Methods

canonicalise_chunk_name(raw_name) click to toggle source
# File lib/mau/fabricator.rb, line 996
def canonicalise_chunk_name raw_name
  name = ''
  raw_name.strip.split(/(\[\[.*?\]*\]\])/, -1).
      each_with_index do |part, i|
    part.gsub! /\s+/, ' ' if i.even?
    name << part
  end
  return name
end
commatise_oxfordly(items) click to toggle source
# File lib/mau/fabricator.rb, line 1593
def commatise_oxfordly items
  result = []
  items.each_with_index do |item, i|
    unless i.zero? then
      unless items.length == 2 then
        result.push OpenStruct.new(:type => :plain,
            :data => ',')
      end
      result.push OpenStruct.new(:type => :space)
      if i == items.length - 1 then
        result.push OpenStruct.new(:type => :plain,
            :data => 'and')
        result.push OpenStruct.new(:type => :space)
      end
    end
    result.push *item
  end
  return result
end
format_location(h) click to toggle source
# File lib/mau/fabricator.rb, line 964
def format_location h
  if h.column then
    return "%s:%i.%i" % [h.filename, h.line, h.column]
  else
    return "%s:%i" % [h.filename, h.line]
  end
end
format_location_range(h, dash: "-") click to toggle source
# File lib/mau/fabricator.rb, line 972
def format_location_range h, dash: "-"
  if h.from.filename != h.to.filename then
    return format_location(h.from) + dash +
        format_location(h.to)
  else
    if h.from.line != h.to.line then
      result = h.from.filename + ":"
      result << h.from.line.to_s
      result << "." << h.from.column.to_s if h.from.column
      result << dash
      result << h.to.line.to_s
      result << "." << h.to.column.to_s if h.to.column
    else
      result = h.from.filename + ":"
      result << h.from.line.to_s
      if h.from.column or h.to.column then
        result << "." <<
          h.from.column.to_s << dash << h.to.column.to_s
      end
    end
    return result
  end
end
htmlify(nodes, port) click to toggle source
# File lib/mau/fabricator.rb, line 2036
def htmlify nodes, port
  nodes.each do |node|
    case node.type
    when :plain then
      port.print node.data.to_xml

    when :space then
      port.print((node.data || ' ').to_xml)

    when :nbsp then
      port.print '&nbsp;'

    when :monospace, :bold, :italic, :underscore then
      html_tag = Fabricator::MARKUP2HTML[node.type]
      port.print "<%s>" % html_tag
      htmlify node.content, port
      port.print "</%s>" % html_tag

    when :mention_chunk then
      port.print "<span class='maui-chunk-mention'>\u00AB"
      htmlify(
          parse_markup(node.name, Fabricator::MF::LINK),
          port)
      port.print "\u00BB</span>"

    when :link then
      port.print "<a href='#{node.target.to_xml}'>"
      htmlify node.content, port
      port.print "</a>"
    else
      raise 'invalid node type'
    end
  end
  return
end
load_fabric(input, chunk_size_limit: 24) click to toggle source
# File lib/mau/fabricator.rb, line 1192
  def load_fabric input, chunk_size_limit: 24
    vp = Fabricator::Vertical_Peeker.new input
    integrator = Fabricator::Integrator.new

    in_list = false
    loop do
      vertical_separation = 0
      while vp.peek_line == '' do
        if vertical_separation == 2 then
          integrator.warn vp.location_ahead,
              "more than two consecutive blank lines"
        end
        vertical_separation += 1
        vp.get_line
      end
      break if vp.eof?
      if vertical_separation >= 2 then
        integrator.force_section_break
        in_list = false
      end
      element_location = vp.location_ahead
      case vp.peek_line
      when /^\s+/ then
        if !in_list or
            vp.peek_line !~ /^
                (?<margin> \s+ )
                - (?<separator> \s+ )
                /x then
          body_location = vp.location_ahead
          element = vp.get_indented_lines_with_skip
          element.type = :block
          element.body_loc = element_location
        else
          margin = $~['margin']
          lines = [$~['separator'] + $']
          vp.get_line
          while !vp.eof? and
              vp.peek_line.start_with? margin and
              vp.peek_line !~ /^\s*-\s/ do
            lines.push vp.get_line[margin.length .. -1]
          end
          element = OpenStruct.new(
            type: :item,
            lines: lines,
            content: parse_markup(lines.map(&:strip).join ' '),
            indent: margin.length,
            loc: element_location)
        end

      when /^<<\s*
          (?: (?<root-type> \.file|\.script)\s+ )?
          (?<raw-name> [^\s].*?)
          \s*>>:$/x then
        name = canonicalise_chunk_name $~['raw-name']
        vp.get_line
        element = OpenStruct.new(
          type: :divert,
          root_type: $~['root-type'],
          name: name,
          header_loc: element_location)

        body_location = vp.location_ahead
        body = vp.get_indented_lines_with_skip
        if body then
          element.type = :chunk
          element.lines = body.lines
          element.indent = body.indent
          element.body_loc = body_location
          element.initial = element.final = true
        end

      when /^-\s/ then
        # We'll discard the leading dash but save the following
        # whitespace.
        lines = [vp.get_line[1 .. -1]]
        while !vp.eof? and
            vp.peek_line != '' and
            vp.peek_line !~ /^\s*-\s/ do
          lines.push vp.get_line
        end
        element = OpenStruct.new(
          type: :item,
          lines: lines,
          content: parse_markup(lines.map(&:strip).join ' '),
          indent: 0,
          loc: element_location)

      when /^[^\s]/ then
        lines = []
        while vp.peek_line =~ /^[^\s]/ and
            vp.peek_line !~ /^-\s/ do
          lines.push vp.get_line
        end
        mode_flags_to_suppress = 0
        case lines[0]
        when /^(==+)(\s+)/ then
          lines[0] = $2 + $'
          element = OpenStruct.new(
            type: :title,
            level: $1.length - 1,
            loc: element_location)
          mode_flags_to_suppress |= Fabricator::MF::LINK

        when /^\*\s+/ then
          lines[0] = $'
          element = OpenStruct.new(
              type: :rubric,
              loc: element_location)

        else
          element = OpenStruct.new(
              type: :paragraph,
              loc: element_location)
        end
        element.lines = lines
        element.content =
            parse_markup(lines.map(&:strip).join(' '),
            mode_flags_to_suppress)
      else raise 'assertion failed'
      end
      integrator.integrate element
      in_list = element.type == :item
    end
    integrator.clear_diversion

    integrator.check_root_type_consistency
    integrator.check_chunk_sizes(chunk_size_limit)
    integrator.tangle_roots
    return integrator.output
  end

  def weave_ctxt fabric, port,
      width: 80,
      pseudographics: Fabricator::UNICODE_PSEUDOGRAPHICS
    wr = Fabricator::Text_Wrapper.new port,
        width: width,
        pseudographics: pseudographics
    unless fabric.warnings.empty? then
      wr.styled :section_title do
        wr.add_plain 'Warnings'
      end
      wr.linebreak
      wr.linebreak
      weave_ctxt_warning_list fabric.warnings, wr
      wr.linebreak
    end
    toc_generated = false
    fabric.presentation.each do |element|
      case element.type
      when :title then
        if !toc_generated then
          weave_ctxt_toc fabric.toc, wr
          toc_generated = true
        end
        wr.styled :section_title do
          wr.add_plain "#{element.number}."
          wr.add_space
          wr.hang do
            wr.add_nodes element.content
          end
        end
        wr.linebreak
        wr.linebreak
      when :section then
        rubricated = element.elements[0].type == :rubric
        # If we're encountering the first rubric/title, output
        # the table of contents.
        if rubricated and !toc_generated then
          weave_ctxt_toc fabric.toc, wr
          toc_generated = true
        end

        start_index = 0 # index of the first non-special child
        if rubricated then
          start_index += 1
          wr.styled :rubric do
            wr.add_plain "§%i." % element.section_number
            wr.add_space
            wr.add_nodes element.elements.first.content
          end
        else
          wr.styled :section_number do
            wr.add_plain "§%i." % element.section_number
          end
        end

        # If the rubric or the section sign is followed by a
        # paragraph, a chunk header, or a divert, we'll output
        # it in the same paragraph.
        starter = element.elements[start_index]
        if starter then
          case starter.type
          when :paragraph, :divert, :chunk then
            wr.add_space
            weave_ctxt_section_part starter, fabric, wr
            start_index += 1
          else
            wr.linebreak
          end
        end

        # Finally, the blank line that separates the special
        # paragraph from the section's body, if any.
        wr.linebreak

        element.elements[start_index .. -1].each do |child|
          weave_ctxt_section_part child, fabric, wr
          wr.linebreak
        end

        unless (element.warnings || []).empty? then
          weave_ctxt_warning_list element.warnings, wr,
              inline: true, indent: false
          wr.linebreak
        end
      else raise 'data structure error'
      end
    end
    return
  end

  def weave_ctxt_warning_list list, wr, inline: false,
      indent: true
    list.to_a.each do |warning|
      wr.styled inline ? :inline_warning : :null do
        wr.add_plain (indent ? '  ' : '') + '!!! ' if inline
        wr.add_plain format_location(warning.loc)
        wr.add_plain ':'
        wr.add_space
        wr.hang do
          warning.message.split(/(\s+)/).
              each_with_index do |part, i|
            if i.even? then
              wr.add_plain part
            else
              wr.add_space part
            end
          end
        end
      end
      wr.linebreak
    end
    return
  end

  def weave_ctxt_section_part element, fabric, wr
    case element.type
    when :paragraph then
      wr.add_nodes element.content
      wr.linebreak

    when :divert, :chunk, :diverted_chunk then
      if [:divert, :chunk].include? element.type then
        weave_ctxt_chunk_header element, wr
        weave_ctxt_warning_list element.warnings, wr,
            inline: true
      end
      if [:chunk, :diverted_chunk].include? element.type then
        wr.styled :chunk_frame do
          wr.add_pseudographics element.initial ?
            :initial_chunk_margin :
            :chunk_margin
        end
        wr.styled :monospace do
          element.content.each do |node|
            case node.type
            when :verbatim then
              wr.add_plain node.data
            when :newline then
              wr.linebreak
              wr.styled :chunk_frame do
                wr.add_pseudographics :chunk_margin
              end
            when :use then
              weave_ctxt_use node, wr
            else raise 'data structure error'
            end
          end
        end
        wr.linebreak
        if element.final then
          wr.styled :chunk_frame do
            wr.add_pseudographics :final_chunk_marker
          end
          wr.linebreak
        end
        weave_ctxt_warning_list element.warnings, wr,
            inline: true
        if element.final then
          wr.styled :chunk_xref do
            wr.add_nodes xref_chain(element, fabric)
          end
          wr.linebreak
        end
      end

    when :list then
      weave_ctxt_list element.items, wr

    when :block then
      weave_ctxt_block element, wr
    else
      raise 'data structure error'
    end
    return
  end

  def weave_ctxt_chunk_header element, wr
    wr.styled :chunk_header do
      wr.add_pseudographics :before_chunk_name
      if element.root_type then
        wr.styled :root_type do
          wr.add_plain element.root_type
        end
        wr.add_space
      end
      wr.add_nodes(
          parse_markup(element.name, Fabricator::MF::LINK))
      wr.add_pseudographics :after_chunk_name
      wr.add_plain ":"
    end
    wr.linebreak
    return
  end

  def weave_ctxt_block element, wr
    element.lines.each do |line|
      wr.styled :block_frame do
        wr.add_pseudographics :block_margin
      end
      wr.styled :monospace do
        wr.add_plain line
      end
      wr.linebreak
    end
    return
  end

  def weave_ctxt_use node, wr
    wr.styled :use do
      wr.add_pseudographics :before_chunk_name
      if node.clearindent then
        wr.add_plain ".clearindent "
      end
      wr.add_nodes parse_markup(node.name, Fabricator::MF::LINK)
      if node.vertical_separation then
        wr.add_plain " " + node.vertical_separation
      end
      if node.postprocess then
        wr.add_plain " " + node.postprocess
      end
      wr.add_pseudographics :after_chunk_name
    end
    return
  end

  # Given a chunk, prepare its transclusion summary as a list of
  # markup nodes.  Should only be used on chunks that are the
  # last in a chunk chain (i.e., that have [[final]] set).
  def xref_chain element, fabric, dash: "-"
    xref = markup
    if element.initial then
      xref.words "This chunk is "
    else
      xref.words "These chunks are "
    end
    cbn_entry = fabric.chunks_by_name[element.name]
    transcluders = cbn_entry.transcluders
    if transcluders then
      xref.words "transcluded by "
      xref.push *commatise_oxfordly(
          transcluders.map{|ref| markup.
              node(:mention_chunk, name: ref.name).
              space.
              plain("(§%i)" % ref.section_number)
          })
    else
      if cbn_entry.root_type then
        xref.words "solely a transclusion root"
      else
        xref.words "never transcluded"
      end
    end
    xref.words " and "
    tlocs = element.divert ?
        element.divert.chain_tangle_locs :
        element.tangle_locs
    if tlocs then
      xref.
          words("tangled to ").
          push(*commatise_oxfordly(
          tlocs.map{|range| markup.
              plain(format_location_range(range, dash: dash))
          })).
          plain(".")
    else
      xref.words "never tangled."
    end
    return xref
  end

  def commatise_oxfordly items
    result = []
    items.each_with_index do |item, i|
      unless i.zero? then
        unless items.length == 2 then
          result.push OpenStruct.new(:type => :plain,
              :data => ',')
        end
        result.push OpenStruct.new(:type => :space)
        if i == items.length - 1 then
          result.push OpenStruct.new(:type => :plain,
              :data => 'and')
          result.push OpenStruct.new(:type => :space)
        end
      end
      result.push *item
    end
    return result
  end

  def weave_ctxt_list items, wr
    items.each do |item|
      wr.add_pseudographics :bullet
      wr.add_plain " "
      wr.hang do
        wr.add_nodes item.content
      end
      wr.linebreak
      unless (item.warnings || []).empty? then
        wr.hang do
          weave_ctxt_warning_list item.warnings, wr,
              inline: true
        end
      end
      if item.sublist then
        wr.add_plain "  "
        wr.hang do
          weave_ctxt_list item.sublist.items, wr
        end
      end
    end
    return
  end

  def weave_ctxt_toc toc, wr
    if toc.length >= 2 then
      wr.styled :section_title do
        wr.add_plain 'Contents'
      end
      wr.linebreak; wr.linebreak
      rubric_level = 0
      toc.each do |entry|
        case entry.type
        when :title then
          rubric_level = entry.level - 1 + 1
          wr.add_plain '  ' * (entry.level - 1)
          wr.add_plain entry.number + '.'
          wr.add_space
          wr.hang do
            wr.add_nodes entry.content
          end

        when :rubric then
          wr.add_plain '  ' * rubric_level
          wr.add_plain '§%i.' % entry.section_number
          wr.add_space
          wr.hang do
            wr.add_nodes entry.content
          end

        else
          raise 'assertion failed'
        end
        wr.linebreak
      end
      wr.linebreak
    end
    return
  end

  def weave_html fabric, port,
      title: nil,
      link_css: []
    title ||= "(Untitled)"
    port.puts '<!doctype html>'
    port.puts '<html>'
    port.puts '<head>'
    port.puts "<meta http-equiv='Content-type' " +
        "content='text/html; charset=utf-8' />"
    port.puts "<title>#{title.to_xml}</title>"
    if link_css.empty? then
      port.puts "<style type='text/css'>"
      port.puts '/**** Fonts ****/
@import url("http://fonts.googleapis.com/css?family=Roboto");
@import url("http://fonts.googleapis.com/css?family=Cousine");
/**** Rules ****/
body, .maui-transclude {
  font-family: "Roboto", sans-serif; }

pre, tt, code {
  font-family: "Cousine", monospace; }

body {
  colour: black;
  background: white; }

tt, code {
  color: forestgreen; }

.maui-inline-warnings {
  color: red; }

.maui-warnings tt {
  color: inherit; }

.maui-rubric {
  color: crimson; }

ul.maui-warnings {
  padding-left: 0; }
  ul.maui-warnings > li {
    list-style: none; }

.maui-chunk-body {
  margin-left: 20px;
  border-left: 2px solid #cccccc;
  padding-left: 5px; }

.maui-initial-chunk > .maui-chunk-body:before {
  content: "";
  display: block;
  width: 22px;
  border-top: solid 2px #cccccc;
  margin-left: -27px; }

.maui-final-chunk > .maui-chunk-body:after {
  content: "";
  display: block;
  margin-left: -7px;
  width: 40px;
  border-bottom: solid 2px #cccccc; }

.maui-chunk-body, .maui-chunk > .maui-warnings {
  margin-top: 0;
  margin-bottom: 0; }

.maui-chunk {
  margin-top: 16px;
  margin-bottom: 16px; }

.maui-chunk-xref {
  font-size: small;
  font-style: italic;
  margin-left: 22px; }

/* Backwards compatibility with pre-HTML5 browsers */
section {
  display: block; }'
      port.puts "</style>"
    else
      link_css.each do |link|
        port.puts ("<link rel='stylesheet' type='text/css' " +
            "href='%s' />") % link.to_xml
      end
    end
    port.puts '</head>'
    port.puts '<body>'
    port.puts
    port.puts "<h1>#{title.to_xml}</h1>"
    unless fabric.warnings.empty? then
      port.puts "<h2>Warnings</h2>"
      port.puts
      weave_html_warning_list fabric.warnings, port
      port.puts
    end
    toc_generated = false
    fabric.presentation.each do |element|
      case element.type
      when :title then
        if !toc_generated then
          weave_html_toc fabric.toc, port
          toc_generated = true
        end
        port.print '<h%i' % (element.level + 1)
        port.print " id='%s'" % "T.#{element.number}"
        port.print '>'
        port.print "#{element.number}. "
        htmlify element.content, port
        port.puts '</h%i>' % (element.level + 1)
      when :section then
        rubricated = element.elements[0].type == :rubric
        # If we're encountering the first rubric/title, output
        # the table of contents.
        if rubricated and !toc_generated then
          weave_html_toc fabric.toc, port
          toc_generated = true
        end

        start_index = 0
        port.puts "<section class='maui-section' id='%s'>" %
            "S.#{element.section_number}"
        port.puts
        port.print "<p>"
        port.print "<b class='%s'>" %
            (rubricated ? 'maui-rubric' : 'maui-section-number')
        port.print "\u00A7#{element.section_number}."
        if rubricated then
          port.print " "
          htmlify element.elements[start_index].content, port
          start_index += 1
        end
        port.print "</b>"
        subelement = element.elements[start_index]
        warnings = nil
        case subelement && subelement.type
          when :paragraph then
            port.print " "
            htmlify subelement.content, port
            start_index += 1
          when :divert then
            port.print " "
            weave_html_chunk_header subelement, 'maui-divert',
                port, tag: 'span'
            warnings = subelement.warnings
            start_index += 1
        end
        port.puts "</p>"
        if warnings then
          weave_html_warning_list warnings, port, inline: true
        end
        port.puts
        element.elements[start_index .. -1].each do |child|
          weave_html_section_part child, fabric, port
          port.puts
        end
        unless (element.warnings || []).empty? then
          weave_html_warning_list element.warnings, port,
              inline: true
          port.puts
        end
        port.puts "</section>"
      else raise 'data structure error'
      end
      port.puts
    end
    port.puts '</html>'
    port.puts '</body>'
    port.puts '</html>'
    return
  end

  def weave_html_section_part element, fabric, port
    case element.type
    when :paragraph then
      port.print "<p>"
      htmlify element.content, port
      port.puts "</p>"

    when :list then
      weave_html_list element.items, port

    when :divert then
      weave_html_chunk_header element, 'maui-divert',
          port
      port.puts
      weave_html_warning_list element.warnings, port,
          inline: true

    when :chunk, :diverted_chunk then
      port.print "<div class='maui-chunk"
      port.print " maui-initial-chunk" if element.initial
      port.print " maui-final-chunk" if element.final
      port.print "'>"
      if element.type == :chunk then
        weave_html_chunk_header element, 'maui-chunk-header',
            port
        port.puts
      end
      weave_html_chunk_body element, port
      unless (element.warnings || []).empty? then
        weave_html_warning_list element.warnings, port,
            inline: true
      end
      if element.final then
        port.print "<div class='maui-chunk-xref'>"
        htmlify(
            xref_chain(element, fabric, dash: "\u2013"),
            port)
        port.puts "</div>"
      end
      port.puts "</div>"

    when :block then
      port.print "<pre class='maui-block'>"
      element.lines.each_with_index do |line, i|
        port.puts unless i.zero?
        port.print line.to_xml
      end
      port.puts "</pre>"
    else
      raise 'data structure error'
    end
    return
  end

  def weave_html_toc toc, port
    if toc.length >= 2 then
      port.puts "<h2>Contents</h2>"
      port.puts
      last_level = 0
      # What level should the rubrics in the current
      # (sub(sub))chapter appear at?
      rubric_level = 1
      toc.each do |entry|
        if entry.type == :rubric then
          level = rubric_level
        else
          level = entry.level
          rubric_level = entry.level + 1
        end
        if level > last_level then
          raise 'assertion failed' \
              unless level == last_level + 1
          port.print "\n<ul><li>"
        elsif level == last_level then
          port.print "</li>\n<li>"
        else
          port.print "</li></ul>" * (last_level - level) +
              "\n<li>"
        end
        case entry.type
        when :title then
          port.print "#{entry.number}. "
          port.print "<a href='#T.#{entry.number}'>"
          htmlify entry.content, port
          port.print "</a>"
        when :rubric then
          port.print "\u00A7#{entry.section_number}. "
          port.print "<a href='#S.#{entry.section_number}'>"
          htmlify entry.content, port
          port.print "</a>"
        else
          raise 'assertion failed'
        end
        last_level = level
      end
      port.puts "</li></ul>" * last_level
      port.puts
    end
    return
  end

  def weave_html_list items, port
    port.puts "<ul>"
    items.each do |item|
      port.print "<li>"
      htmlify item.content, port
      if item.sublist then
        port.puts
        weave_html_list item.sublist.items, port
      end
      unless (item.warnings || []).empty? then
        port.puts
        weave_html_warning_list item.warnings, port,
            inline: true
      end
      port.puts "</li>"
    end
    port.puts "</ul>"
    return
  end

  def weave_html_chunk_header element, cls, port, tag: 'div'
    port.print "<#{tag} class='%s'>" % cls
    port.print "&#xAB;"
    if element.root_type then
      port.print "<u>%s</u> " % element.root_type.to_xml
    end
    htmlify(
        parse_markup(element.name, Fabricator::MF::LINK),
        port)
    port.print "&#xBB;:"
    port.print "</#{tag}>"
    # Note that we won't output a trailing linebreak here.
    return
  end

  def weave_html_chunk_body element, port
    port.print "<pre class='maui-chunk-body'>"
    element.content.each do |node|
      case node.type
      when :verbatim then
        port.print node.data.to_xml
      when :newline then
        port.puts
      when :use then
        port.print "<span class='maui-transclude'>"
        port.print "&#xAB;"
        if node.clearindent then
          port.print ".clearindent "
        end
        htmlify(
            parse_markup(node.name, Fabricator::MF::LINK),
            port)
        if node.vertical_separation then
          port.print " " + node.vertical_separation.to_xml
        end
        if node.postprocess then
          port.print " " + node.postprocess.to_xml
        end
        port.print "&#xBB;"
        port.print "</span>"
      else raise 'data structure error'
      end
    end
    port.puts "</pre>"
    return
  end

  def weave_html_warning_list list, port, inline: false
    if list and !list.empty? then
      port.print "<ul class='maui-warnings"
      port.print " maui-inline-warnings" if inline
      port.puts "'>"
      list.each do |warning|
        port.print "<li"
        port.print " id='W.#{warning.number}'" if inline
        port.print ">"
        port.print "!!! " if inline
        if !inline and warning.inline then
          port.print "<a href='#W.%i'>" % warning.number
        end
        port.print "<tt>%s</tt>" %
            format_location(warning.loc).to_xml
        port.print ": " + warning.message
        port.print "</a>" if !inline and warning.inline
        port.puts "</li>"
      end
      port.puts "</ul>"
    end
    return
  end

  def htmlify nodes, port
    nodes.each do |node|
      case node.type
      when :plain then
        port.print node.data.to_xml

      when :space then
        port.print((node.data || ' ').to_xml)

      when :nbsp then
        port.print '&nbsp;'

      when :monospace, :bold, :italic, :underscore then
        html_tag = Fabricator::MARKUP2HTML[node.type]
        port.print "<%s>" % html_tag
        htmlify node.content, port
        port.print "</%s>" % html_tag

      when :mention_chunk then
        port.print "<span class='maui-chunk-mention'>\u00AB"
        htmlify(
            parse_markup(node.name, Fabricator::MF::LINK),
            port)
        port.print "\u00BB</span>"

      when :link then
        port.print "<a href='#{node.target.to_xml}'>"
        htmlify node.content, port
        port.print "</a>"
      else
        raise 'invalid node type'
      end
    end
    return
  end
markup() click to toggle source
# File lib/mau/fabricator.rb, line 1153
def markup
  return Fabricator::Markup_Constructor.new
end
parse_markup(s, suppress_modes = 0) click to toggle source
# File lib/mau/fabricator.rb, line 1006
def parse_markup s, suppress_modes = 0
  ps = Fabricator::Pointered_String.new s
  stack = Fabricator::Markup_Parser_Stack.new suppress_modes
  while ps.pointer < s.length do
    if ps.at? "[[" and
        end_offset = s.index("]]", ps.pointer + 2) then
      while ps[end_offset + 2] == ?] do
        end_offset += 1
      end
      monospaced_content = []
      ps[ps.pointer + 2 ... end_offset].split(/(\s+)/).
          each_with_index do |part, i|
        monospaced_content.push OpenStruct.new(
            type: i.even? ? :plain : :space,
            data: part
        )
      end
      stack.last.content.push OpenStruct.new(
          type: :monospace,
          content: monospaced_content)
      ps.pointer = end_offset + 2

    elsif stack.last.mode & Fabricator::MF::BOLD != 0 and
        ps.biu_starter? ?* then
      stack.spawn '*',
          Fabricator::MF::BOLD,
          Fabricator::MF::END_BOLD
      ps.pointer += 1

    elsif stack.last.mode & Fabricator::MF::ITALIC != 0 and
        ps.biu_starter? ?/ then
      stack.spawn '/',
          Fabricator::MF::ITALIC,
          Fabricator::MF::END_ITALIC
      ps.pointer += 1

    elsif stack.last.mode & Fabricator::MF::UNDERSCORE \
            != 0 and
        ps.biu_starter? ?_ then
      stack.spawn '_',
          Fabricator::MF::UNDERSCORE,
          Fabricator::MF::END_UNDERSCORE
      ps.pointer += 1

    elsif stack.last.mode & Fabricator::MF::END_BOLD != 0 and
        ps.biu_terminator? ?* then
      stack.ennode :bold, Fabricator::MF::END_BOLD
      ps.pointer += 1

    elsif stack.last.mode & Fabricator::MF::END_ITALIC \
            != 0 and
        ps.biu_terminator? ?/ then
      stack.ennode :italic, Fabricator::MF::END_ITALIC
      ps.pointer += 1

    elsif stack.last.mode & Fabricator::MF::END_UNDERSCORE \
            != 0 and
        ps.biu_terminator? ?_ then
      stack.ennode :underscore, Fabricator::MF::END_UNDERSCORE
      ps.pointer += 1

    elsif stack.last.mode & Fabricator::MF::LINK != 0 and
        ps.biu_starter? ?< then
      stack.spawn '<',
          Fabricator::MF::LINK,
          Fabricator::MF::END_LINK
      stack.last.start_offset = ps.pointer
      ps.pointer += 1

    elsif stack.last.mode & Fabricator::MF::END_LINK != 0 and
        ps.at? '|' and
        end_offset = s.index(?>, ps.pointer + 1) then
      target = ps[ps.pointer + 1 ... end_offset]
      if link_like? target then
        stack.ennode(:link,
            Fabricator::MF::END_LINK).target = target
        ps.pointer = end_offset + 1
      else
        # False alarm: this is not a link, after all.
        stack.cancel_link
        stack.last.content.push OpenStruct.new(
          type: :plain,
          data: '|',
        )
        ps.pointer += 1
      end

    elsif stack.last.mode & Fabricator::MF::END_LINK != 0 and
        ps.at? '>' then
      j = stack.rindex do |x|
        x.term_type == Fabricator::MF::END_LINK
      end
      target = ps[stack[j].start_offset + 1 ... ps.pointer]
      if link_like? target then
        stack[j .. -1] = []
        stack.last.content.push OpenStruct.new(
            type: :link,
            implicit_face: true,
            target: target,
            content: [OpenStruct.new(
              type: :plain,
              data: target,
            )],
        )
      else
        # False alarm: this is not a link, after all.
        stack.cancel_link
        stack.last.content.push OpenStruct.new(
          type: :plain,
          data: '>',
        )
      end
      ps.pointer += 1

    elsif ps.at? ' ' then
      ps.pointer += 1
      while ps.at? ' ' do
        ps.pointer += 1
      end
      stack.last.content.push OpenStruct.new(type: :space)

    elsif ps.at? "\u00A0" then
      stack.last.content.push OpenStruct.new(type: :nbsp)
      ps.pointer += 1

    else
      j = ps.pointer + 1
      while j < s.length and !" */<>[_|".include? ps[j] do
        j += 1
      end
      stack.last.content.push OpenStruct.new(
          type: :plain,
          data: String.new(ps[ps.pointer ... j]),
      )
      ps.pointer = j
    end
  end
  while stack.length > 1 do
    stack.unspawn
  end
  return stack.last.content
end
plan_to_write_out(results) click to toggle source

Take a [[results]] record from tangling and construct a matching [[proc]] to be stored in the [[writeout_plan]].

# File lib/mau/fabricator.rb, line 1159
def plan_to_write_out results
  return proc do |output_filename|
    File.write output_filename, results.content
    puts "Tangled #{results.filename},"
    if results.line_count != 1 then
      print "  #{results.line_count} lines"
    else
      print "  #{results.line_count} line"
    end
    puts " (#{results.nonblank_line_count} non-blank),"
    if results.longest_line_length != 1 then
      puts "  longest #{results.longest_line_length} chars."
    else
      puts "  longest #{results.longest_line_length} char."
    end
    if results.root_type == '.script' and
        !Fabricator::WINDOWS_HOSTED_P then
      stat = File.stat output_filename
      m = stat.mode
      uc = ""
      [(m |= 0o100), (uc << "u")] if m & 0o400 != 0
      [(m |= 0o010), (uc << "g")] if m & 0o040 != 0
      [(m |= 0o001), (uc << "o")] if m & 0o004 != 0
      File.chmod m, output_filename
      puts "Set %s+x on %s, resulting in %03o" % [
        uc,
        output_filename,
        m & 0o777,
      ]
    end
  end
end
show_warnings(fabric) click to toggle source
# File lib/mau/fabricator.rb, line 956
def show_warnings fabric
  fabric.warnings.each do |warning|
    $stderr.puts "%s: %s" %
        [format_location(warning.loc), warning.message]
  end
  return
end
weave_ctxt(fabric, port, width: 80, pseudographics: Fabricator::UNICODE_PSEUDOGRAPHICS) click to toggle source
# File lib/mau/fabricator.rb, line 1323
def weave_ctxt fabric, port,
    width: 80,
    pseudographics: Fabricator::UNICODE_PSEUDOGRAPHICS
  wr = Fabricator::Text_Wrapper.new port,
      width: width,
      pseudographics: pseudographics
  unless fabric.warnings.empty? then
    wr.styled :section_title do
      wr.add_plain 'Warnings'
    end
    wr.linebreak
    wr.linebreak
    weave_ctxt_warning_list fabric.warnings, wr
    wr.linebreak
  end
  toc_generated = false
  fabric.presentation.each do |element|
    case element.type
    when :title then
      if !toc_generated then
        weave_ctxt_toc fabric.toc, wr
        toc_generated = true
      end
      wr.styled :section_title do
        wr.add_plain "#{element.number}."
        wr.add_space
        wr.hang do
          wr.add_nodes element.content
        end
      end
      wr.linebreak
      wr.linebreak
    when :section then
      rubricated = element.elements[0].type == :rubric
      # If we're encountering the first rubric/title, output
      # the table of contents.
      if rubricated and !toc_generated then
        weave_ctxt_toc fabric.toc, wr
        toc_generated = true
      end

      start_index = 0 # index of the first non-special child
      if rubricated then
        start_index += 1
        wr.styled :rubric do
          wr.add_plain "§%i." % element.section_number
          wr.add_space
          wr.add_nodes element.elements.first.content
        end
      else
        wr.styled :section_number do
          wr.add_plain "§%i." % element.section_number
        end
      end

      # If the rubric or the section sign is followed by a
      # paragraph, a chunk header, or a divert, we'll output
      # it in the same paragraph.
      starter = element.elements[start_index]
      if starter then
        case starter.type
        when :paragraph, :divert, :chunk then
          wr.add_space
          weave_ctxt_section_part starter, fabric, wr
          start_index += 1
        else
          wr.linebreak
        end
      end

      # Finally, the blank line that separates the special
      # paragraph from the section's body, if any.
      wr.linebreak

      element.elements[start_index .. -1].each do |child|
        weave_ctxt_section_part child, fabric, wr
        wr.linebreak
      end

      unless (element.warnings || []).empty? then
        weave_ctxt_warning_list element.warnings, wr,
            inline: true, indent: false
        wr.linebreak
      end
    else raise 'data structure error'
    end
  end
  return
end
weave_ctxt_block(element, wr) click to toggle source
# File lib/mau/fabricator.rb, line 1517
def weave_ctxt_block element, wr
  element.lines.each do |line|
    wr.styled :block_frame do
      wr.add_pseudographics :block_margin
    end
    wr.styled :monospace do
      wr.add_plain line
    end
    wr.linebreak
  end
  return
end
weave_ctxt_chunk_header(element, wr) click to toggle source
# File lib/mau/fabricator.rb, line 1499
def weave_ctxt_chunk_header element, wr
  wr.styled :chunk_header do
    wr.add_pseudographics :before_chunk_name
    if element.root_type then
      wr.styled :root_type do
        wr.add_plain element.root_type
      end
      wr.add_space
    end
    wr.add_nodes(
        parse_markup(element.name, Fabricator::MF::LINK))
    wr.add_pseudographics :after_chunk_name
    wr.add_plain ":"
  end
  wr.linebreak
  return
end
weave_ctxt_list(items, wr) click to toggle source
# File lib/mau/fabricator.rb, line 1613
def weave_ctxt_list items, wr
  items.each do |item|
    wr.add_pseudographics :bullet
    wr.add_plain " "
    wr.hang do
      wr.add_nodes item.content
    end
    wr.linebreak
    unless (item.warnings || []).empty? then
      wr.hang do
        weave_ctxt_warning_list item.warnings, wr,
            inline: true
      end
    end
    if item.sublist then
      wr.add_plain "  "
      wr.hang do
        weave_ctxt_list item.sublist.items, wr
      end
    end
  end
  return
end
weave_ctxt_section_part(element, fabric, wr) click to toggle source
# File lib/mau/fabricator.rb, line 1437
def weave_ctxt_section_part element, fabric, wr
  case element.type
  when :paragraph then
    wr.add_nodes element.content
    wr.linebreak

  when :divert, :chunk, :diverted_chunk then
    if [:divert, :chunk].include? element.type then
      weave_ctxt_chunk_header element, wr
      weave_ctxt_warning_list element.warnings, wr,
          inline: true
    end
    if [:chunk, :diverted_chunk].include? element.type then
      wr.styled :chunk_frame do
        wr.add_pseudographics element.initial ?
          :initial_chunk_margin :
          :chunk_margin
      end
      wr.styled :monospace do
        element.content.each do |node|
          case node.type
          when :verbatim then
            wr.add_plain node.data
          when :newline then
            wr.linebreak
            wr.styled :chunk_frame do
              wr.add_pseudographics :chunk_margin
            end
          when :use then
            weave_ctxt_use node, wr
          else raise 'data structure error'
          end
        end
      end
      wr.linebreak
      if element.final then
        wr.styled :chunk_frame do
          wr.add_pseudographics :final_chunk_marker
        end
        wr.linebreak
      end
      weave_ctxt_warning_list element.warnings, wr,
          inline: true
      if element.final then
        wr.styled :chunk_xref do
          wr.add_nodes xref_chain(element, fabric)
        end
        wr.linebreak
      end
    end

  when :list then
    weave_ctxt_list element.items, wr

  when :block then
    weave_ctxt_block element, wr
  else
    raise 'data structure error'
  end
  return
end
weave_ctxt_toc(toc, wr) click to toggle source
# File lib/mau/fabricator.rb, line 1637
def weave_ctxt_toc toc, wr
  if toc.length >= 2 then
    wr.styled :section_title do
      wr.add_plain 'Contents'
    end
    wr.linebreak; wr.linebreak
    rubric_level = 0
    toc.each do |entry|
      case entry.type
      when :title then
        rubric_level = entry.level - 1 + 1
        wr.add_plain '  ' * (entry.level - 1)
        wr.add_plain entry.number + '.'
        wr.add_space
        wr.hang do
          wr.add_nodes entry.content
        end

      when :rubric then
        wr.add_plain '  ' * rubric_level
        wr.add_plain '§%i.' % entry.section_number
        wr.add_space
        wr.hang do
          wr.add_nodes entry.content
        end

      else
        raise 'assertion failed'
      end
      wr.linebreak
    end
    wr.linebreak
  end
  return
end
weave_ctxt_use(node, wr) click to toggle source
# File lib/mau/fabricator.rb, line 1530
def weave_ctxt_use node, wr
  wr.styled :use do
    wr.add_pseudographics :before_chunk_name
    if node.clearindent then
      wr.add_plain ".clearindent "
    end
    wr.add_nodes parse_markup(node.name, Fabricator::MF::LINK)
    if node.vertical_separation then
      wr.add_plain " " + node.vertical_separation
    end
    if node.postprocess then
      wr.add_plain " " + node.postprocess
    end
    wr.add_pseudographics :after_chunk_name
  end
  return
end
weave_ctxt_warning_list(list, wr, inline: false, indent: true) click to toggle source
# File lib/mau/fabricator.rb, line 1413
def weave_ctxt_warning_list list, wr, inline: false,
    indent: true
  list.to_a.each do |warning|
    wr.styled inline ? :inline_warning : :null do
      wr.add_plain (indent ? '  ' : '') + '!!! ' if inline
      wr.add_plain format_location(warning.loc)
      wr.add_plain ':'
      wr.add_space
      wr.hang do
        warning.message.split(/(\s+)/).
            each_with_index do |part, i|
          if i.even? then
            wr.add_plain part
          else
            wr.add_space part
          end
        end
      end
    end
    wr.linebreak
  end
  return
end
weave_html(fabric, port, title: nil, link_css: []) click to toggle source
# File lib/mau/fabricator.rb, line 1673
  def weave_html fabric, port,
      title: nil,
      link_css: []
    title ||= "(Untitled)"
    port.puts '<!doctype html>'
    port.puts '<html>'
    port.puts '<head>'
    port.puts "<meta http-equiv='Content-type' " +
        "content='text/html; charset=utf-8' />"
    port.puts "<title>#{title.to_xml}</title>"
    if link_css.empty? then
      port.puts "<style type='text/css'>"
      port.puts '/**** Fonts ****/
@import url("http://fonts.googleapis.com/css?family=Roboto");
@import url("http://fonts.googleapis.com/css?family=Cousine");
/**** Rules ****/
body, .maui-transclude {
  font-family: "Roboto", sans-serif; }

pre, tt, code {
  font-family: "Cousine", monospace; }

body {
  colour: black;
  background: white; }

tt, code {
  color: forestgreen; }

.maui-inline-warnings {
  color: red; }

.maui-warnings tt {
  color: inherit; }

.maui-rubric {
  color: crimson; }

ul.maui-warnings {
  padding-left: 0; }
  ul.maui-warnings > li {
    list-style: none; }

.maui-chunk-body {
  margin-left: 20px;
  border-left: 2px solid #cccccc;
  padding-left: 5px; }

.maui-initial-chunk > .maui-chunk-body:before {
  content: "";
  display: block;
  width: 22px;
  border-top: solid 2px #cccccc;
  margin-left: -27px; }

.maui-final-chunk > .maui-chunk-body:after {
  content: "";
  display: block;
  margin-left: -7px;
  width: 40px;
  border-bottom: solid 2px #cccccc; }

.maui-chunk-body, .maui-chunk > .maui-warnings {
  margin-top: 0;
  margin-bottom: 0; }

.maui-chunk {
  margin-top: 16px;
  margin-bottom: 16px; }

.maui-chunk-xref {
  font-size: small;
  font-style: italic;
  margin-left: 22px; }

/* Backwards compatibility with pre-HTML5 browsers */
section {
  display: block; }'
      port.puts "</style>"
    else
      link_css.each do |link|
        port.puts ("<link rel='stylesheet' type='text/css' " +
            "href='%s' />") % link.to_xml
      end
    end
    port.puts '</head>'
    port.puts '<body>'
    port.puts
    port.puts "<h1>#{title.to_xml}</h1>"
    unless fabric.warnings.empty? then
      port.puts "<h2>Warnings</h2>"
      port.puts
      weave_html_warning_list fabric.warnings, port
      port.puts
    end
    toc_generated = false
    fabric.presentation.each do |element|
      case element.type
      when :title then
        if !toc_generated then
          weave_html_toc fabric.toc, port
          toc_generated = true
        end
        port.print '<h%i' % (element.level + 1)
        port.print " id='%s'" % "T.#{element.number}"
        port.print '>'
        port.print "#{element.number}. "
        htmlify element.content, port
        port.puts '</h%i>' % (element.level + 1)
      when :section then
        rubricated = element.elements[0].type == :rubric
        # If we're encountering the first rubric/title, output
        # the table of contents.
        if rubricated and !toc_generated then
          weave_html_toc fabric.toc, port
          toc_generated = true
        end

        start_index = 0
        port.puts "<section class='maui-section' id='%s'>" %
            "S.#{element.section_number}"
        port.puts
        port.print "<p>"
        port.print "<b class='%s'>" %
            (rubricated ? 'maui-rubric' : 'maui-section-number')
        port.print "\u00A7#{element.section_number}."
        if rubricated then
          port.print " "
          htmlify element.elements[start_index].content, port
          start_index += 1
        end
        port.print "</b>"
        subelement = element.elements[start_index]
        warnings = nil
        case subelement && subelement.type
          when :paragraph then
            port.print " "
            htmlify subelement.content, port
            start_index += 1
          when :divert then
            port.print " "
            weave_html_chunk_header subelement, 'maui-divert',
                port, tag: 'span'
            warnings = subelement.warnings
            start_index += 1
        end
        port.puts "</p>"
        if warnings then
          weave_html_warning_list warnings, port, inline: true
        end
        port.puts
        element.elements[start_index .. -1].each do |child|
          weave_html_section_part child, fabric, port
          port.puts
        end
        unless (element.warnings || []).empty? then
          weave_html_warning_list element.warnings, port,
              inline: true
          port.puts
        end
        port.puts "</section>"
      else raise 'data structure error'
      end
      port.puts
    end
    port.puts '</html>'
    port.puts '</body>'
    port.puts '</html>'
    return
  end
weave_html_chunk_body(element, port) click to toggle source
# File lib/mau/fabricator.rb, line 1980
def weave_html_chunk_body element, port
  port.print "<pre class='maui-chunk-body'>"
  element.content.each do |node|
    case node.type
    when :verbatim then
      port.print node.data.to_xml
    when :newline then
      port.puts
    when :use then
      port.print "<span class='maui-transclude'>"
      port.print "&#xAB;"
      if node.clearindent then
        port.print ".clearindent "
      end
      htmlify(
          parse_markup(node.name, Fabricator::MF::LINK),
          port)
      if node.vertical_separation then
        port.print " " + node.vertical_separation.to_xml
      end
      if node.postprocess then
        port.print " " + node.postprocess.to_xml
      end
      port.print "&#xBB;"
      port.print "</span>"
    else raise 'data structure error'
    end
  end
  port.puts "</pre>"
  return
end
weave_html_chunk_header(element, cls, port, tag: 'div') click to toggle source
# File lib/mau/fabricator.rb, line 1965
def weave_html_chunk_header element, cls, port, tag: 'div'
  port.print "<#{tag} class='%s'>" % cls
  port.print "&#xAB;"
  if element.root_type then
    port.print "<u>%s</u> " % element.root_type.to_xml
  end
  htmlify(
      parse_markup(element.name, Fabricator::MF::LINK),
      port)
  port.print "&#xBB;:"
  port.print "</#{tag}>"
  # Note that we won't output a trailing linebreak here.
  return
end
weave_html_list(items, port) click to toggle source
# File lib/mau/fabricator.rb, line 1945
def weave_html_list items, port
  port.puts "<ul>"
  items.each do |item|
    port.print "<li>"
    htmlify item.content, port
    if item.sublist then
      port.puts
      weave_html_list item.sublist.items, port
    end
    unless (item.warnings || []).empty? then
      port.puts
      weave_html_warning_list item.warnings, port,
          inline: true
    end
    port.puts "</li>"
  end
  port.puts "</ul>"
  return
end
weave_html_section_part(element, fabric, port) click to toggle source
# File lib/mau/fabricator.rb, line 1844
def weave_html_section_part element, fabric, port
  case element.type
  when :paragraph then
    port.print "<p>"
    htmlify element.content, port
    port.puts "</p>"

  when :list then
    weave_html_list element.items, port

  when :divert then
    weave_html_chunk_header element, 'maui-divert',
        port
    port.puts
    weave_html_warning_list element.warnings, port,
        inline: true

  when :chunk, :diverted_chunk then
    port.print "<div class='maui-chunk"
    port.print " maui-initial-chunk" if element.initial
    port.print " maui-final-chunk" if element.final
    port.print "'>"
    if element.type == :chunk then
      weave_html_chunk_header element, 'maui-chunk-header',
          port
      port.puts
    end
    weave_html_chunk_body element, port
    unless (element.warnings || []).empty? then
      weave_html_warning_list element.warnings, port,
          inline: true
    end
    if element.final then
      port.print "<div class='maui-chunk-xref'>"
      htmlify(
          xref_chain(element, fabric, dash: "\u2013"),
          port)
      port.puts "</div>"
    end
    port.puts "</div>"

  when :block then
    port.print "<pre class='maui-block'>"
    element.lines.each_with_index do |line, i|
      port.puts unless i.zero?
      port.print line.to_xml
    end
    port.puts "</pre>"
  else
    raise 'data structure error'
  end
  return
end
weave_html_toc(toc, port) click to toggle source
# File lib/mau/fabricator.rb, line 1898
def weave_html_toc toc, port
  if toc.length >= 2 then
    port.puts "<h2>Contents</h2>"
    port.puts
    last_level = 0
    # What level should the rubrics in the current
    # (sub(sub))chapter appear at?
    rubric_level = 1
    toc.each do |entry|
      if entry.type == :rubric then
        level = rubric_level
      else
        level = entry.level
        rubric_level = entry.level + 1
      end
      if level > last_level then
        raise 'assertion failed' \
            unless level == last_level + 1
        port.print "\n<ul><li>"
      elsif level == last_level then
        port.print "</li>\n<li>"
      else
        port.print "</li></ul>" * (last_level - level) +
            "\n<li>"
      end
      case entry.type
      when :title then
        port.print "#{entry.number}. "
        port.print "<a href='#T.#{entry.number}'>"
        htmlify entry.content, port
        port.print "</a>"
      when :rubric then
        port.print "\u00A7#{entry.section_number}. "
        port.print "<a href='#S.#{entry.section_number}'>"
        htmlify entry.content, port
        port.print "</a>"
      else
        raise 'assertion failed'
      end
      last_level = level
    end
    port.puts "</li></ul>" * last_level
    port.puts
  end
  return
end
weave_html_warning_list(list, port, inline: false) click to toggle source
# File lib/mau/fabricator.rb, line 2012
def weave_html_warning_list list, port, inline: false
  if list and !list.empty? then
    port.print "<ul class='maui-warnings"
    port.print " maui-inline-warnings" if inline
    port.puts "'>"
    list.each do |warning|
      port.print "<li"
      port.print " id='W.#{warning.number}'" if inline
      port.print ">"
      port.print "!!! " if inline
      if !inline and warning.inline then
        port.print "<a href='#W.%i'>" % warning.number
      end
      port.print "<tt>%s</tt>" %
          format_location(warning.loc).to_xml
      port.print ": " + warning.message
      port.print "</a>" if !inline and warning.inline
      port.puts "</li>"
    end
    port.puts "</ul>"
  end
  return
end
xref_chain(element, fabric, dash: "-") click to toggle source

Given a chunk, prepare its transclusion summary as a list of markup nodes. Should only be used on chunks that are the last in a chunk chain (i.e., that have [[final]] set).

# File lib/mau/fabricator.rb, line 1551
def xref_chain element, fabric, dash: "-"
  xref = markup
  if element.initial then
    xref.words "This chunk is "
  else
    xref.words "These chunks are "
  end
  cbn_entry = fabric.chunks_by_name[element.name]
  transcluders = cbn_entry.transcluders
  if transcluders then
    xref.words "transcluded by "
    xref.push *commatise_oxfordly(
        transcluders.map{|ref| markup.
            node(:mention_chunk, name: ref.name).
            space.
            plain("(§%i)" % ref.section_number)
        })
  else
    if cbn_entry.root_type then
      xref.words "solely a transclusion root"
    else
      xref.words "never transcluded"
    end
  end
  xref.words " and "
  tlocs = element.divert ?
      element.divert.chain_tangle_locs :
      element.tangle_locs
  if tlocs then
    xref.
        words("tangled to ").
        push(*commatise_oxfordly(
        tlocs.map{|range| markup.
            plain(format_location_range(range, dash: dash))
        })).
        plain(".")
  else
    xref.words "never tangled."
  end
  return xref
end