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

Walk.pudu

Pudu101 lines4.2 KB

GitHub ↗
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 Paths1112/// Deepest folder nesting read.13const MAX_DEPTH: Int = 641415/// Most files a project may hold.16const MAX_FILES: Int = 1000001718/// Largest file read, in bytes.19const MAX_BYTES: Int = 335544322021/// Folder names never read: tool state and dependency caches.22const IGNORED: Array[Str] = ["node_modules", "obj", "bin", "deps", "dist-newstyle"]2324/// Every regular file under a folder as a portable relative path, sorted. Hidden entries,25/// ignored folders, the `skip` folders, and symbolic links are left out.26export 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}3031/// The text of files by relative path; unreadable files are reported and left out.32export 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}4344/// The text of one file inside the folder.45export 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}5253/// The bytes of one file inside the folder.54export 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}6162/// The physical path of a relative path, refused when it leaves the folder or is a link.63fn 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}7879/// Files below a relative folder.80fn 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