
Tools.pudu
Pudu134 lines6.1 KB
1/** @Docgen.Docset.Tools — git history, diagram rendering, remote reference maps, and reference services */2module PuduLangDocgen.Docset.Tools34import Std.Bytes as Bytes5import Std.Env as Env6import Std.Http.Client as Client7import Std.Process as Process8import Std.Result as Result9import PuduLangDocgen.Configuration as Configuration10import PuduLangDocgen.Constants.Codes as Codes11import PuduLangDocgen as Docgen12import PuduLangDocgen.Docset.Walk as Walk13import PuduLangDocgen.Paths as Paths14import PuduLangDocgen.References as References15import PuduLangDocgen.Repository as Repository161718const FETCH_MILLIS: Int = 30000192021export fn history(base: Str) -> Map[Str, Str] {22 if !Process.isAvailable("git") { return mapOf([]) }23 match Process.run("git", &["-C", base, "log", "--pretty=format:@%cs", "--name-only", "--relative", "--", "."]) {24 case Ok(finished) => if finished.status == 0 { Repository.history(finished.output) } else { mapOf([]) }25 case Err(_) => mapOf([])26 }27}28293031export fn repository(base: Str, configured: &Configuration.Contribution) -> Configuration.Contribution {32 let (environmentRepo, environmentBranch) = Repository.fromEnvironment(&Env.variables())33 var repo = if configured.repo.isEmpty() { environmentRepo } else { Repository.normalize(configured.repo) }34 var branch = if environmentBranch.isEmpty() { configured.branch } else { environmentBranch }35 var prefix = configured.path36 if Process.isAvailable("git") {37 if repo.isEmpty() { repo = Repository.normalize(trimmed(Process.output("git", &["-C", base, "remote", "get-url", "origin"]))) }38 if environmentBranch.isEmpty() && configured.branch == "main" {39 let local = trimmed(Process.output("git", &["-C", base, "rev-parse", "--abbrev-ref", "HEAD"]))40 if !local.isEmpty() && local != "HEAD" { branch = local }41 }42 if prefix.isEmpty() {43 let found = trimmed(Process.output("git", &["-C", base, "rev-parse", "--show-prefix"]))44 prefix = if found.endsWith("/") { found.take(found.length() - 1) } else { found }45 }46 }47 Configuration.Contribution{repo: repo, branch: branch, path: prefix}48}49505152export fn diagrams(sources: &Array[Str], settings: &Configuration.Diagrams) -> (Map[Str, Str], Array[Docgen.Diagnostic]) {53 var drawn: Array[(Str, Str)] = []54 var problems: Array[Docgen.Diagnostic] = []55 for source in *sources {56 match Process.withInput(settings.java, &["-jar", settings.jar, "-tsvg", "-pipe"], source) {57 case Ok(finished) => {58 if finished.status == 0 && finished.output.contains("<svg") {59 drawn = drawn.push((source, "data:image/svg+xml;base64," + Bytes.encodeBase64(&Bytes.fromText(finished.output))))60 } else { problems = problems.push(Docgen.warning(Codes.TOOL_FAILED, settings.jar, 1, "PlantUML did not draw a diagram: " + finished.errors.trim())) }61 }62 case Err(reason) => { problems = problems.push(Docgen.warning(Codes.TOOL_FAILED, settings.jar, 1, "PlantUML could not start: " + reason)) }63 }64 }65 (mapOf(drawn), problems)66}676869export fn references(base: Str, sources: &Array[Str]) -> (Array[Docgen.Reference], Array[Docgen.Diagnostic]) {70 var found: Array[Docgen.Reference] = []71 var problems: Array[Docgen.Diagnostic] = []72 for source in *sources {73 let text = if Paths.remote(source) { fetch(source) } else { Result.mapErr(Walk.text(base, source), |problem: Docgen.Diagnostic| problem.message) }74 match text {75 case Ok(held) => match References.parse(source, held) {76 case Ok(read) => { found = found.concat(read) }77 case Err(reason) => { problems = problems.push(Docgen.warning(Codes.XREF_UNAVAILABLE, source, 1, reason)) }78 }79 case Err(reason) => { problems = problems.push(Docgen.warning(Codes.XREF_UNAVAILABLE, source, 1, reason)) }80 }81 }82 (found, problems)83}8485868788export fn consult(services: &Array[Str], wanted: &Array[Str]) -> (Array[Docgen.Reference], Array[Docgen.Diagnostic]) {89 var found: Array[Docgen.Reference] = []90 var problems: Array[Docgen.Diagnostic] = []91 var failing: Array[Str] = []92 for uid in *wanted {93 for service in *services {94 if failing.contains(service) { continue }95 let address = References.serviceAddress(service, uid)96 let answered = match fetch(address) {97 case Ok(text) => References.parse(address, text)98 case Err(reason) => Err(reason)99 }100 match answered {101 case Ok(read) => {102 let matching = read.filter(|reference: Docgen.Reference| reference.uid == uid)103 if !matching.isEmpty() {104 found = found.push(matching[0])105 break106 }107 }108 case Err(reason) => {109 failing = failing.push(service)110 problems = problems.push(Docgen.warning(Codes.XREF_UNAVAILABLE, service, 1, reason))111 }112 }113 }114 }115 (found, problems)116}117118119export fn fetch(address: Str) -> Result[Str, Str] {120 let limits = Client.Limits{..Client.limits(), deadlineMillis: FETCH_MILLIS}121 match Client.fetch(address, &limits) {122 case Ok(response) => if response.status.code >= 200 && response.status.code < 300 { Ok(response.body) } else { Err("server answered " + show(response.status.code)) }123 case Err(problem) => Err(Client.explain(&problem))124 }125}126127128fn trimmed(result: Result[Str, Str]) -> Str {129 match result {130 case Ok(held) => held.trim()131 case Err(_) => ""132 }133}134