cliHelper.env.psm1

#!/usr/bin/env pwsh
using namespace System.Management.Automation.Language
using module Private/cliHelper.env.Utils/cliHelper.env.Utils.psm1
using module Private/cliHelper.env.Crypto/cliHelper.env.Crypto.psm1

#region Classes
#Requires -Version 7
# .SYNOPSIS
# Module main class
class dotEnv : EnvTools {
  dotEnv() {
    [dotEnv]::SetEnvFile(); Set-EnvConfig
  }
  static [string] Get([string]$key) {
    [ValidateNotNullOrEmpty()][string]$key = $key
    return [dotEnv]::Read([dotEnv].EnvFile).Where({ $_.Name -eq $key }).Value
  }
  static [dotEntry[]] Read([string]$EnvFile) {
    [ValidateNotNullOrEmpty()][string]$EnvFile = $(Resolve-Path $EnvFile -ea Ignore).Path
    $res_Obj = @(); $content = [IO.File]::ReadAllLines($EnvFile)
    if ([string]::IsNullOrWhiteSpace($content)) {
      Write-Debug "The .env file is empty!"
      return $res_Obj
    }
    foreach ($line in $content) {
      if ([string]::IsNullOrWhiteSpace($line)) { continue }
      if ($line.StartsWith("#") -or $line.StartsWith("//")) {
        Write-Verbose "🔵 ~ comment: $([dotEnv]::sensor($line))"
        continue
      }
      ($m, $d ) = switch -Wildcard ($line) {
        "*:=*" { "Prefix", ($line -split ":=", 2); Break }
        "*=:*" { "Suffix", ($line -split "=:", 2); Break }
        "*=*" { "Assign", ($line -split "=", 2); Break }
        Default {
          throw 'Unable to find Key value pair in line'
        }
      }
      $res_Obj += [dotEntry]::new($d[0].Trim(), $d[1].Trim(), $m)
    }
    return $res_Obj
  }
  static [void] Update([IO.FileInfo]$EnvFile, [string]$Name, [string]$Value) {
    [dotEnv]::Update($EnvFile, $Name, $Value, $false)
  }
  static [void] Update([IO.FileInfo]$EnvFile, [string]$Name, [string]$Value, [bool]$StripComments) {
    $Entries = [dotenv]::Read($EnvFile.FullName);
    if ($StripComments) {
      [IO.File]::WriteAllText($EnvFile,
        $([dotEnv]::Update($Entries, $Name, $Value).ForEach({ $_.ToString() }) | Out-String).Trim(),
        [System.Text.Encoding]::UTF8
      )
    } else {
      $q = $Entries.Where({ $_.Name -eq $Name }); $s = ''
      $sb = [System.Text.StringBuilder]::new([IO.File]::ReadAllText($EnvFile.FullName));
      $ms = [PSObject]@{ Assign = '='; Prefix = ":="; Suffix = "=:" };
      if ($q.count -ne 0) {
        $s = $ms[$q.Action]
        $pa = "(?m)^($Name{0}).*$" -f $s; $re = "$Name{0}$value" -f $s
        $updatedContent = $sb.ToString() -replace $pa, $re
        [IO.File]::WriteAllText($EnvFile.FullName, $updatedContent)
      } else {
        Write-Debug "key $Name was not found. Addind new one ..."
        $Entries += [dotEntry]::new($Name, $Value, "Assign")
        [IO.File]::WriteAllText($EnvFile.FullName, $($Entries.ForEach({ $_.ToString() }) | Out-String))
      }
    }
  }
  static hidden [dotEntry[]] Update([dotEntry[]]$Entries, [string]$Name, [string]$Value) {
    return $Entries.ForEach({ if ($_.Name -eq $Name) { $_.Set($Name, $Value) } })
  }
  static [void] Set([string]$EnvFile) {
    [dotEnv]::Set([dotEnv]::Read($EnvFile))
  }
  static [void] Set([dotEntry[]]$Entries) {
    foreach ($item in $Entries) {
      switch ($item.Action) {
        "Assign" {
          [Environment]::SetEnvironmentVariable($item.Name, $item.value, "Process") | Out-Null
        }
        "Prefix" {
          $item.value = "{0};{1}" -f $item.value, [System.Environment]::GetEnvironmentVariable($item.Name)
          [Environment]::SetEnvironmentVariable($item.Name, $item.value, "Process") | Out-Null
        }
        "Suffix" {
          $item.value = "{1};{0}" -f $item.value, [System.Environment]::GetEnvironmentVariable($item.Name)
          [Environment]::SetEnvironmentVariable($item.Name, $item.value, "Process") | Out-Null
        }
        Default {
          throw [System.IO.InvalidDataException]::new()
        }
      }
    }
  }
  static [void] StripComments([string]$EnvFile) {
    [dotEnv]::StripComments($EnvFile, [System.Text.Encoding]::UTF8)
  }
  static [void] StripComments([string]$EnvFile, [System.Text.Encoding]$Encoding) {
    [ValidateNotNullOrEmpty()][string]$EnvFile = $(Resolve-Path $EnvFile -ea Ignore).Path
    [string]$content = ([dotenv]::Read($EnvFile).ForEach({ $_.ToString() }) | Out-String).Trim()
    [IO.File]::WriteAllText($EnvFile, $content, $Encoding)
  }
  static [bool] IsPersisted([string]$source) {
    $p = @(); $p += [dotEnv]::Config.Persisted;
    return $p.Contains($source)
  }
  static [void] Persist([string]$source) {
    $p = @(); $p += [dotEnv]::Config.Persisted;
    if (!$p.Contains($source)) {
      $p += $source; [dotEnv]::Config.Set("Persisted", $p)
    }
  }
  static [void] Unpersist([string]$source) {
    $p = @(); $p += [dotEnv]::Config.Persisted;
    if (!$p.Contains($source)) {
      $p = $p.where({ $_ -ne $source })
      [dotEnv]::Config.Set("Persisted", $p)
    }
  }
  static [string] sensor([string]$str) {
    if ([string]::IsNullOrWhiteSpace($str)) { return $str }
    $_90 = [int][Math]::Floor($str.Length * .9)
    $_10 = [int][Math]::Floor($str.Length * .1)
    $_sr = ($str.Substring(0, $_10) + '░' * $_90)
    $_cs = "CENSORED"; if ($_sr.Length -gt 13) {
      $50l = [int][Math]::Floor($_sr.Length * .5)
      $_sr = $_sr.Substring(0, $50l - $_cs.Length) + $_cs + $_sr.Substring($50l + $_cs.Length)
    }
    return $_sr
  }
  static [void] SetEnvFile() {
    [dotEnv].PsObject.properties.add([psscriptproperty]::new('EnvFile', { return [IO.Path]::Combine($(Get-Variable executionContext -ValueOnly).SessionState.Path.CurrentLocation.Path, '.env') }))
    [dotEnv].PsObject.properties.add([psscriptproperty]::new('enc_envfile', { return [IO.Path]::Combine($(Get-Variable executionContext -ValueOnly).SessionState.Path.CurrentLocation.Path, '.env.enc') }))
  }
}

