
Tasks.pudu
Pudu153 lines7.4 KB
1/** @Docgen.Docset.Tasks — project scaffolding, template export, printing, and reference maps */2module PuduLangDocgen.Docset.Tasks34import Std.Env as Env5import Std.Fs as Fs6import Std.Io as Io7import Std.List as List8import Std.Path as Path9import Std.Process as Process10import PuduLangDocgen.Build as Build11import PuduLangDocgen.Constants.Codes as Codes12import PuduLangDocgen.Constants.Package as Package13import PuduLangDocgen as Docgen14import PuduLangDocgen.Docset.Publish as Publish15import PuduLangDocgen.Docset.Tools as Tools16import PuduLangDocgen.Docset.Walk as Walk17import PuduLangDocgen.Paths as Paths18import PuduLangDocgen.References as References19import PuduLangDocgen.Scaffold as Scaffold20import PuduLangDocgen.Site.Print as Print21import PuduLangDocgen.Theme.Layout as Layout22import PuduLangDocgen.Theme as Theme232425const BROWSERS: Array[Str] = [26 "chromium", "chromium-browser", "google-chrome", "google-chrome-stable", "microsoft-edge", "msedge",27 "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Chromium.app/Contents/MacOS/Chromium",28 "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"29]303132const PDF_MILLIS: Int = 120000333435export fn init(folder: Str, answers: &Scaffold.Answers) -> Result[Array[Str], Array[Docgen.Diagnostic]] {36 if Io.exists(Path.join(folder, Package.CONFIG_FILE)) { return Err([Docgen.error(Codes.CONFIG_INVALID, Path.join(folder, Package.CONFIG_FILE), 1, "a project already exists here")]) }37 var written: Array[Str] = []38 for (path, text) in Scaffold.files(answers) {39 let target = Path.join(folder, path)40 if Io.exists(target) { continue }41 write(target, text) ?42 written = written.push(path)43 }44 Ok(written)45}464748export fn exportTemplate(folder: Str) -> Result[Array[Str], Array[Docgen.Diagnostic]] {49 var written: Array[Str] = []50 for (name, text) in Layout.templates() {51 let path = name + ".html"52 write(Path.join(folder, path), text) ?53 written = written.push(path)54 }55 for asset in Theme.assets() {56 let path = "reference/" + Path.nameOf(asset.path)57 write(Path.join(folder, path), asset.content) ?58 written = written.push(path)59 }60 write(Path.join(folder, "public/main.css"), "/* Styles loaded after the default site styles. */\n") ?61 write(Path.join(folder, "public/main.js"), "/* Behavior loaded after the default site script. */\n") ?62 Ok(written.push("public/main.css").push("public/main.js"))63}646566export fn pdf(base: Str, built: &Build.Built, output: Str, renderer: &Array[Str]) -> Result[Array[Str], Array[Docgen.Diagnostic]] {67 let root = Path.join(base, output)68 let documents = Print.documents(&built.pages, &built.books)69 if documents.isEmpty() { return Err([Docgen.error(Codes.TOOL_FAILED, "toc.yml", 1, "no table of contents sets pdf: true")]) }70 let command = if renderer.isEmpty() { detect() } else { *renderer }71 if command.isEmpty() { return Err([Docgen.error(Codes.TOOL_FAILED, "pdf", 1, "no PDF renderer found; install Chromium or Chrome, or set pdf.renderer with \{input\} and \{output\}")]) }72 let limit = match Env.variable("DOCGEN_PDF_TIMEOUT") {73 case Some(written) => match written.toInt() { case Some(found) => found case None => PDF_MILLIS }74 case None => PDF_MILLIS75 }76 var produced: Array[Str] = []77 for document in documents {78 let input = Path.join(root, document.path)79 let target = Path.join(root, document.pdf)80 write(input, document.html) ?81 let header = Path.join(root, document.path + ".header.html")82 let footer = Path.join(root, document.path + ".footer.html")83 write(header, if document.header.isEmpty() { "<div></div>" } else { document.header }) ?84 write(footer, document.footer) ?85 let arguments = command.slice(1, command.length()).map(|part: Str| part.replace("\{input\}", input).replace("\{output\}", target).replace("\{header\}", header).replace("\{footer\}", footer).replace("\{url\}", "file://" + input))86 match Process.within(command[0], &arguments, limit) {87 case Ok(Some(finished)) => {88 if finished.status != 0 || !Io.exists(target) { return Err([Docgen.error(Codes.TOOL_FAILED, document.pdf, 1, "renderer failed: " + finished.errors.trim())]) }89 }90 case Ok(None) => { return Err([Docgen.error(Codes.TOOL_FAILED, document.pdf, 1, "renderer took longer than " + show(limit) + " ms")]) }91 case Err(reason) => { return Err([Docgen.error(Codes.TOOL_FAILED, document.pdf, 1, "renderer could not start: " + reason)]) }92 }93 let _cleaned = [Io.remove(header), Io.remove(footer)]94 produced = produced.push(document.pdf)95 }96 Ok(produced)97}9899100export fn download(address: Str, file: Str) -> Result[Int, Array[Docgen.Diagnostic]] {101 let text = match Tools.fetch(address) {102 case Ok(found) => found103 case Err(reason) => { return Err([Docgen.error(Codes.XREF_UNAVAILABLE, address, 1, reason)]) }104 }105 let read = match References.parse(address, text) {106 case Ok(found) => found107 case Err(reason) => { return Err([Docgen.error(Codes.XREF_UNAVAILABLE, address, 1, reason)]) }108 }109 write(file, if file.toLower().endsWith(".json") { References.toJson(&read, "") } else { References.toYaml(&read, "") }) ?110 Ok(read.length())111}112113114115export fn merge(sources: &Array[Str], file: Str) -> Result[Int, Array[Docgen.Diagnostic]] {116 var found: Array[Docgen.Reference] = []117 for source in *sources {118 let text = if Paths.remote(source) { Tools.fetch(source) } else { Io.read(source) }119 match text {120 case Ok(held) => match References.parse(source, held) {121 case Ok(read) => { found = found.concat(read) }122 case Err(reason) => { return Err([Docgen.error(Codes.XREF_UNAVAILABLE, source, 1, reason)]) }123 }124 case Err(reason) => { return Err([Docgen.error(Codes.XREF_UNAVAILABLE, source, 1, reason)]) }125 }126 }127 var kept: Array[Docgen.Reference] = []128 var seen: Array[Str] = []129 for reference in found {130 if !seen.contains(reference.uid) {131 kept = kept.push(reference)132 seen = seen.push(reference.uid)133 }134 }135 let ordered = List.sortOn(&kept, |held: Docgen.Reference| held.uid)136 write(file, if file.toLower().endsWith(".json") { References.toJson(&ordered, "") } else { References.toYaml(&ordered, "") }) ?137 Ok(ordered.length())138}139140141fn write(path: Str, text: Str) -> Result[(), Array[Docgen.Diagnostic]] { Publish.place(path, text) }142143144fn detect() -> Array[Str] {145 for browser in BROWSERS {146 if Process.isAvailable(browser) || (browser.startsWith("/") && Io.exists(browser)) {147 return [browser, "--headless=new", "--disable-gpu", "--no-pdf-header-footer", "--print-to-pdf=\{output\}", "\{url\}"]148 }149 }150 if Process.isAvailable("wkhtmltopdf") { return ["wkhtmltopdf", "--enable-local-file-access", "--print-media-type", "--header-html", "\{header\}", "--footer-html", "\{footer\}", "\{input\}", "\{output\}"] }151 []152}153