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

Overwrite.pudu

Pudu116 lines5.0 KB

GitHub ↗
1/** @Docgen.Build.Overwrite — sections that amend documented identities from separate files */2module PuduLangDocgen.Build.Overwrite34import PuduLangDocgen.Api.Model as Model5import PuduLangDocgen.Constants.Codes as Codes6import PuduLangDocgen as Docgen7import PuduLangDocgen.Meta as Meta8import PuduLangDocgen.Yaml as Yaml910/** @Docgen.Build.Section — one amendment: target identity, metadata, and Markdown content */11export type Section = { uid: Str, meta: Array[(Str, Docgen.Meta)], content: Str, target: Str, source: Str, line: Int }1213/// Stand-in for the `*content` alias, which the YAML reader refuses.14const CONTENT: Str = "\u{1}content"1516/// The sections of an overwrite file. A header is a `---` block whose YAML is a mapping with a17/// `uid`; a later block whose YAML is not a mapping stays Markdown. A key whose value is18/// `*content` receives the section's Markdown instead of the body.19export fn parse(path: Str, text: Str) -> (Array[Section], Array[Docgen.Diagnostic]) {20  let lines = text.replace("\r\n", "\n").split("\n")21  var sections: Array[Section] = []22  var problems: Array[Docgen.Diagnostic] = []23  var index = 024  var current: Option[Section] = None25  var body: Array[Str] = []26  while index < lines.length() {27    if lines[index].trim() == "---" {28      var close = index + 129      while close < lines.length() && lines[close].trim() != "---" { close = close + 1 }30      if close < lines.length() {31        let header = lines.slice(index + 1, close).map(|line: Str| line.replace(": *content", ": \"" + CONTENT + "\"")).join("\n")32        match Yaml.decode(header) {33          case Ok(Docgen.Fields(fields)) => {34            sections = finish(sections, &current, &body)35            let uid = Meta.textOr(&fields, "uid", "")36            if uid.isEmpty() {37              problems = problems.push(Docgen.error(Codes.OVERWRITE_INVALID, path, index + 1, "overwrite header needs a uid"))38              current = None39            } else {40              var target = ""41              var meta: Array[(Str, Docgen.Meta)] = []42              for (key, held) in fields {43                if held == Docgen.Text(CONTENT) { target = key } else if key != "uid" { meta = meta.push((key, held)) }44              }45              current = Some(Section{uid: uid, meta: meta, content: "", target: target, source: path, line: index + 1})46            }47            body = []48            index = close + 149            continue50          }51          case Err(reason) => {52            if index == 0 { problems = problems.push(Docgen.error(Codes.OVERWRITE_INVALID, path, 1, "overwrite header: " + reason)) }53          }54          case _ => {}55        }56      }57    }58    body = body.push(lines[index])59    index = index + 160  }61  (finish(sections, &current, &body), problems)62}6364/// Sections with the open one closed over its collected body.65fn finish(sections: Array[Section], current: &Option[Section], body: &Array[Str]) -> Array[Section] {66  match current {67    case Some(open) => sections.push(Section{..open, content: body.join("\n").trim()})68    case None => sections69  }70}7172/// Metadata the sections give an identity, later sections winning.73export fn metaFor(sections: &Array[Section], uid: Str) -> Array[(Str, Docgen.Meta)] {74  var result: Array[(Str, Docgen.Meta)] = []75  for section in *sections {76    if section.uid == uid { result = Meta.merge(&result, &section.meta) }77  }78  result79}8081/// Modules with their declarations' documentation amended: content aimed at `summary` or82/// `remarks` replaces that part, content aimed at `example` adds an example section, and83/// content aimed at nothing is added after the existing documentation.84export fn units(all: &Array[Model.Unit], sections: &Array[Section]) -> Array[Model.Unit] {85  if sections.isEmpty() { return *all }86  all.map(fn(unit: Model.Unit) -> Model.Unit {87      let members = unit.members.map(fn(member: Model.Member) -> Model.Member {88          let inner = member.members.map(|held: Model.Member| Model.Member{..held, doc: amended(held.uid, held.doc, sections)})89          Model.Member{..member, doc: amended(member.uid, member.doc, sections), members: inner}90        })91      Model.Unit{..unit, doc: amended(unit.uid, unit.doc, sections), members: members}92    })93}9495/// Documentation with every section for an identity applied in order.96fn amended(uid: Str, doc: Str, sections: &Array[Section]) -> Str {97  var result = doc98  for section in *sections {99    if section.uid != uid || section.content.isEmpty() { continue }100    let summary = Model.summary(result)101    let remarks = Model.remarks(result)102    result = if section.target == "summary" { joined(section.content, remarks) }103      else if section.target == "remarks" { joined(summary, section.content) }104      else if section.target == "example" { joined(result, "## Examples\n\n" + section.content) }105      else { joined(result, section.content) }106  }107  result108}109110/// Two pieces of Markdown separated by a blank line, dropping empty ones.111fn joined(first: Str, second: Str) -> Str {112  if first.trim().isEmpty() { return second }113  if second.trim().isEmpty() { return first }114  first + "\n\n" + second115}116