module Quarto

Constants

EXTENSIONS_TO_SOURCE_FORMATS
SECTION_TEMPLATE
SPINE_TEMPLATE
VERSION
XINCLUDE_NS

Public Class Methods

configure() { |self| ... } click to toggle source
# File lib/quarto.rb, line 10
def self.configure
  yield self
end
stylesheets() click to toggle source
# File lib/quarto.rb, line 14
def self.stylesheets
  @stylesheets ||= [code_stylesheet]
end

Public Instance Methods

build_dir() click to toggle source
# File lib/quarto.rb, line 54
def build_dir
  "build"
end
code_stylesheet() click to toggle source
# File lib/quarto.rb, line 125
def code_stylesheet
  "#{build_dir}/code.css"
end
codex_file() click to toggle source
# File lib/quarto.rb, line 165
def codex_file
  "build/codex.xhtml"
end
configuration() click to toggle source
# File lib/quarto.rb, line 18
def configuration
  Quarto
end
create_codex_file(codex_file, spine_file) click to toggle source
# File lib/quarto.rb, line 169
def create_codex_file(codex_file, spine_file)
  expand_xinclude(codex_file, spine_file)
end
create_master_file(master_file, skeleton_file) click to toggle source
# File lib/quarto.rb, line 245
def create_master_file(master_file, skeleton_file)
  expand_xinclude(master_file, skeleton_file, format: false)
end
create_skeleton_file(skeleton_file, codex_file) click to toggle source
# File lib/quarto.rb, line 181
def create_skeleton_file(skeleton_file, codex_file)
  puts "scan #{codex_file} for source code listings"
  skel_doc = open(codex_file) do |f|
    Nokogiri::XML(f)
  end
  skel_doc.css("pre.sourceCode").each_with_index do |pre_elt, i|
    lang = pre_elt["class"].split[1]
    ext  = {"ruby" => "rb"}.fetch(lang){ lang.downcase }
    code     = pre_elt.at_css("code").text
    digest   = Digest::SHA1.hexdigest(code)
    listing_path = "#{listings_dir}/#{digest}.#{ext}"
    puts "extract listing #{i} to #{listing_path}"
    open(listing_path, 'w') do |f|
      f.write(strip_listing(code))
    end
    highlight_path = "#{highlights_dir}/#{digest}.html"
    inc_elt = skel_doc.create_element("xi:include") do |elt|
      elt["href"] = highlight_path
      elt.add_child(
        "<xi:fallback>"\
        "<p>[Missing code listing: #{highlight_path}]</p>"\
        "</xi:fallback>")
    end
    pre_elt.replace(inc_elt)
  end
  puts "create #{skeleton_file}"
  open(skeleton_file, "w") do |f|
    format_xml(f) do |format_input|
      skel_doc.write_xml_to(format_input)
    end
  end
end
create_spine_file(spine_file, section_files, options={}) click to toggle source
# File lib/quarto.rb, line 133
def create_spine_file(spine_file, section_files, options={})
  options = {stylesheets: configuration.stylesheets}.merge(options)
  puts "create #{spine_file}"
  doc = Nokogiri::XML.parse(SPINE_TEMPLATE)
  doc.root.add_namespace("xi", "http://www.w3.org/2001/XInclude")
  head_elt = doc.root.at_css("head")
  stylesheets = Array(options[:stylesheets])
  stylesheets.each do |stylesheet|
    head_elt.add_child(
      doc.create_element(
        "style",
        File.read(stylesheet)))
  end
  section_files.each do |section_file|
    doc.root["xml:base"] = ".."
    body = doc.root.at_css("body")
    body.add_child(doc.create_element("xi:include") do |inc_elt|
        inc_elt["href"]     = section_file
        inc_elt["xpointer"] = "xmlns(ns=http://www.w3.org/1999/xhtml)xpointer(//ns:body/*)"
        inc_elt.add_child(doc.create_element("xi:fallback") do |fallback_elt|
            fallback_elt.add_child(doc.create_element("p",
                "[Missing section: #{section_file}]"))
          end)
      end)
  end
  open(spine_file, 'w') do |f|
    format_xml(f) do |format_input|
      doc.write_to(format_input)
    end
  end
end
deliverable_dir() click to toggle source
# File lib/quarto.rb, line 249
def deliverable_dir
  "#{build_dir}/deliverables"
end
deliverable_files() click to toggle source
# File lib/quarto.rb, line 253
def deliverable_files
  [pdf_file]
