scripts/Interpolate-Item.ps1

function Interpolate-Item {
  [CmdletBinding()]
  param (
    [Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true)]
    [object]$item,

    [Parameter(Mandatory = $false)]
    [hashtable]$step = @{},

    [Parameter(Mandatory = $false)]
    [hashtable]$config = (Coalesce (Peek-Config), @{}),

    [Parameter(Mandatory = $false)]
    [object[]]$rules = @(@{'\[\[\$\((?<eval>.*?)\)\]\]'=''},
      @{'\[\[\.(?<eval>.*?)\]\]'='$context.'})
  )
  process {
    "Interpolate-Item:In:$(@{'$_'=$_} | ConvertTo-Yaml)" | f-log-dbg
    "Interpolate-Item:In:$(@{'$step'=$step} | ConvertTo-Yaml)" | f-log-dbg

    if (-not $item) {
      return $step
    }

    $context_tmp = $step['$context']
    if ($context_tmp) { $step.Remove('$context') }

    if (($item.GetType() -match 'HashtableEnumerator') `
        -or ($item -is [Collections.DictionaryEntry])) {
      $item = $item | ForEach-Object { @{Key = $_.Key; Value = $_.Value } }
    }

    $context = $step
    if ($item -is [hashtable]) {
      $context = Merge-Hashtables $step $item
    }
    $context = $context | Interpolate-Evals
    "Interpolate-Item:Build-Context:$(@{'$context'=$context} | ConvertTo-Yaml)" | f-log-dbg

    try {
      $json = $context | ConvertTo-Json -Depth 99
      "Interpolate-Item:Merge:`$json = $json" | f-log-dbg
      $counter = 0
      foreach ($regex in $rules.Keys) {
        while ($json -match $regex) {
          "Interpolate-Item:$(@{'$matches'=$matches} | ConvertTo-Yaml)" | f-log-dbg
          $subst = $matches.0
          $eval = $matches.eval
          if (-not $eval) {
            if ($item -is [hashtable]){
              $value = $context | f-subtract -s $step
            } else {
              $value = $item
            }
          }
          else {
            $eval = ConvertFrom-Json "`"$($rules.$($regex))$eval`""
            $value = Invoke-Expression $eval
          }
          if ($value -isnot [string]) {
            # assign complex values via json
            $value = $value | ConvertTo-Json -Depth 99
            "Interpolate-Item:Out:ConvertTo-Json:`$value = $value" | f-log-dbg
            # it might be former string, so:
            $json = $json.Replace("`"$subst`"", $value)
          }
          else {
            $value = $value | f-escape-json
          }
          "Interpolate-Item:Replace:In:`n$(@{subst=`"$subst`";eval=`"$eval`";value=`"$value`"} | ConvertTo-Yaml)" | f-log-dbg
          $json = $json.Replace($subst, $value)
          "Interpolate-Item:Replace:Out:$(@{'$json'=`"$json`"} | ConvertTo-Yaml)" | f-log-dbg

          if ($counter++ -ge ${global:pwshake-context}.options.max_depth) {
            throw "Circular reference detected for evaluations: $($regex.Matches($json) | Sort-Object -Property Value)"
          }

          $context = $json | ConvertFrom-Yaml
        }
      }
      "Interpolate-Item:Loop-Out:$(@{'$context'=$context} | ConvertTo-Yaml)" | f-log-dbg
    }
    finally {
      if ($context_tmp) { $context['$context'] = $context_tmp }
      if ($step.Keys.Count) {
        $item.Keys | f-null | ForEach-Object { $context.Remove($_) }
      }
    }

    "Interpolate-Item:Out:$(@{'$context'=$context} | ConvertTo-Yaml)" | f-log-dbg
    return $context
  }
}