
Walk.pudu
Pudu101 lines4.2 KB
1/** @Docgen.Docset.Walk — confined reads of a project folder and its files */2module PuduLangDocgen.Docset.Walk34import Std.Fs as Fs5import Std.Io as Io6import Std.List as List7import Std.Path as Path8import PuduLangDocgen.Constants.Codes as Codes9import PuduLangDocgen as Docgen10import PuduLangDocgen.Paths as Paths111213const MAX_DEPTH: Int = 64141516const MAX_FILES: Int = 100000171819const MAX_BYTES: Int = 33554432202122const IGNORED: Array[Str] = ["node_modules", "obj", "bin", "deps", "dist-newstyle"]23242526export fn files(base: Str, skip: &Array[Str]) -> Result[Array[Str], Array[Docgen.Diagnostic]] {27 let found = walk(base, "", skip, 0) ?28 Ok(List.sorted(&found))29}303132export fn texts(base: Str, paths: &Array[Str]) -> (Map[Str, Str], Array[Docgen.Diagnostic]) {33 var found: Array[(Str, Str)] = []34 var problems: Array[Docgen.Diagnostic] = []35 for path in *paths {36 match text(base, path) {37 case Ok(held) => { found = found.push((path, held)) }38 case Err(problem) => { problems = problems.push(problem) }39 }40 }41 (mapOf(found), problems)42}434445export fn text(base: Str, path: Str) -> Result[Str, Docgen.Diagnostic] {46 let physical = inside(base, path) ?47 match Io.read(physical) {48 case Ok(held) => Ok(held)49 case Err(reason) => Err(Docgen.error(Codes.FILE_UNREADABLE, path, 1, reason))50 }51}525354export fn bytes(base: Str, path: Str) -> Result[Bytes, Docgen.Diagnostic] {55 let physical = inside(base, path) ?56 match Io.readAllBytes(physical) {57 case Ok(held) => Ok(held)58 case Err(reason) => Err(Docgen.error(Codes.FILE_UNREADABLE, path, 1, show(reason)))59 }60}616263fn inside(base: Str, path: Str) -> Result[Str, Docgen.Diagnostic] {64 if !Paths.relative(path) { return Err(Docgen.error(Codes.FILE_UNREADABLE, path, 1, "path is not a portable relative path")) }65 let physical = match Fs.resolveInside(base, path) {66 case Ok(found) => found67 case Err(reason) => { return Err(Docgen.error(Codes.FILE_UNREADABLE, path, 1, show(reason))) }68 }69 match Fs.metadata(physical) {70 case Ok(details) => {71 if details.isSymbolicLink { return Err(Docgen.error(Codes.FILE_UNREADABLE, path, 1, "symbolic links are not read")) }72 if details.size > MAX_BYTES { return Err(Docgen.error(Codes.FILE_UNREADABLE, path, 1, "file is larger than 32 MiB")) }73 Ok(physical)74 }75 case Err(reason) => Err(Docgen.error(Codes.FILE_UNREADABLE, path, 1, show(reason)))76 }77}787980fn walk(base: Str, relative: Str, skip: &Array[Str], depth: Int) -> Result[Array[Str], Array[Docgen.Diagnostic]] {81 if depth > MAX_DEPTH { return Err([Docgen.error(Codes.FILE_UNREADABLE, relative, 1, "folders nest deeper than " + show(MAX_DEPTH))]) }82 let directory = if relative.isEmpty() { base } else { Path.join(base, relative) }83 let names = match Io.list(directory) {84 case Ok(found) => List.sorted(&found)85 case Err(reason) => { return Err([Docgen.error(Codes.FILE_UNREADABLE, if relative.isEmpty() { "." } else { relative }, 1, reason)]) }86 }87 var result: Array[Str] = []88 for name in names {89 let path = if relative.isEmpty() { name } else { relative + "/" + name }90 if name.startsWith(".") || IGNORED.contains(name) || skip.contains(path) || !Paths.relative(path) { continue }91 let details = match Fs.metadata(Path.join(directory, name)) {92 case Ok(found) => found93 case Err(_) => { continue }94 }95 if details.isSymbolicLink { continue }96 if details.isDirectory { result = result.concat(walk(base, path, skip, depth + 1) ?) } else { result = result.push(path) }97 if result.length() > MAX_FILES { return Err([Docgen.error(Codes.FILE_UNREADABLE, ".", 1, "project holds more than " + show(MAX_FILES) + " files")]) }98 }99 Ok(result)100}101