end
export_command_for(source_file, export_file) click to toggle source
# File lib/quarto.rb, line 85
def export_command_for(source_file, export_file)
  %W[pandoc --no-highlight -w html5 -o #{export_file} #{source_file}]
end
export_dir() click to toggle source
# File lib/quarto.rb, line 71
def export_dir
  "build/exports"
end
export_files() click to toggle source
# File lib/quarto.rb, line 75
def export_files
  source_files.pathmap("#{export_dir}/%p").ext('.html')
end
export_for_section_file(section_file) click to toggle source
# File lib/quarto.rb, line 97
def export_for_section_file(section_file)
  section_file.pathmap("%{^#{section_dir},#{export_dir}}X%{xhtml,html}x")
end
format_of_source_file(source_file) click to toggle source
# File lib/quarto.rb, line 62
def format_of_source_file(source_file)
  ext = source_file.pathmap("%x")[1..-1]
  EXTENSIONS_TO_SOURCE_FORMATS.fetch(ext)
end
highlights_dir() click to toggle source
# File lib/quarto.rb, line 214
def highlights_dir
  "#{build_dir}/highlights"
end
highlights_needed_by(skeleton_file) click to toggle source
# File lib/quarto.rb, line 218
def highlights_needed_by(skeleton_file)
  doc = open(skeleton_file) do |f|
    Nokogiri::XML(f)
  end
  doc.xpath("//xi:include", "xi" => XINCLUDE_NS).map{|e| e["href"]}
end
listing_for_highlight_file(highlight_file) click to toggle source
# File lib/quarto.rb, line 225
def listing_for_highlight_file(highlight_file)
  base = highlight_file.pathmap("%n")
  FileList["#{listings_dir}/#{base}.*"].first
end
listings_dir() click to toggle source
# File lib/quarto.rb, line 177
def listings_dir
  "#{build_dir}/listings"
end
master_file() click to toggle source
# File lib/quarto.rb, line 241
def master_file
  "#{build_dir}/master.xhtml"
end
normalize_export(export_file, section_file, format) click to toggle source
# File lib/quarto.rb, line 101
def normalize_export(export_file, section_file, format)
  format ||= "NO_FORMAT_GIVEN"
  send("normalize_#{format}_export", export_file, section_file)
end
normalize_markdown_export(export_file, section_file) click to toggle source
# File lib/quarto.rb, line 106
def normalize_markdown_export(export_file, section_file)
  puts "normalize #{export_file} to #{section_file}"
  doc = open(export_file) do |f|
    Nokogiri::HTML(f)
  end
  normal_doc = Nokogiri::XML.parse(SECTION_TEMPLATE)
  normal_doc.at_css("body").replace(doc.at_css("body"))
  normal_doc.at_css("title").content = export_file.pathmap("%n")
  open(section_file, "w") do |f|
    format_xml(f) do |pipe_input|
      normal_doc.write_xml_to(pipe_input)
    end
  end
end
pdf_file() click to toggle source
# File lib/quarto.rb, line 257
def pdf_file
  "#{deliverable_dir}/book.pdf"
end
section_dir() click to toggle source
# File lib/quarto.rb, line 89
def section_dir
  "build/sections"
end
section_files() click to toggle source
# File lib/quarto.rb, line 93
def section_files
  export_files.pathmap("%{^#{export_dir},#{section_dir}}X%{html,xhtml}x")
end
skeleton_file() click to toggle source
# File lib/quarto.rb, line 173
def skeleton_file
  "#{build_dir}/skeleton.xhtml"
end
source_exts() click to toggle source
# File lib/quarto.rb, line 58
def source_exts
  EXTENSIONS_TO_SOURCE_FORMATS.keys
end
source_files() click to toggle source
# File lib/quarto.rb, line 67
def source_files
  FileList["**/*.{#{source_exts.join(',')}}"]
end
source_for_export_file(export_file) click to toggle source
# File lib/quarto.rb, line 79
def source_for_export_file(export_file)
  base = export_file.sub(/^#{export_dir}\//,'').ext('')
  pattern = "#{base}.{#{source_exts.join(',')}}"
  FileList[pattern].first
end
source_list_file() click to toggle source
# File lib/quarto.rb, line 121
def source_list_file
  "build/sources"
end
spine_file() click to toggle source
# File lib/quarto.rb, line 129
def spine_file
  "build/spine.xhtml"
end
strip_listing(code) click to toggle source

Strip extraneous whitespace from around a code listing

# File lib/quarto.rb, line 231
def strip_listing(code)
  code.gsub!(/\t/, "  ")
  lines  = code.split("\n")
  first_code_line = lines.index{|l| l =~ /\S/}
  last_code_line  = lines.rindex{|l| l =~ /\S/}
  lines = lines[first_code_line..last_code_line]
  indent = lines.map{|l| l.index(/[^ ]/)}.min
  lines.map{|l| l.slice(indent..-1)}.join("\n") + "\n"
end

Private Instance Methods

expand_xinclude(output_file, input_file, options={}) click to toggle source
# File lib/quarto.rb, line 272
def expand_xinclude(output_file, input_file, options={})
  options = {format: true}.merge(options)
  puts "expand #{input_file} to #{output_file}"
  cleanup_args = %W[--nsclean --xmlout]
  if options[:format]
    cleanup_args << "--format"
  end
  Open3.pipeline_r(
    %W[xmllint --xinclude --xmlout #{input_file}],
    # In order to clean up extraneous namespace declarations we need a second
    # xmllint process
    ["xmllint",  *cleanup_args, "-"]) do |output, wait_thr|
    open(output_file, 'w') do |f|
      IO.copy_stream(output, f)
    end
  end
end
format_xml(output_io) { |stdin| ... } click to toggle source
# File lib/quarto.rb, line 263
def format_xml(output_io)
  Open3.popen2(*%W[xmllint --format --xmlout -]) do
    |stdin, stdout, wait_thr|
    yield(stdin)
    stdin.close
    IO.copy_stream(stdout, output_io)
  end
end