
Markdown.pudu
Pudu122 lines5.1 KB
1/** @Docgen.Markdown.Module — article text parsed into metadata and a block tree */2module PuduLangDocgen.Markdown34import Std.Map as Map5import Std.Set as Set6import PuduLangDocgen.Constants.Codes as Codes7import PuduLangDocgen as Docgen8import PuduLangDocgen.Markdown.Blocks as Blocks9import PuduLangDocgen.Markdown.Directives as Directives10import PuduLangDocgen.Markdown.FrontMatter as FrontMatter11import PuduLangDocgen.Markdown.Layout as Layout12import PuduLangDocgen.Markdown.Phrase as Phrase13import PuduLangDocgen.Markdown.Render as Render14import PuduLangDocgen.Markdown.Snippet as Snippet15import PuduLangDocgen.Markdown.Syntax as Syntax1617/** @Docgen.Markdown.Article — front matter, blocks, and parse diagnostics of one file */18export type Article = { meta: Array[(Str, Docgen.Meta)], blocks: Array[Syntax.Block], diagnostics: Array[Docgen.Diagnostic] }192021export const ALERTS: Array[Str] = ["NOTE", "TIP", "IMPORTANT", "CAUTION", "WARNING"]22232425export fn parse(origin: Str, text: Str, files: &Map[Str, Str], alerts: &Array[Str]) -> Article {26 let header = match FrontMatter.split(text) {27 case Ok(found) => found28 case Err((line, message)) => { return Article{meta: [], blocks: [], diagnostics: [Docgen.error(Codes.FRONT_MATTER_INVALID, origin, line, message)]} }29 }30 let (found, rest) = Layout.definitions(&header.lines)31 let kinds = setOf(ALERTS.concat(alerts.map(|kind: Str| kind.toUpper())))32 let env = Blocks.Env{..Blocks.environment(origin, files, &kinds), definitions: found}33 let parsed = Blocks.parse(&rest, &env, 0)34 Article{meta: header.meta, blocks: parsed.blocks, diagnostics: parsed.diagnostics}35}36373839export fn fragment(text: Str, context: &Phrase.Scope, demote: Int) -> Render.Rendered {40 let article = parse(context.source, text, &mapOf([]), &[])41 let blocks = article.blocks.map(fn(block: Syntax.Block) -> Syntax.Block {42 match block {43 case Syntax.Heading(title) => Syntax.Heading(Syntax.Title{..title, level: if title.level + demote > 6 { 6 } else { title.level + demote }})44 case other => other45 }46 })47 let rendered = Render.render(&blocks, context)48 Render.Rendered{..rendered, diagnostics: article.diagnostics.concat(rendered.diagnostics)}49}505152export fn diagrams(blocks: &Array[Syntax.Block]) -> Array[Str] {53 var found: Array[Str] = []54 for block in *blocks {55 let nested = match block {56 case Syntax.CodeBlock(sample) => {57 let language = sample.language.toLower()58 if language == "plantuml" || language == "puml" { [sample.text] } else { [] }59 }60 case Syntax.Quote(children) => diagrams(&children)61 case Syntax.Alert(notice) => diagrams(¬ice.blocks)62 case Syntax.Included(_path, children) => diagrams(&children)63 case Syntax.ListBlock(listing) => within(&listing.items.map(|item: Syntax.Item| item.blocks))64 case Syntax.TabGroup(group) => within(&group.map(|tab: Syntax.Tab| tab.blocks))65 case Syntax.Grid(columns) => within(&columns.map(|column: Syntax.Column| column.blocks))66 case Syntax.Footnote(_label, children) => diagrams(&children)67 case _ => []68 }69 for text in nested {70 if !found.contains(text) { found = found.push(text) }71 }72 }73 found74}757677fn within(groups: &Array[Array[Syntax.Block]]) -> Array[Str] {78 var found: Array[Str] = []79 for group in *groups { found = found.concat(diagrams(&group)) }80 found81}82838485export fn dependencies(origin: Str, text: Str) -> Array[Str] {86 var found: Array[Str] = []87 for line in text.split("\n") {88 var written: Array[Str] = []89 match Directives.include(line) {90 case Some((_title, path)) => { written = written.push(path) }91 case None => {}92 }93 match Directives.excerpt(line) {94 case Some(held) => { written = written.push(held.destination) }95 case None => {}96 }97 match Directives.colon(line) {98 case Some(held) => { if held.name == "code" { written = written.push(Directives.value(&held, "source")) } }99 case None => {}100 }101 var rest = line102 while rest.toUpper().contains("[!INCLUDE") {103 let at = rest.toUpper().indexOf("[!INCLUDE")104 rest = rest.drop(at + 9)105 let open = rest.indexOf("](")106 let close = rest.indexOf(")")107 if open >= 0 && close > open { written = written.push(rest.drop(open + 2).take(close - open - 2).trim()) }108 }109 for path in written {110 let target = match Snippet.selection(path) {111 case Ok(chosen) => chosen.path112 case Err(_) => path113 }114 match Layout.locate(origin, target) {115 case Ok(located) => { if !found.contains(located) { found = found.push(located) } }116 case Err(_) => {}117 }118 }119 }120 found121}122