#endregion Classes

#region typeAccelerators
# Types that will be available to users when they import the module.
$typestoExport = @(
  [dotEnv]
)
$TypeAcceleratorsClass = [psobject].Assembly.GetType('System.Management.Automation.TypeAccelerators'); $addedtypes = @()
foreach ($Type in $typestoExport) {
  if ($Type.FullName -notin $TypeAcceleratorsClass::Get.Keys) {
    $TypeAcceleratorsClass::Add($Type.FullName, $Type); $addedtypes += $Type
  }
}
# Remove type accelerators when the module is removed.
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
  foreach ($Type in $addedtypes) {
    $TypeAcceleratorsClass::Remove($Type.FullName)
  }
}.GetNewClosure();

#endregion typeAccelerators

$Private = Get-ChildItem ([IO.Path]::Combine($PSScriptRoot, 'Private')) -Filter "*.ps1" -ErrorAction SilentlyContinue
$Public = Get-ChildItem ([IO.Path]::Combine($PSScriptRoot, 'Public')) -Filter "*.ps1" -ErrorAction SilentlyContinue
foreach ($file in ($Public + $Private)) {
  Try {
    if ([string]::IsNullOrWhiteSpace($file.fullname)) { continue }
    . "$($file.fullname)"
  } Catch {
    Write-Warning "Failed to import function $($file.BaseName): $_"
    $host.UI.WriteErrorLine($_)
  }
}

$Param = @{
  Function = $Public.BaseName
  Variable = 'localizedData'
  Cmdlet   = "*"
  Alias    = "*"
}
Export-ModuleMember @Param -Verbose