//
// Copyright (c) 2011, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 15 Aug 11 Brian Frank Creation
//
using concurrent
using fandoc::FandocParser
using fandoc::Heading
**
** DocChapter models a fandoc "chapter" in a manual like docLang
**
const class DocChapter : Doc
{
** Constructor
internal new make(DocPodLoader loader, File f)
{
this.pod = loader.pod
this.name = f.name == "pod.fandoc" ? "pod-doc" : f.basename
this.loc = DocLoc("${pod}::${f.name}", 1)
this.doc = DocFandoc(this.loc, f.in.readAllStr)
this.qname = "$pod.name::$name"
// parse fandoc and build the headings tree
headingTop := DocHeading[,]
headingMap := Str:DocHeading[:]
meta := Str:Str[:]
try
{
// parse fandoc silently - don't worry about errors,
// we'll catch and report them at render time
parser := FandocParser()
parser.silent = true
fandocDoc := parser.parse(f.name, doc.text.in)
meta = fandocDoc.meta
fandocHeadings := fandocDoc.findHeadings
// map headings into tree structure
buildHeadingsTree(loader, fandocHeadings, headingTop, headingMap)
}
catch (Err e)
{
loader.err("Cannot parse fandoc chapter", loc, e)
}
this.headings = headingTop
this.headingMap = headingMap
this.meta = meta
}
private Void buildHeadingsTree(DocPodLoader loader, Heading[] fandoc, DocHeading[] top, Str:DocHeading map)
{
// if no headings just bail
if (fandoc.isEmpty) return
// first map Fandoc headings to DocHeadings and map by anchor id
headings := DocHeading[,]
children := DocHeading:DocHeading[][:]
fandoc.each |d|
{
id := d.anchorId
h := DocHeading { it.level = d.level; it.title = d.title; it.anchorId = id}
if (id == null) loader.err("Heading missing anchor id: $h.title", loc)
else if (map[id] != null) loader.err("Heading duplicate anchor id: $id", loc)
else map[id] = h
headings.add(h)
children[h] = DocHeading[,]
}
// now map into a tree structure
stack := DocHeading[,]
headings.each |h|
{
while (stack.peek != null && stack.peek.level >= h.level)
stack.pop
// top level heading
if (stack.isEmpty)
{
if (h.level != 2 && pod.name != "fandoc")
loader.err("Expected top-level heading to be level 2: $h.title", loc)
top.add(h)
}
// child level heading
else
{
if (stack.peek.level +1 != h.level)
loader.err("Expected heading to be level ${stack.peek.level+1}: $h.title", loc)
children[stack.peek].add(h)
}
stack.add(h)
}
// map children map to immutable list fields
children.each |kids, h| { h.childrenRef.val = kids.toImmutable }
}
** Pod which defines this chapter such as "docLang"
const DocPod pod
** Simple name of the chapter such as "Overview" or "pod-doc"
const Str name
** Document name under space is same as `name`
override Str docName() { name }
** The space for this doc is `pod`
override DocSpace space() { pod }
** Title is 'meta.title', or qualified name if not specified.
override Str title() { meta["title"] ?: qname }
** Use title for breadcrumb
override Str breadcrumb() { title }
** Default renderer is `DocChapterRenderer`
override Type renderer() { DocChapterRenderer# }
** Return if this chapter is the special "pod-doc" file
Bool isPodDoc() { name == "pod-doc" }
** Qualified name as "pod::name"
const Str qname
** Location for chapter file
const DocLoc loc
** Fandoc heating metadata
const Str:Str meta
** Chapter contents as Fandoc string
const DocFandoc doc
** Top-level chapter headings
const DocHeading[] headings
** Chapter number (one-based)
Int num() { numRef.val }
internal const AtomicInt numRef := AtomicInt()
** Summary for TOC
Str summary() { summaryRef.val }
internal const AtomicRef summaryRef := AtomicRef("")
** Previous chapter in TOC order or null if first
DocChapter? prev() { prevRef.val }
internal const AtomicRef prevRef := AtomicRef(null)
** Next chapter in TOC order or null if last
DocChapter? next() { nextRef.val }
internal const AtomicRef nextRef := AtomicRef(null)
** Get a chapter heading by its anchor id or raise NameErr/return null.
override DocHeading? heading(Str id, Bool checked := true)
{
h := headingMap[id]
if (h != null) return h
return super.heading(id, checked)
}
** Return qname
override Str toStr() { qname }
private const Str:DocHeading headingMap
** Index the chapter name and body
override Void onCrawl(DocCrawler crawler)
{
summary := DocFandoc(this.loc, this.summary)
crawler.addKeyword(name, title, summary, null)
crawler.addKeyword(qname, title, summary, null)
crawler.addFandoc(doc)
}
}
**
** DocHeader models a heading in a table of contents for pod/chapter.
**
const class DocHeading
{
** Constructor
internal new make(|This| f) { f(this) }
** Heading level, chapter top-level sections start at level 2
const Int level
** Display title for the heading
const Str title
** Anchor id for heading or null if not available
const Str? anchorId
** Children headings
DocHeading[] children() { childrenRef.val }
internal const AtomicRef childrenRef := AtomicRef()
}