private/_PSTSYaml.ps1
$includeValues = [ordered]@{ } function _fromYAML { [CmdletBinding()] param ( [parameter(Position = 0, Mandatory = $false, ValueFromPipeline = $true)] [string] $YamlString, [parameter(Position = 1, Mandatory = $false, ValueFromPipeline = $false)][string] $Path, [switch]$representational ) try { If($Path) { $streamReader = [System.IO.File]::OpenText($Path) } Else { $streamReader = new-object System.IO.StringReader([string]$yamlString) } $yamlStream = New-Object YamlDotNet.RepresentationModel.YamlStream $yamlStream.Load([System.IO.TextReader]$streamReader) $root = $yamlStream.Documents[0] if ($representational) { return ,$root.RootNode } [System.Collections.Specialized.OrderedDictionary]$tree = _process ($yamlStream.Documents[0]) return $tree } Catch { Throw $_ } Finally { if ($null -ne $streamReader.Basestream) { $streamReader.Close() if ( $representational -eq $false) { $includeValues.Clear() } } } } function _parseScalar { [OutputType([System.String],[System.Int32],[System.Int64],[System.Decimal],[System.Single],[System.Double], [System.DateTime])] [CmdletBinding()] param ( [YamlDotNet.RepresentationModel.YamlScalarNode]$node ) $value = "$($node)" #manage yes/no |on/off values $value = switch -Regex ($value) { '(?i)\A(?:on|yes)\z' { 'true' break } '(?i)\A(?:off|no)\z' { 'false' break } default { $value } } $out = $null if ([bool]::TryParse($value, [ref]$out)) { return $out} if ( $value -as [int] -or $value -eq "0" ) { return [int]$value } if ( $value -as [long] ) { return [long]$value } if ( $value -as [decimal] ) { return [decimal]$value } if ( $value -as [single] ) { return [single]$value } if ( $value -as [double] ) { return [double]$value } #if ( $value -as [DateTime] ) { return [DateTime]$value } return [string]$value } function _getFromKey() { [CmdletBinding()] param ( [object[]]$lookup, [string]$key ) $data = $null Foreach ($n in $lookup) { if ($n.Key -eq $key) { $data = $n.Value break } elseif ($n.GetType().Name -eq "YamlSequenceNode") { return _getFromKey -lookup $n -key $key } } return ,$data } function _parseFunction { [CmdletBinding()] param ( [object]$node, [object]$scope = $null ) if ($node.GetType().Name -ne "YamlSequenceNode") { return $node } $typed = [YamlDotNet.RepresentationModel.YamlSequenceNode]$node function isNumeric([System.Object] $obj) { return $obj -is [byte] -or $obj -is [int16] -or $obj -is [Int32] -or $obj -is [int64] ` -or $obj -is [sbyte] -or $obj -is [uint16] -or $obj -is [uint32] -or $obj -is [uint64] ` -or $obj -is [float] -or $obj -is [double] -or $obj -is [decimal] } switch ($typed.Tag) { "!add" { $ret = [YamlDotNet.RepresentationModel.YamlScalarNode]::new() $tmp = 0 foreach($obj in $typed.Children) { $val = _process $obj $scope if(!(isNumeric($val))) { Throw "invalid value : $($val)" } $tmp = $tmp + $val } $ret.Value = $tmp } "!mul" { $ret = [YamlDotNet.RepresentationModel.YamlScalarNode]::new() $tmp = 1 foreach($obj in $typed.Children) { $val = _process $obj $scope if(!(isNumeric($val))) { Throw "invalid value : $($val)" } $tmp = $tmp * $val } $ret.Value = $tmp } "!count" { $val = _process $($typed | Select-Object -First 1) $scope $ret = [YamlDotNet.RepresentationModel.YamlScalarNode]::new() $ret.Value = $val.Count } "!tolower" { #if($typed.Value.Count -ne 1) { Throw "Too many strings to lower" } $val = _process ($typed | Select-Object -First 1) $scope if(!($val -is [string])) { Throw "$($val) is not a string" } $ret = [YamlDotNet.RepresentationModel.YamlScalarNode]::new() $ret.Value = $val.ToLower() } "!toupper" { #if($typed.Value.Count -ne 1) { Throw "Too many strings to upper" } $val = _process ($typed | Select-Object -First 1) $scope if(!($val -is [string])) { Throw "$($val) is not a string" } $ret = [YamlDotNet.RepresentationModel.YamlScalarNode]::new() $ret.Value = $val.ToUpper() } "!include" { $file = _process $($typed | Select-Object -First 1) $scope $key = _process $($typed | Select-Object -skip 1 -First 1) $scope $values= _fromYAML -Path $file -representational $Includevalues.Add($key, $values) break } "!ruleset" { $value = _process $($typed | Select-Object -First 1) $scope $rulesetNode = $($typed | Select-Object -First 1 -skip 1) $ruleset = _process $rulesetNode $scope $match = [regex]::IsMatch($value, $ruleset) if ($match -eq $true) { throw [exception]::new("ruleset matched a forbidden string on $value .") } $ret = [YamlDotNet.RepresentationModel.YamlScalarNode]::new() $ret.Value = $value } "!get" { $key = _process $($typed | Select-Object -First 1) $scope $data = _process $($typed | Select-Object -skip 1 -First 1) $scope if($data.GetType().Name -eq 'OrderedDictionary') { $list = $data.Keys } else { $list = $data.Split(".") } $toleration = (_process $($typed | Select-Object -skip 2 -First 1 ) $scope) $rulesetNode = $($typed | Select-Object -skip 3 -First 1 ) if ($null -ne $rulesetNode) { $ruleset = _process $rulesetNode $scope } $values = $Includevalues.($key) $tmp = $null $lookup = $values foreach ($item in $list) { if ($item -eq "*") { $tmp = $lookup break } $tmp = _getFromKey -lookup $lookup -key $item if ($null -ne $tmp) { $lookup = ($lookup | Where-Object { $null -eq (Compare-Object $_.Value $tmp) }).Value } } #if ($tmp -eq $null) { # return $null #} if ($null -ne $ruleset -and $null -ne $tmp -and $tmp.GetType().Name -eq "YamlScalarNode") { $match = [regex]::IsMatch($tmp.Value, $ruleset) if ($match -eq $true) { throw [exception]::new("ruleset matched a forbidden string on $($tmp.Value) .") } } if ($null -eq $tmp -and $toleration -eq $false) { throw [exception]::new("Null value returned for $data with no toleration set") } return ,$tmp } "!concat" { $ret = [YamlDotNet.RepresentationModel.YamlScalarNode]::new() $typed | ForEach-Object -process { $current = _parseFunction $_ $scope $ret.Value = "$($ret.Value)$($current.value)" } break } "!format" { $ret = @() $toFormat = [YamlDotNet.RepresentationModel.YamlScalarNode]::new($($typed | Select-Object -First 1)) $formatParams = @() $($typed | Select-Object -skip 1) | ForEach-Object -process { $data = _parseFunction $_ $scope $cast = $data -as [YamlDotNet.RepresentationModel.YamlNode] $formatParams += $cast.value } $toFormat.Value = $toFormat.Value -f $formatParams return ,$toFormat break } default { $ret = $typed break } } return ,$ret } function _foreach { [CmdletBinding()] param ( $dataBlock, $collection, $iterator, $scope ) $new = @() $collection | ForEach-Object { $block = [YamlDotNet.RepresentationModel.YamlSequenceNode]::new() $scope[$iterator] = $_ $block = _process -node $dataBlock -obj $scope $new += @($block) } return ,$new } function _process { [CmdletBinding()] param ( $node, $obj=$null ) $nodeType = $node.GetType().Name Write-Verbose "$nodeType = $($node)" switch ($nodeType) { "OrderedDictionary" { return $node } "YamlDocument" { $typed = [YamlDotNet.RepresentationModel.YamlDocument]$node return _process $typed.RootNode $obj } "YamlMappingNode" { $typed = [YamlDotNet.RepresentationModel.YamlMappingNode]$node $ret = [ordered]@{ } foreach ($item in $typed) { #check if we match a directive and process accordingly $match = [regex]::Match($item.Key.Value, "{{([a-zA-z]*?)}}") if ($match.Success) { $parsed = _parseDirectives ` -directive $item ` -node ($typed.Children.GetEnumerator() | Where-Object { $_.Key.Value -eq "{{do}}" }) ` -scope $obj #$parsed = _parseDirectives ` #-directive $item ` #-node ($typed | Select-Object -skip 1 -First 1) ` #-scope $obj return ,$parsed break } else { $data = _process $item.Value $obj if ($null -ne $data) { $ret.($item.Key.Value) = $data } } } return ,$ret } "YamlSequenceNode" { $parsed = _parseFunction $node $obj if ($null -eq $parsed) { return $null } if ($parsed.GetType().Name -ne "YamlSequenceNode") { $ret = _process $parsed $obj } else { $ret = @() foreach($i in $parsed) { $item = _process $i $obj if ($null -ne $item -and $item.count -ne 0) { $ret+=$item } } } return ,$ret } "YamlScalarNode" { $matches = [regex]::Matches($node.Value,"%%[^\s%%=\/!@#$%^&*(),?`":{}|<>]+") if ($matches.Count -eq 0) { return _parseScalar $node } $tmp = [YamlDotNet.RepresentationModel.YamlScalarNode]::new($node.Value) foreach ($match in $matches) { $target = $match.Value.Substring(2) $resolveArray= $target.split(".") $iterator = $resolveArray[0] $lookupScope = $obj[$iterator] if ($resolveArray.Count -eq 1 -and $resolveArray[0] -eq $iterator) { if ($lookupScope.GetType().Name -eq "YamlScalarNode") { $tmp.Value = $tmp.Value -replace $match.Value, $lookupScope.Value break } else { $tmp = $lookupScope.Value break } } foreach ($k in $lookupScope) { $data = $k.($resolveArray[1]) foreach ($i in ($resolveArray | Select -Skip 2)) { $data = $data.($i) } if ($null -ne $data) { #if ($resolveArray[1] -eq $k.Key.Value) { if (($data.GetType().Name -ne "OrderedDictionary") -and ($data.GetType().Name -ne "Object[]")) { #if ($k.Value.GetType().Name -eq "YamlScalarNode") { $tmp.Value = $tmp.Value -replace $match.Value, $data # $tmp.Value = $tmp.Value -replace $match.Value, $k.Value.Value break } else { $tmp = $data #$k.Value break } } } if ($tmp.ToString() -eq $node.Value) { return $null } } $override = ($includeValues.Values | Where-Object { $_.Key -eq $tmp -or $_.Key -eq $tmp.value }).Value.Value #$override = ($includeValues.Values | Where-Object { $_.Key -eq $tmp.Value }).Value.Value if ($override) { $tmp = $override return _process $tmp $obj } else { return _process $tmp $obj } #if ($override) { # $tmp.Value = $override #} #return _process $tmp $obj } default { return $node } } } Function _parseDirectives { [CmdletBinding()] param ( [object]$directive, [object]$node, [object]$scope = $null ) if ($null -eq $scope) { $scope = [ordered]@{} } $func = $directive.Key.Value if ($func.Contains("{{export}}")) { $exportKey = _parseFunction ($directive.Value | Select-Object -First 1) $scope $exportRaw = _parseFunction ($directive.Value | Select-Object -First 1 -skip 1) $scope $isAppend = _process ($directive.Value | Select-Object -First 1 -skip 2) $scope $exportValue = _process -node $exportRaw -obj $scope if ( $null -eq $Includevalues.($exportKey.Value)) { $Includevalues.Add($exportKey.Value, $exportValue) } elseif ($isAppend -eq $true) { $Includevalues.($exportKey.Value) = "{0} {1}" -f $Includevalues.($exportKey.Value), $exportValue } elseif ($isAppend -eq $false) { $Includevalues.($exportKey.Value) = $exportValue } return $null } if ($func.Contains("{{foreach}}")) { $collection = _parseFunction ($directive.Value | Select-Object -First 1) $scope if ($null -eq $collection) { return $null } # if ($collection.GetType().Name -eq "YamlScalarNode") { #this must return a valid yaml. it will fails otherwise, which is the case calling process #because it will returns an ordereddictionary which is the input ! $collection = _process -node $collection -obj $scope # } $iterator = _parseFunction ($directive.Value | Select-Object -First 1 -skip 1) $scope $ret = _foreach -collection $collection $node.Value $iterator.Value $scope return ,$ret } if ($func.Contains("{{if}}")) { $right = _process $(_parseFunction ($directive.Value | Select-Object -First 1) $scope) $scope $operand = _process $(_parseFunction ($directive.Value | Select-Object -First 1 -Skip 1) $scope) $scope $left = _process $(_parseFunction ($directive.Value | Select-Object -First 1 -Skip 2) $scope) $scope if ($left -ne $null -and $left.ToString() -eq "null") { $left = $null } if ($left -ne $null -and $right.ToString() -eq "null") { $right = $null } if ($null -ne $right -or $null -ne $left) { $op = Invoke-Expression "return `"$right`" $operand `"$left`"" if ($op -eq $true) { $ret = _process $node.Value $scope } } return ,$ret } #skip, although it is not mandatory it keeps the eyes open on the chart #if ($func.Contains("{{end}}")) { #} return $ret } |