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

ApiTest.pudu

Pudu161 lines11.2 KB

GitHub ↗
1/** @Docgen.Api.Tests — Pudu declarations become linked reference pages */2module PuduLangDocgen.ApiTest34import Std.Io as Io5import Std.Result as Result6import Std.Test as Test7import PuduLangDocgen.Api.Catalog as Catalog8import PuduLangDocgen.Api.Lexer as Lexer9import PuduLangDocgen.Api.Model as Model10import PuduLangDocgen.Api.Pages as Pages11import PuduLangDocgen.Api.Parser as Parser12import PuduLangDocgen.Api.Signature as Signature13import PuduLangDocgen as Docgen14import PuduLangDocgen.Markdown.Phrase as Phrase15import PuduLangDocgen.Yaml as Yaml1617/// A module exercising every declaration form.18fn shapesSource() -> Str {19  [20    "/** @Demo.Shapes.Module — geometric shapes and their areas */",21    "module Demo.Shapes",22    "",23    "import Demo.Units as Units",24    "",25    "/** @Demo.Shapes.Shape — a closed figure */",26    "export type Shape = Circle(Int) | Square(Int)",27    "",28    "/// A point on the plane.",29    "export type Point = \{",30    "  /// Horizontal position.",31    "  x: Int,",32    "  y: Int",33    "\}",34    "",35    "/// Measured width.",36    "export type Width = Units.Length",37    "",38    "/// Anything with an area.",39    "export trait Measured \{",40    "  fn area(self: &Self) -> Int",41    "\}",42    "",43    "impl Measured for Shape \{",44    "  /// The shape's area.",45    "  fn area(self: &Self) -> Int \{ 0 \}",46    "\}",47    "",48    "/// The area of a shape in square units.",49    "///",50    "/// ## Examples",51    "///",52    "/// Circles round down.",53    "export fn area(shape: &Shape, scale: Units.Length) -> Int \{",54    "  let text = \"\{ nested \}\"",55    "  0",56    "\}",57    "",58    "/// Largest side accepted.",59    "export const LIMIT: Int = 100",60    "",61    "fn hidden() -> Int \{ 1 \}",62    ""63  ].join("\n")64}6566/// A module the demo imports from.67const UNITS: Str = "module Demo.Units\n\n/// A length in millimetres.\nexport type Length = Millimetres(Int)\n"6869/// Catalog options publishing under `api` with source links.70fn options() -> Catalog.Options {71  Catalog.Options{..Catalog.defaults(), sourceUrl: "https://src.example/\{path\}#L\{line\}"}72}7374/// The demo modules as selected for publication.75fn units() -> Array[Model.Unit] {76  Catalog.select(&[Parser.parse("src/Demo/Shapes.pudu", shapesSource()), Parser.parse("src/Demo/Units.pudu", UNITS)], &options())77}7879/// Rendering context over the demo modules.80fn context() -> Pages.Context {81  let all = units()82  var references: Array[(Str, Docgen.Reference)] = []83  for reference in Catalog.references(&all, &options()) { references = references.push((reference.uid, reference)) }84  Pages.Context{options: options(), references: mapOf(references), outputs: mapOf([]), settings: Phrase.rendering(), units: all}85}8687/// Rendered pages of the shapes module.88fn pages() -> Array[Docgen.Page] {89  let all = units()90  let (found, _problems) = Pages.pages(&all[0], &context())91  found92}9394/// The body of the page at a path.95fn body(path: Str) -> Str {96  for page in pages() {97    if page.path == path { return page.body }98  }99  ""100}101102/// Runs every lexing, parsing, selection, linking, and page contract.103fn main() -> Int {104  let shapes = Parser.parse("src/Demo/Shapes.pudu", shapesSource())105  let members = shapes.members106  let tokens = Lexer.tokens("/* a /* b */ c */ x \"s \{ \"in\" \} t\" 'y' '\\n' /// doc")107  let checks = Test.suite("Api", &[108      Test.equals("tokens skip nested comments and keep literals", &tokens.map(|token: Lexer.Token| token.text), &["x", "\"s \{ \"in\" \} t\"", "'y'", "'\\n'", "doc"]),109      Test.equals("module identity and anchor text", &(shapes.uid, shapes.doc), &("Demo.Shapes", "Geometric shapes and their areas")),110      Test.equals("imports", &shapes.imports, &[("Units", "Demo.Units")]),111      Test.equals("record signature without docs", &members[1].signature, &"type Point = \{ x: Int, y: Int \}"),112      Test.equals("long records print a field per line", &Parser.parse("a.pudu", "module A\nexport type R = \{ a: Int, /// b\n b: Str, c: Bool \}").members[0].signature, &"type R = \{\n  a: Int,\n  b: Str,\n  c: Bool\n\}"),113      Test.equals("declaration kinds", &members.map(|member: Model.Member| member.kind), &["union", "record", "alias", "trait", "function", "constant", "function"]),114      Test.equals("exported flags", &members.map(|member: Model.Member| member.exported), &[true, true, true, true, true, true, false]),115      Test.equals("variants", &members[0].members.map(|member: Model.Member| member.signature), &["Circle(Int)", "Square(Int)"]),116      Test.equals("fields and their docs", &members[1].members.map(|member: Model.Member| (member.name, member.returns, member.doc)), &[("x", "Int", "Horizontal position."), ("y", "Int", "")]),117      Test.equals("alias target", &members[2].returns, &"Units.Length"),118      Test.equals("trait methods", &members[3].members.map(|member: Model.Member| member.signature), &["fn area(self: &Self) -> Int"]),119      Test.equals("function signature", &members[4].signature, &"fn area(shape: &Shape, scale: Units.Length) -> Int"),120      Test.equals("function parameters", &members[4].parameters, &[Model.Parameter{name: "shape", kind: "&Shape"}, Model.Parameter{name: "scale", kind: "Units.Length"}]),121      Test.equals("constant value shown", &members[5].signature, &"const LIMIT: Int = 100"),122      Test.equals("implementation", &shapes.implementations.map(|held: Model.Implementation| (held.contract, held.target, held.methods.length())), &[("Measured", "Shape", 1)]),123      Test.equals("summary and remarks", &(Model.summary(members[4].doc), Model.remarks(members[4].doc)), &("The area of a shape in square units.", "## Examples\n\nCircles round down.")),124      Test.equals("private members dropped", &units()[0].members.length(), &6),125      Test.equals("private members kept on request", &Catalog.select(&[shapes], &Catalog.Options{..options(), includePrivate: true})[0].members.length(), &7),126      Test.equals("exclude patterns", &Catalog.select(&[shapes], &Catalog.Options{..options(), exclude: ["Demo.Shapes.area", "Demo.Shapes.LIMIT"]})[0].members.length(), &4),127      Test.equals("include patterns keep the module", &Catalog.select(&[shapes], &Catalog.Options{..options(), include: ["Demo.Shapes.Point"]})[0].members.map(|member: Model.Member| member.name), &["Point"]),128      Test.equals("reference destinations", &Catalog.references(&units(), &options()).map(|reference: Docgen.Reference| reference.href).slice(0, 6), &["api/Demo.Shapes.html", "api/Demo.Shapes.Shape.html", "api/Demo.Shapes.Shape.html#Circle", "api/Demo.Shapes.Shape.html#Square", "api/Demo.Shapes.Point.html", "api/Demo.Shapes.Point.html#x"]),129      Test.equals("toc lists types", &Catalog.toc(&units(), &options())[0].children.map(|item: Docgen.TocItem| item.title), &["Measured", "Point", "Shape", "Width"]),130      Test.equals("source link", &Catalog.sourceLink(&options(), "src/a.pudu", 7), &"https://src.example/src/a.pudu#L7"),131      Test.equals("signature links through imports", &Signature.html(&shapes, "fn f(s: &Shape) -> Units.Length", &context().references, "api/Demo.Shapes.html"), &"<span class=\"hl-k\">fn</span> f(s: &amp;<a class=\"xref\" href=\"Demo.Shapes.Shape.html\">Shape</a>) -&gt; <a class=\"xref\" href=\"Demo.Units.Length.html\">Units.Length</a>"),132      Test.equals("unknown names stay text", &Signature.resolve(&shapes, "Str", &context().references), &""),133      Test.equals("pages per module and type", &pages().map(|page: Docgen.Page| page.path), &["api/Demo.Shapes.html", "api/Demo.Shapes.Shape.html", "api/Demo.Shapes.Point.html", "api/Demo.Shapes.Width.html", "api/Demo.Shapes.Measured.html"]),134      Test.that("module page sections", body("api/Demo.Shapes.html").contains("<h2 id=\"types\">") && body("api/Demo.Shapes.html").contains("<h2 id=\"functions\">") && body("api/Demo.Shapes.html").contains("<h3 id=\"area\">")),135      Test.that("demoted doc headings", body("api/Demo.Shapes.html").contains("<h4 id=\"examples\">Examples")),136      Test.that("parameters listed", body("api/Demo.Shapes.html").contains("<dt><code>scale</code></dt>")),137      Test.that("source links shown", body("api/Demo.Shapes.html").contains("https://src.example/src/Demo/Shapes.pudu#L34")),138      Test.that("variants table", body("api/Demo.Shapes.Shape.html").contains("<tr id=\"Circle\"><td><code>Circle</code></td>")),139      Test.that("implemented traits", body("api/Demo.Shapes.Shape.html").contains("<h2 id=\"implements\">") && body("api/Demo.Shapes.Shape.html").contains("Demo.Shapes.Measured")),140      Test.that("trait implementors", body("api/Demo.Shapes.Measured.html").contains("<h2 id=\"implementations\">")),141      Test.that("related functions", body("api/Demo.Shapes.Shape.html").contains("<h2 id=\"related\">")),142      Test.that("field docs rendered", body("api/Demo.Shapes.Point.html").contains("<p>Horizontal position.</p>")),143      Test.equals("nested module navigation", &Catalog.toc(&units(), &Catalog.Options{..options(), nested: true}).map(|item: Docgen.TocItem| (item.title, item.href, item.children.map(|child: Docgen.TocItem| child.title))), &[("Demo", "", ["Shapes", "Units"])]),144      Test.equals("separate member pages", &Catalog.references(&units(), &Catalog.Options{..options(), separate: true}).filter(|reference: Docgen.Reference| reference.uid == "Demo.Shapes.area").map(|reference: Docgen.Reference| reference.href), &["api/Demo.Shapes.area.html"]),145      Test.that("plain documentation", {146          let (found, _problems) = Pages.pages(&units()[0], &Pages.Context{..context(), options: Catalog.Options{..options(), skipMarkup: true}})147          found[0].body.contains("<p>The area of a shape in square units.</p>") && found[0].body.contains("## Examples")148        }),149      Test.equals("filter rules", &match Catalog.rules(&Result.unwrapOr(Yaml.decode("apiRules:\n- exclude:\n    uidRegex: \\.area$\n    type: Function\n- include:\n    uidRegex: .*"), Docgen.Nothing)) { case Ok(found) => Catalog.select(&[shapes], &Catalog.Options{..options(), rules: found})[0].members.map(|member: Model.Member| member.name) case Err(reason) => [reason] }, &["Shape", "Point", "Width", "Measured", "LIMIT"]),150      Test.equals("bad filter", &match Catalog.rules(&Docgen.Fields([("apiRules", Docgen.Items([Docgen.Fields([("exclude", Docgen.Fields([("uidRegex", Docgen.Text("("))]))])]))])) { case Ok(_) => "accepted" case Err(reason) => reason.take(9) }, &"uidRegex "),151      Test.equals("category labels", &Catalog.toc(&units(), &Catalog.Options{..options(), categories: "flattened"})[0].children.map(|item: Docgen.TocItem| item.title), &["Types", "Point", "Shape", "Width", "Traits", "Measured"]),152      Test.equals("category groups", &Catalog.toc(&units(), &Catalog.Options{..options(), categories: "nested"})[0].children.map(|item: Docgen.TocItem| (item.title, item.children.length())), &[("Types", 3), ("Traits", 1)]),153      Test.equals("alphabetic variants", &Catalog.select(&[Parser.parse("a.pudu", "module A\nexport type T = Zed | Alpha")], &Catalog.Options{..options(), alphabetic: true})[0].members[0].members.map(|member: Model.Member| member.name), &["Alpha", "Zed"]),154      Test.equals("source links excluded", &Catalog.sourceLink(&Catalog.Options{..options(), sourceExclude: ["src/**"]}, "src/a.pudu", 1), &""),155      Test.equals("kind names", &[Pages.kindName("union"), Pages.kindName("alias"), Pages.kindName("other")], &["Union", "Type alias", "other"])156    ])157  let ran = Test.run(&checks)158  for failure in Test.failuresOf(&ran) { let _printed = Io.writeErrorLine(failure) }159  Test.report(&ran)160}161