Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-mcp

Model Context Protocol server for Pudu language documentation and compiler tools

0.1.1Apache-2.01

InstallClose

SyncDocs.pudu

Pudu157 lines6.1 KB

GitHub ↗
1/** @Tools.SyncDocs.Generator — compiles published prose into Pudu source */2module SyncDocs34import Std.Env as Env5import Std.Io as Io6import Std.List as List7import Std.Option as Option8import Std.Text as Text910/// Where the generated corpus is written.11const DOCS_OUTPUT: Str = "src/PuduLangMcp/Generated/Docs.pudu"12/// The first line of the generated module.13const HEADER: Str = "/** @Generated.Docs.Corpus — published Pudu prose, generated; do not edit */\n"1415/// The site pages the website publishes, by slug and source file.16const PAGES: Array[(Str, Str)] = [17  ("conduct", "CODE_OF_CONDUCT.md"),18  ("contributing", "CONTRIBUTING.md"),19  ("security", "SECURITY.md"),20  ("community", "website/pages/community.md"),21  ("privacy", "website/pages/privacy.md"),22  ("brand", "website/pages/brand.md")23]2425/// One document as it will be written.26type Document = { group: Str, slug: Str, title: Str, markdown: Str }2728/// Regenerates the corpus from a pudu-lang checkout.29fn main() -> Int {30  let checkout = match Env.at(0) {31    case Some(given) => given32    case None => { return failed("usage: pudu run tools/SyncDocs.pudu <pudu-lang-checkout> [revision]") }33  }34  let revision = Option.unwrapOr(Env.at(1), "unknown")35  var documents: Array[Document] = []36  match chapters(checkout + "/website/docs") {37    case Ok(found) => { documents = documents.concat(found) }38    case Err(problem) => { return failed(problem) }39  }40  for page in PAGES {41    match Io.read(checkout + "/" + page[1]) {42      case Ok(text) => { documents = documents.push(Document{group: "pages", slug: page[0], title: titleOf(text, page[0]), markdown: text}) }43      case Err(problem) => { return failed("cannot read " + page[1] + ": " + problem) }44    }45  }46  match releases(checkout) {47    case Ok(found) => { documents = documents.concat(found) }48    case Err(problem) => { return failed(problem) }49  }50  match examples(checkout + "/website/playground/examples") {51    case Ok(found) => { documents = documents.concat(found) }52    case Err(problem) => { return failed(problem) }53  }54  let docsModule = HEADER + "module PuduLangMcp.Generated.Docs\n\nimport PuduLangMcp.Domain.Docs.Chapter as Chapter\n\nexport const REVISION: Str = " + literal(revision) + "\n\nexport const CHAPTERS: Array[Chapter.Chapter] = [\n" + documents.map(|held: Document| documentLiteral(&held)).join(",\n") + "\n]\n"55  match Io.write(DOCS_OUTPUT, docsModule) {56    case Ok(_) => ()57    case Err(problem) => { return failed("cannot write " + DOCS_OUTPUT + ": " + problem) }58  }59  let _said = Io.writeLine("wrote " + show(documents.length()) + " documents to " + DOCS_OUTPUT)60  061}6263/// The language guide chapters.64fn chapters(directory: Str) -> Result[Array[Document], Str] {65  let names = sortedWith(directory, ".md") ?66  var found: Array[Document] = []67  for name in names {68    let text = readIn(directory, name) ?69    let slug = slugOf(name, ".md")70    found = found.push(Document{group: "docs", slug: slug, title: titleOf(text, slug), markdown: text})71  }72  if found.isEmpty() { Err("no documentation chapters in " + directory) } else { Ok(found) }73}7475/// Every release note of every compiler series.76fn releases(checkout: Str) -> Result[Array[Document], Str] {77  let series = sortedWith(checkout + "/packages/pudu", "") ?78  var found: Array[Document] = []79  for version in series {80    let directory = checkout + "/packages/pudu/" + version + "/release-notes"81    if Io.exists(directory) {82      for name in sortedWith(directory, ".md") ? {83        let text = readIn(directory, name) ?84        let number = name.replace(".md", "")85        found = found.push(Document{group: "releases", slug: number, title: "Pudu " + number, markdown: text})86      }87    }88  }89  Ok(found)90}9192/// The playground examples as fenced documents.93fn examples(directory: Str) -> Result[Array[Document], Str] {94  let names = sortedWith(directory, ".pudu") ?95  var found: Array[Document] = []96  for name in names {97    let code = readIn(directory, name) ?98    let slug = slugOf(name, ".pudu")99    let first = Option.unwrapOr(List.first(&code.split("\n")), "")100    let title = if first.startsWith("// ") { first.drop(3).trim() } else { slug }101    found = found.push(Document{group: "examples", slug: slug, title: title, markdown: "# " + title + "\n\n```pudu\n" + code.trim() + "\n```\n"})102  }103  Ok(found)104}105106/// A directory's file names with a suffix, sorted.107fn sortedWith(directory: Str, suffix: Str) -> Result[Array[Str], Str] {108  match Io.list(directory) {109    case Ok(found) => Ok(List.sortBy(&found.filter(|name: Str| name.endsWith(suffix) && !name.startsWith(".")), fn(left: Str, right: Str) -> Bool { left < right }))110    case Err(problem) => Err("cannot list " + directory + ": " + problem)111  }112}113114/// A file's text with carriage returns removed.115fn readIn(directory: Str, name: Str) -> Result[Str, Str] {116  match Io.read(directory + "/" + name) {117    case Ok(text) => Ok(text.replace("\r", ""))118    case Err(problem) => Err("cannot read " + directory + "/" + name + ": " + problem)119  }120}121122/// A file name without its extension and ordering prefix.123fn slugOf(name: Str, extension: Str) -> Str {124  let stem = Text.stripSuffix(name, extension)125  let dash = stem.indexOf("-")126  if dash > 0 && isDigits(stem.take(dash)) { stem.drop(dash + 1) } else { stem }127}128129/// The first top-level heading, or a fallback.130fn titleOf(text: Str, fallback: Str) -> Str {131  for line in text.split("\n") {132    if line.startsWith("# ") { return line.drop(2).trim() }133  }134  fallback135}136137/// Whether text is one or more decimal digits.138fn isDigits(text: Str) -> Bool {139  !text.isEmpty() && text.chars().filter(|c: Char| c < '0' || c > '9').isEmpty()140}141142/// A document as a Pudu record literal.143fn documentLiteral(held: &Document) -> Str {144  "  Chapter.Chapter\{group: " + literal(held.group) + ", slug: " + literal(held.slug) + ", title: " + literal(held.title) + ", markdown: " + literal(held.markdown) + "\}"145}146147/// A Pudu string literal whose value is exactly `text`.148fn literal(text: Str) -> Str {149  "\"" + text.replace("\\", "\\\\").replace("\"", "\\\"").replace("\{", "\\\{").replace("\}", "\\\}").replace("\r", "").replace("\n", "\\n").replace("\t", "\\t") + "\""150}151152/// Reports a failure on stderr and answers exit status 1.153fn failed(message: Str) -> Int {154  let _said = Io.writeErrorLine("SyncDocs: " + message)155  1156}157