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

Export.pudu

Pudu131 lines8.0 KB

GitHub ↗
1/** @Docgen.Api.Export — extracted declarations written as model, Markdown, or API page files */2module PuduLangDocgen.Api.Export34import Std.Json as Json5import PuduLangDocgen.Api.Catalog as Catalog6import PuduLangDocgen.Api.Model as Model7import PuduLangDocgen as Docgen8import PuduLangDocgen.Yaml as Yaml910/// First line of every API page document.11const API_PAGE: Str = "#YamlMime:ApiPage\n"1213/// Formats metadata may be written in.14export const FORMATS: Array[Str] = ["json", "markdown", "apiPage"]1516/// Files for selected modules in a format, with paths relative to the metadata folder and a17/// `toc.yml` listing modules and their types.18export fn files(units: &Array[Model.Unit], format: Str) -> Array[(Str, Str)] {19  var result: Array[(Str, Str)] = []20  let extension = if format == "markdown" { ".md" } else if format == "apiPage" { ".yml" } else { ".json" }21  for unit in *units {22    if format == "json" { result = result.push((unit.uid + ".json", Json.encodePretty(&unitJson(&unit)))) }23      else {24      result = result.push((unit.uid + extension, if format == "markdown" { moduleMarkdown(&unit) } else { API_PAGE + Yaml.encode(&modulePage(&unit)) }))25      for member in unit.members {26        if Model.isType(member.kind) { result = result.push((member.uid + extension, if format == "markdown" { typeMarkdown(&unit, &member) } else { API_PAGE + Yaml.encode(&typePage(&unit, &member)) })) }27      }28    }29  }30  let options = Catalog.Options{..Catalog.defaults(), dest: ""}31  let toc = Catalog.toc(units, &options).map(fn(item: Docgen.TocItem) -> Docgen.Meta {32      let children = item.children.map(|child: Docgen.TocItem| Docgen.Fields([("name", Docgen.Text(child.title)), ("href", Docgen.Text(child.href.replace(".html", extension)))]))33      Docgen.Fields([("name", Docgen.Text(item.title)), ("href", Docgen.Text(item.href.replace(".html", extension))), ("items", Docgen.Items(children))])34    })35  result.push(("toc.yml", Yaml.encode(&Docgen.Items(toc))))36}3738/// A module's full model as JSON.39fn unitJson(unit: &Model.Unit) -> Json.Json {40  Json.object(&[41      ("uid", Json.Text(unit.uid)), ("path", Json.Text(unit.path)), ("line", Json.Number(unit.line)), ("doc", Json.Text(unit.doc)),42      ("imports", Json.list(&unit.imports.map(fn(entry: (Str, Str)) -> Json.Json {43              let (alias, name) = entry44              Json.object(&[("alias", Json.Text(alias)), ("module", Json.Text(name))])45            }))),46      ("members", Json.list(&unit.members.map(|member: Model.Member| memberJson(&member)))),47      ("implementations", Json.list(&unit.implementations.map(|held: Model.Implementation| Json.object(&[("trait", Json.Text(held.contract)), ("target", Json.Text(held.target)), ("line", Json.Number(held.line)), ("methods", Json.list(&held.methods.map(|method: Model.Member| memberJson(&method))))]))))48    ])49}5051/// One declaration and its nested members as JSON.52fn memberJson(member: &Model.Member) -> Json.Json {53  Json.object(&[54      ("uid", Json.Text(member.uid)), ("name", Json.Text(member.name)), ("kind", Json.Text(member.kind)), ("signature", Json.Text(member.signature)),55      ("doc", Json.Text(member.doc)), ("line", Json.Number(member.line)), ("exported", Json.Boolean(member.exported)),56      ("parameters", Json.list(&member.parameters.map(|parameter: Model.Parameter| Json.object(&[("name", Json.Text(parameter.name)), ("type", Json.Text(parameter.kind))])))),57      ("returns", Json.Text(member.returns)), ("members", Json.list(&member.members.map(|inner: Model.Member| memberJson(&inner))))58    ])59}6061/// A module page in Markdown with declarations as sections.62fn moduleMarkdown(unit: &Model.Unit) -> Str {63  var lines = ["---", "uid: " + Yaml.scalar(unit.uid), "title: " + Yaml.scalar(unit.uid), "---", "", "# " + unit.uid, ""]64  if !unit.doc.isEmpty() { lines = lines.push(unit.doc).push("") }65  for member in unit.members {66    if Model.isType(member.kind) {67      let summary = Model.summary(member.doc)68      lines = lines.push("- <xref:" + member.uid + ">" + (if summary.isEmpty() { "" } else { ": " + summary }))69    }70  }71  for member in unit.members {72    if !Model.isType(member.kind) {73      lines = lines.concat(["", "## " + member.name + " \{#" + member.name + "\}", "", "```pudu", member.signature, "```", ""])74      if !member.doc.isEmpty() { lines = lines.push(member.doc) }75    }76  }77  lines.join("\n") + "\n"78}7980/// A type page in Markdown with fields, variants, or methods as a table or sections.81fn typeMarkdown(unit: &Model.Unit, member: &Model.Member) -> Str {82  var lines = ["---", "uid: " + Yaml.scalar(member.uid), "title: " + Yaml.scalar(member.name), "---", "", "# " + member.name, "", "Module <xref:" + unit.uid + ">", "", "```pudu", member.signature, "```", ""]83  if !member.doc.isEmpty() { lines = lines.push(member.doc).push("") }84  if member.kind == "trait" {85    for method in member.members { lines = lines.concat(["## " + method.name + " \{#" + method.name + "\}", "", "```pudu", method.signature, "```", "", method.doc, ""]) }86  } else if !member.members.isEmpty() {87    lines = lines.concat(["| Name | Type | Description |", "| --- | --- | --- |"])88    for inner in member.members { lines = lines.push("| `" + inner.name + "` | `" + inner.returns.replace("|", "\\|") + "` | " + Model.summary(inner.doc).replace("|", "\\|") + " |") }89  }90  lines.join("\n") + "\n"91}9293/// A module as an API page document.94fn modulePage(unit: &Model.Unit) -> Docgen.Meta {95  var body = [Docgen.Fields([("h1", Docgen.Text(unit.uid))])]96  if !unit.doc.isEmpty() { body = body.push(Docgen.Fields([("markdown", Docgen.Text(unit.doc))])) }97  let types = unit.members.filter(|member: Model.Member| Model.isType(member.kind))98  if !types.isEmpty() {99    body = body.push(Docgen.Fields([("h2", Docgen.Text("Types"))]))100    body = body.push(Docgen.Fields([("list", Docgen.Items(types.map(|member: Model.Member| Docgen.Fields([("text", Docgen.Text(member.name)), ("url", Docgen.Text(member.uid + ".html"))]))))]))101  }102  for member in unit.members {103    if !Model.isType(member.kind) { body = body.concat(declaration(member, 3)) }104  }105  Docgen.Fields([("title", Docgen.Text(unit.uid)), ("languageId", Docgen.Text("pudu")), ("body", Docgen.Items(body))])106}107108/// A type as an API page document.109fn typePage(unit: &Model.Unit, member: &Model.Member) -> Docgen.Meta {110  var body = [Docgen.Fields([("h1", Docgen.Text(member.name))]), Docgen.Fields([("facts", Docgen.Items([Docgen.Fields([("name", Docgen.Text("Module")), ("value", Docgen.Fields([("text", Docgen.Text(unit.uid)), ("url", Docgen.Text(unit.uid + ".html"))]))])]))]), Docgen.Fields([("code", Docgen.Text(member.signature))])]111  if !member.doc.isEmpty() { body = body.push(Docgen.Fields([("markdown", Docgen.Text(member.doc))])) }112  if !member.members.isEmpty() {113    body = body.push(Docgen.Fields([("h2", Docgen.Text(if member.kind == "trait" { "Methods" } else if member.kind == "union" { "Variants" } else { "Fields" }))]))114    if member.kind == "trait" {115      for method in member.members { body = body.concat(declaration(method, 3)) }116    } else {117      body = body.push(Docgen.Fields([("parameters", Docgen.Items(member.members.map(|inner: Model.Member| Docgen.Fields([("name", Docgen.Text(inner.name)), ("type", Docgen.Text(inner.returns)), ("description", Docgen.Text(inner.doc))]))))]))118    }119  }120  Docgen.Fields([("title", Docgen.Text(member.name)), ("languageId", Docgen.Text("pudu")), ("body", Docgen.Items(body))])121}122123/// API page blocks documenting one function, method, or constant.124fn declaration(member: Model.Member, level: Int) -> Array[Docgen.Meta] {125  var blocks = [Docgen.Fields([("api" + show(level), Docgen.Text(member.name)), ("id", Docgen.Text(member.name))]), Docgen.Fields([("code", Docgen.Text(member.signature))])]126  if !member.doc.isEmpty() { blocks = blocks.push(Docgen.Fields([("markdown", Docgen.Text(member.doc))])) }127  let shown = member.parameters.filter(|parameter: Model.Parameter| parameter.name != "self")128  if !shown.isEmpty() { blocks = blocks.push(Docgen.Fields([("parameters", Docgen.Items(shown.map(|parameter: Model.Parameter| Docgen.Fields([("name", Docgen.Text(parameter.name)), ("type", Docgen.Text(parameter.kind))]))))])) }129  blocks130}131