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

Options.pudu

Pudu159 lines6.7 KB

GitHub ↗
1/** @Environment.Options.Module — what a load reads and how */2module PuduLangEnvironment.Options34import PuduLangEnvironment.Constants.Messages as Messages5import PuduLangEnvironment.Constants.Names as Names6import PuduLangEnvironment.Domain.Encoding as Encoding7import PuduLangEnvironment.Source as Source89/** @Environment.Options.Options — every choice a load makes */10export type Options = {11  strict: Bool,12  files: Array[Str],13  sources: Array[Source.Source],14  encoding: Encoding.Encoding,15  trimValues: Bool,16  overwrite: Bool,17  probe: Option[Int],18  exportSyntax: Bool,19  inlineComments: Bool,20  expansion: Bool,21  cascade: Bool,22  environmentName: Option[Str],23  directory: Str24}2526/// Lenient, reading `.env` in the working directory as UTF-8, untrimmed, overwriting, with inline27/// comments and without export syntax, expansion, probing, or the cascade.28export fn defaults() -> Options {29  Options {30    strict: false,31    files: [Names.DEFAULT_FILE],32    sources: [],33    encoding: Encoding.Utf8,34    trimValues: false,35    overwrite: true,36    probe: None,37    exportSyntax: false,38    inlineComments: true,39    expansion: false,40    cascade: false,41    environmentName: None,42    directory: "."43  }44}4546/// Every reason the options cannot be followed, all at once.47export fn validate(options: &Options) -> Array[Str] {48  let givenFiles = options.files != [Names.DEFAULT_FILE]49  let givenSources = !options.sources.isEmpty()50  let probing = options.probe != None51  var problems: Array[Str] = []52  if options.files.isEmpty() { problems = problems.push(Messages.NO_FILES) }53  if givenFiles && probing { problems = problems.push(Messages.FILES_WITH_PROBE) }54  if givenFiles && options.cascade { problems = problems.push(Messages.FILES_WITH_CASCADE) }55  if givenSources && givenFiles { problems = problems.push(Messages.SOURCES_WITH_FILES) }56  if givenSources && probing { problems = problems.push(Messages.SOURCES_WITH_PROBE) }57  if givenSources && options.cascade { problems = problems.push(Messages.SOURCES_WITH_CASCADE) }58  if let Some(levels) = options.probe {59    if levels < 0 { problems = problems.push(Messages.NEGATIVE_PROBE) }60  }61  problems62}6364/** @Environment.Options.Configuring — options changed one choice at a time */65export trait Configuring {66  fn withStrict(self: &Self) -> Self67  fn withoutStrict(self: &Self) -> Self68  fn withFiles(self: &Self, paths: &Array[Str]) -> Self69  fn withSources(self: &Self, given: &Array[Source.Source]) -> Self70  fn withEncoding(self: &Self, encoding: Encoding.Encoding) -> Self71  fn withTrimValues(self: &Self) -> Self72  fn withoutTrimValues(self: &Self) -> Self73  fn withOverwrite(self: &Self) -> Self74  fn withoutOverwrite(self: &Self) -> Self75  fn withProbe(self: &Self) -> Self76  fn withProbeLevels(self: &Self, levels: Int) -> Self77  fn withoutProbe(self: &Self) -> Self78  fn withExportSyntax(self: &Self) -> Self79  fn withoutExportSyntax(self: &Self) -> Self80  fn withInlineComments(self: &Self) -> Self81  fn withoutInlineComments(self: &Self) -> Self82  fn withExpansion(self: &Self) -> Self83  fn withoutExpansion(self: &Self) -> Self84  fn withCascade(self: &Self) -> Self85  fn withEnvironment(self: &Self, name: Str) -> Self86  fn withoutCascade(self: &Self) -> Self87  fn inDirectory(self: &Self, directory: Str) -> Self88}8990impl Configuring for Options {91  /// The same options refusing a missing or unreadable source.92  fn withStrict(self: &Self) -> Self { Options{..*self, strict: true} }9394  /// The same options skipping and recording a missing or unreadable source.95  fn withoutStrict(self: &Self) -> Self { Options{..*self, strict: false} }9697  /// The same options reading these files in order; no files means `.env`.98  fn withFiles(self: &Self, paths: &Array[Str]) -> Self {99    Options{..*self, files: if paths.isEmpty() { [Names.DEFAULT_FILE] } else { *paths }}100  }101102  /// The same options reading these sources instead of files.103  fn withSources(self: &Self, given: &Array[Source.Source]) -> Self { Options{..*self, sources: *given} }104105  /// The same options decoding bytes in this encoding when no byte-order mark says otherwise.106  fn withEncoding(self: &Self, encoding: Encoding.Encoding) -> Self { Options{..*self, encoding: encoding} }107108  /// The same options trimming surrounding space from every value.109  fn withTrimValues(self: &Self) -> Self { Options{..*self, trimValues: true} }110111  /// The same options keeping values as written.112  fn withoutTrimValues(self: &Self) -> Self { Options{..*self, trimValues: false} }113114  /// The same options letting later assignments and loaded values replace earlier ones.115  fn withOverwrite(self: &Self) -> Self { Options{..*self, overwrite: true} }116117  /// The same options keeping the first assignment and every non-empty process variable.118  fn withoutOverwrite(self: &Self) -> Self { Options{..*self, overwrite: false} }119120  /// The same options probing the default number of parent directories for `.env`.121  fn withProbe(self: &Self) -> Self { Options{..*self, probe: Some(Names.DEFAULT_PROBE_LEVELS)} }122123  /// The same options probing this many parent directories.124  fn withProbeLevels(self: &Self, levels: Int) -> Self { Options{..*self, probe: Some(levels)} }125126  /// The same options reading files where they are named.127  fn withoutProbe(self: &Self) -> Self { Options{..*self, probe: None} }128129  /// The same options reading `export KEY=value` as `KEY=value`.130  fn withExportSyntax(self: &Self) -> Self { Options{..*self, exportSyntax: true} }131132  /// The same options reading `export` as part of the key.133  fn withoutExportSyntax(self: &Self) -> Self { Options{..*self, exportSyntax: false} }134135  /// The same options ending an unquoted value at a `#` after whitespace.136  fn withInlineComments(self: &Self) -> Self { Options{..*self, inlineComments: true} }137138  /// The same options keeping `#` in unquoted values.139  fn withoutInlineComments(self: &Self) -> Self { Options{..*self, inlineComments: false} }140141  /// The same options expanding `$NAME` and `${NAME}` in unquoted and double-quoted values.142  fn withExpansion(self: &Self) -> Self { Options{..*self, expansion: true} }143144  /// The same options keeping `$` as written.145  fn withoutExpansion(self: &Self) -> Self { Options{..*self, expansion: false} }146147  /// The same options reading the layered files the environment's name selects.148  fn withCascade(self: &Self) -> Self { Options{..*self, cascade: true} }149150  /// The same options reading the layered files `name` selects.151  fn withEnvironment(self: &Self, name: Str) -> Self { Options{..*self, cascade: true, environmentName: Some(name)} }152153  /// The same options reading given files instead of the cascade.154  fn withoutCascade(self: &Self) -> Self { Options{..*self, cascade: false} }155156  /// The same options locating relative files and starting probes in `directory`.157  fn inDirectory(self: &Self, directory: Str) -> Self { Options{..*self, directory: directory} }158}159