Pudu programming language
Menu
Package

@chrismichaelps / pudu-lang-environment

Environment files for Pudu: load .env variables safely with layered files, discovery, expansion, typed reads, and secrets kept out of every message

0.1.0Apache-2.01

InstallClose

Variables.pudu

Pudu119 lines5.1 KB

GitHub ↗
1/** @Environment.Variables.Module — loaded values settled over the process */2module PuduLangEnvironment.Variables34import Std.App.Secret as Secret5import Std.Env as Env6import Std.Map as Map7import Std.Process as Process8import PuduLangEnvironment.Constants.Messages as Messages9import PuduLangEnvironment.Constants.Names as Names10import PuduLangEnvironment.Domain.Layers as Layers11import PuduLangEnvironment.Domain.Values as Values12import PuduLangEnvironment as Environment13import PuduLangEnvironment.Utils.Template as Template1415/** @Environment.Variables.Variables — a settled view whose values stay hidden */16export type Variables = {17  lookup: fn(Str) -> Option[Str],18  applied: Array[Str],19  sources: Array[Str],20  skipped: Array[Environment.Problem]21}2223/// The loaded values settled over the process values: each replaces a process value when24/// overwriting, or when the process value is unset or empty.25export fn settled(loaded: &Map[Str, Str], current: &Map[Str, Str], overwrite: Bool, sources: Array[Str], skipped: Array[Environment.Problem]) -> Variables {26  let view = Layers.settle(loaded, current, overwrite)27  let values = view.values28  Variables{lookup: fn(key: Str) -> Option[Str] { values.get(key) }, applied: view.applied, sources: sources, skipped: skipped}29}3031/// The process environment alone, read once.32export fn process() -> Variables {33  settled(&Map.empty(), &Map.fromPairs(&Env.variables()), true, [], [])34}3536/// The value of a key that is set and not empty.37export fn find(variables: &Variables, key: Str) -> Option[Str] {38  let held = (variables.lookup)(key) ?39  if held.isEmpty() { None } else { Some(held) }40}4142/// Whether a key is set and not empty.43export fn has(variables: &Variables, key: Str) -> Bool { find(variables, key) != None }4445/// The value of a key, or `Missing` naming it.46export fn text(variables: &Variables, key: Str) -> Result[Str, Environment.Problem] {47  match find(variables, key) {48    case Some(held) => Ok(held)49    case None => Err(Environment.Missing([key]))50  }51}5253/// The whole number a key holds, or `Missing` or `Malformed` naming it.54export fn int(variables: &Variables, key: Str) -> Result[Int, Environment.Problem] {55  converted(variables, key, Values.toInt, Names.KIND_INT)56}5758/// The whole number a key holds, when it is set and spells one.59export fn findInt(variables: &Variables, key: Str) -> Option[Int] { Values.toInt(find(variables, key) ?) }6061/// The floating-point number a key holds, or `Missing` or `Malformed` naming it.62export fn float(variables: &Variables, key: Str) -> Result[Float64, Environment.Problem] {63  converted(variables, key, Values.toFloat, Names.KIND_FLOAT)64}6566/// The floating-point number a key holds, when it is set and spells one.67export fn findFloat(variables: &Variables, key: Str) -> Option[Float64] { Values.toFloat(find(variables, key) ?) }6869/// The exact decimal number a key holds, or `Missing` or `Malformed` naming it.70export fn decimal(variables: &Variables, key: Str) -> Result[Decimal, Environment.Problem] {71  converted(variables, key, Values.toDecimal, Names.KIND_DECIMAL)72}7374/// The exact decimal number a key holds, when it is set and spells one.75export fn findDecimal(variables: &Variables, key: Str) -> Option[Decimal] { Values.toDecimal(find(variables, key) ?) }7677/// The truth value a key holds, or `Missing` or `Malformed` naming it.78export fn bool(variables: &Variables, key: Str) -> Result[Bool, Environment.Problem] {79  converted(variables, key, Values.toBool, Names.KIND_BOOL)80}8182/// The truth value a key holds, when it is set and spells one.83export fn findBool(variables: &Variables, key: Str) -> Option[Bool] { Values.toBool(find(variables, key) ?) }8485/// The value of a key wrapped so that rendering it shows a placeholder, or `Missing` naming it.86export fn secret(variables: &Variables, key: Str) -> Result[Secret.Secret, Environment.Problem] {87  Ok(Secret.secret(text(variables, key) ?))88}8990/// Nothing when every key is set and not empty, else `Missing` with every absent key in the order91/// asked.92export fn require(variables: &Variables, keys: &Array[Str]) -> Result[(), Environment.Problem] {93  let absent = keys.filter(|key: Str| !has(variables, key))94  if absent.isEmpty() { Ok(()) } else { Err(Environment.Missing(absent)) }95}9697/// The launch giving the child process every applied key and its value.98export fn apply(variables: &Variables, launch: &Process.Launch) -> Process.Launch {99  var given = *launch100  for key in variables.applied {101    if let Some(held) = (variables.lookup)(key) { given = Process.Launch{..given, variables: given.variables.push((key, held))} }102  }103  given104}105106/// How many keys were applied from which sources, without any value.107export fn describe(variables: &Variables) -> Str {108  let from = if variables.sources.isEmpty() { Messages.NO_SOURCES } else { variables.sources.join(Messages.LIST_SEPARATOR) }109  Template.fill(Messages.DESCRIBED, &[show(variables.applied.length()), from])110}111112/// The value of a key converted, or `Missing` or `Malformed` naming it and the kind expected.113fn converted[T](variables: &Variables, key: Str, convert: fn(Str) -> Option[T], kind: Str) -> Result[T, Environment.Problem] {114  match convert(text(variables, key) ?) {115    case Some(value) => Ok(value)116    case None => Err(Environment.Malformed(key, kind))117  }118}119