
Discovery.pudu
Pudu103 lines4.1 KB
1/** @Environment.Discovery.Module — which environment files a load reads */2module PuduLangEnvironment.Discovery34import Std.Fs as Fs5import Std.Io as Io6import Std.List as List7import Std.Option as Option8import Std.Path as Path9import Std.Result as Result10import PuduLangEnvironment.Constants.Names as Names11import PuduLangEnvironment.Domain.Cascade as Cascade12import PuduLangEnvironment as Environment13import PuduLangEnvironment.Options as Options1415/** @Environment.Discovery.Found — the files to read and what was skipped */16export type Found = { paths: Array[Str], skipped: Array[Environment.Problem] }17181920export fn discover(options: &Options.Options, process: &Map[Str, Str]) -> Result[Found, Environment.Problem] {21 if options.cascade { return cascaded(options, process) }22 match options.probe {23 case Some(levels) => {24 let start = realDirectory(options.directory)25 match probe(start, levels, &[Names.DEFAULT_FILE]) {26 case Some(directory) => Ok(Found{paths: [Path.join(directory, Names.DEFAULT_FILE)], skipped: []})27 case None => nothing(options, ancestry(start, levels).map(|directory: Str| Path.join(directory, Names.DEFAULT_FILE)))28 }29 }30 case None => Ok(Found{paths: options.files.map(|path: Str| locate(options.directory, path)), skipped: []})31 }32}333435export fn probe(start: Str, levels: Int, names: &Array[Str]) -> Option[Str] {36 for directory in ancestry(start, levels) {37 for named in names {38 if Io.exists(Path.join(directory, named)) { return Some(directory) }39 }40 }41 None42}434445export fn locate(directory: Str, path: Str) -> Str {46 if Path.isAbsolute(path) || directory.isEmpty() || directory == "." { path } else { Path.join(directory, path) }47}484950fn cascaded(options: &Options.Options, process: &Map[Str, Str]) -> Result[Found, Environment.Problem] {51 let name = Cascade.chooseName(&options.environmentName, process)52 if let Some(named) = name {53 if !Cascade.isSafe(named) { return Err(Environment.UnsafeEnvironmentName(named)) }54 }55 let names = Cascade.candidates(&name)56 let directory = match options.probe {57 case Some(levels) => {58 let start = realDirectory(options.directory)59 match probe(start, levels, &names) {60 case Some(found) => found61 case None => {62 var looked: Array[Str] = []63 for each in ancestry(start, levels) { looked = looked.concat(names.map(|named: Str| Path.join(each, named))) }64 return nothing(options, looked)65 }66 }67 }68 case None => Path.directoryOf(locate(options.directory, Option.unwrapOr(List.first(&options.files), Names.DEFAULT_FILE)))69 }70 let candidates = names.map(|named: Str| locate(directory, named))71 let existing = candidates.filter(|path: Str| Io.exists(path))72 if existing.isEmpty() { nothing(options, candidates) } else { Ok(Found{paths: existing, skipped: []}) }73}747576fn nothing(options: &Options.Options, looked: Array[Str]) -> Result[Found, Environment.Problem] {77 let problem = Environment.NotFound(looked)78 if options.strict { Err(problem) } else { Ok(Found{paths: [], skipped: [problem]}) }79}808182fn ancestry(start: Str, levels: Int) -> Array[Str] {83 var found = [start]84 var current = start85 var climbed = 086 while climbed < levels {87 match Path.parentOf(current) {88 case Some(parent) => {89 found = found.push(parent)90 current = parent91 climbed = climbed + 192 }93 case None => { climbed = levels }94 }95 }96 found97}9899100fn realDirectory(directory: Str) -> Str {101 Result.unwrapOr(Fs.canonical(if directory.isEmpty() { "." } else { directory }), directory)102}103