Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-docgen

Documentation publishing for Pudu: articles, API references, navigation, search, and static websites

0.1.0Apache-2.01

InstallClose

Markdown.pudu

Pudu122 lines5.1 KB

GitHub ↗
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] }1920/// Alert kinds every article may use.21export const ALERTS: Array[Str] = ["NOTE", "TIP", "IMPORTANT", "CAUTION", "WARNING"]2223/// An article written at a project-relative path. `files` holds the text of every file it may24/// include or excerpt; `alerts` adds alert kinds to the built-in ones.25export 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}3637/// Documentation text written inside another page, rendered with its headings lowered by38/// `demote` levels so they sit below the page's own sections.39export 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}5051/// Sources of the PlantUML diagrams in blocks, in order, each listed once.52export 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(&notice.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}7576/// Diagram sources of several block lists in order.77fn 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}8283/// Project-relative paths an article includes or excerpts, in first-mention order.84/// Paths that cannot be located are left for parsing to report.85export 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