resources/asciidoctor/lib/alternative_language_lookup/alternative.rb (105 lines of code) (raw):
# frozen_string_literal: true
require_relative '../log_util'
module AlternativeLanguageLookup
##
# Load alternative examples in alternative languages. Creating this class is
# comparatively heavy because it parses the example. It'll also log warnings
# if there are problems with the example. So only create it if you plan to
# use the example.
class Alternative
include LogUtil
LAYOUT_DESCRIPTION = <<~LOG
Alternative language must be a code block followed optionally by a callout list
LOG
def initialize(document, lang, path)
@document = document
@lang = lang
@path = path
@counter = @document.attr 'alternative_language_counter', 0
@listing_text = @colist_text = nil
load
finish_preparations if validate
end
##
# A block for the alternative listing that can be inserted into the main
# document if we've successfully loaded, validated, and munged the
# alternative. nil otherwise.
def listing(parent)
return unless @listing_text
Asciidoctor::Block.new parent, :pass, source: @listing_text
end
##
# A block for the alternative callout list that can be inserted into the
# main document if the alternative contains a callout list and we've
# successfully loaded, validated, and munged the alternative. nil otherwise.
def colist(parent)
return unless @colist_text
Asciidoctor::Block.new parent, :pass, source: @colist_text
end
def load
# Parse the included portion as asciidoc but not as a "child" document
# because that is for parsing text we've already parsed once. This is
# text that we're detecting very late in the process.
@child = Asciidoctor::Document.new "include::#{@path}[]", load_opts
@child.parse
end
def load_opts
{
attributes: @document.attributes.dup,
safe: @document.safe,
backend: @document.backend,
doctype: Asciidoctor::DEFAULT_DOCTYPE,
sourcemap: @document.sourcemap,
base_dir: @document.base_dir,
to_dir: @document.options[:to_dir],
}
end
def validate
@listing, @colist, rest = @child.blocks
unless @listing || !rest.empty?
warn block: @child, message: <<~LOG.strip
#{LAYOUT_DESCRIPTION} but was:
#{@child.blocks}
LOG
return false
end
check_listing & check_colist
end
##
# Return false if the block in listing position isn't a listing or is
# otherwise invalid. Otherwise returns true.
def check_listing
unless @listing.context == :listing
warn block: @listing, message: <<~LOG.strip
#{LAYOUT_DESCRIPTION} but the first block was a #{@listing.context}.
LOG
return false
end
validate_listing_langauge
end
def validate_listing_langauge
unless (listing_lang = @listing.attr 'language') == @lang
warn block: @listing, message: <<~LOG.strip
Alternative language listing must have lang=#{@lang} but was #{listing_lang}.
LOG
return false
end
true
end
##
# Return false if block in the colist position isn't a colist.
# Otherwise returns true.
def check_colist
return true unless @colist
unless @colist.context == :colist
warn block: @colist, message: <<~LOG.strip
#{LAYOUT_DESCRIPTION} but the second block was a #{@colist.context}.
LOG
return false
end
true
end
##
# Finish preparing the alternative after we know it is valid.
def finish_preparations
munge
@document.attributes['alternative_language_counter'] = @counter + 1
@listing_text = @listing.convert
@colist_text = @colist&.convert
end
##
# Munge the loaded document into something we can include in the
# main document.
def munge
@listing.attributes['role'] = 'alternative'
# Munge the callouts so they don't collide with the parent doc
@listing.document.callouts.current_list.each do |co|
co[:id] = munge_coid co[:id]
end
return unless @colist
@colist.attributes['role'] = "alternative lang-#{@lang}"
munge_list_coids
end
##
# Munge the link targets so they link properly to the munged ids in the
# alternate example
def munge_list_coids
@colist.items.each do |item|
coids = item.attr 'coids'
next unless coids
newcoids = []
coids.split(' ').each do |coid|
newcoids << munge_coid(coid)
end
item.attributes['coids'] = newcoids.join ' '
end
end
def munge_coid(coid)
"A#{@counter}-#{coid}"
end
end
end