From b773a7bd5eaa739b0229eb9236da3565a9966d0a Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 27 Feb 2025 23:58:28 +0100 Subject: [PATCH 1/7] Add YAML processing functions: Import, Export, Convert, Merge, and Test --- src/functions/private/Convert-YamlBlock.ps1 | 115 ++++++++++++++++++ src/functions/private/Convert-YamlValue.ps1 | 35 ++++++ .../private/ConvertTo-YamlPSCustomObject.ps1 | 17 +++ src/functions/private/Get-YamlIndentLevel.ps1 | 11 ++ src/functions/private/Merge-YamlObjects.ps1 | 28 +++++ src/functions/private/Test-YamlSchema.ps1 | 38 ++++++ src/functions/public/ConvertFrom-Yaml.ps1 | 11 ++ src/functions/public/ConvertTo-Yaml.ps1 | 64 ++++++++++ src/functions/public/Export-Yaml.ps1 | 11 ++ src/functions/public/Get-PSModuleTest.ps1 | 20 --- src/functions/public/Import-Yaml.ps1 | 9 ++ src/functions/public/Merge-Yaml.ps1 | 20 +++ src/functions/public/Test-Yaml.ps1 | 24 ++++ 13 files changed, 383 insertions(+), 20 deletions(-) create mode 100644 src/functions/private/Convert-YamlBlock.ps1 create mode 100644 src/functions/private/Convert-YamlValue.ps1 create mode 100644 src/functions/private/ConvertTo-YamlPSCustomObject.ps1 create mode 100644 src/functions/private/Get-YamlIndentLevel.ps1 create mode 100644 src/functions/private/Merge-YamlObjects.ps1 create mode 100644 src/functions/private/Test-YamlSchema.ps1 create mode 100644 src/functions/public/ConvertFrom-Yaml.ps1 create mode 100644 src/functions/public/ConvertTo-Yaml.ps1 create mode 100644 src/functions/public/Export-Yaml.ps1 delete mode 100644 src/functions/public/Get-PSModuleTest.ps1 create mode 100644 src/functions/public/Import-Yaml.ps1 create mode 100644 src/functions/public/Merge-Yaml.ps1 create mode 100644 src/functions/public/Test-Yaml.ps1 diff --git a/src/functions/private/Convert-YamlBlock.ps1 b/src/functions/private/Convert-YamlBlock.ps1 new file mode 100644 index 0000000..3c82706 --- /dev/null +++ b/src/functions/private/Convert-YamlBlock.ps1 @@ -0,0 +1,115 @@ +function Convert-YamlBlock { + <# + + #> + param ( + + [string[]]$Lines, + + [int]$StartIndex, + + [int] $IndentLevel + ) + + $i = $StartIndex + # Skip leading empty lines + while ($i -lt $Lines.Count -and $Lines[$i].Trim() -eq '') { + $i++ + } + + # If we've reached the end, return an empty hashtable + if ($i -ge $Lines.Count) { + return @{ + object = @{} + next_index = $i + } + } + + $firstLine = $Lines[$i].Trim() + + # Check if this block is a sequence (starts with "-") + if ($firstLine -like '-*') { + $array = @() + while ($i -lt $Lines.Count) { + $line = $Lines[$i] + if ($line.Trim() -eq '') { + $i++ + continue + } + $currentIndent = Get-YamlIndentLevel -Line $line + if ($currentIndent -lt $IndentLevel) { + break + } + if ($currentIndent -gt $IndentLevel) { + throw "Unexpected indentation at line $($i + 1)" + } + $content = $line.Trim() + if ($content -like '-*') { + if ($content -eq '-') { + # Sequence item is a nested block + $subBlock = Convert-YamlBlock -Lines $Lines -StartIndex ($i + 1) -IndentLevel ($IndentLevel + 1) + if ($subBlock.next_index -eq $i + 1) { + $array += $null + } else { + $array += $subBlock.object + } + $i = $subBlock.next_index + } else { + # Sequence item is a scalar + $rawValue = $content.Substring(1) + $value = Convert-YamlValue -RawValue $rawValue + $array += $value + $i++ + } + } else { + throw "Expected '-' for sequence item at line $($i + 1)" + } + } + return @{ + object = $array + next_index = $i + } + } else { + # This block is a mapping + $hashtable = @{} + while ($i -lt $Lines.Count) { + $line = $Lines[$i] + if ($line.Trim() -eq '') { + $i++ + continue + } + $currentIndent = Get-YamlIndentLevel -Line $line + if ($currentIndent -lt $IndentLevel) { + break + } + if ($currentIndent -gt $IndentLevel) { + throw "Unexpected indentation at line $($i + 1)" + } + $content = $line.Trim() + if ($content -match '^(.*):$') { + # Key with a nested block + $key = $matches[1].Trim() + $subBlock = Convert-YamlBlock -Lines $Lines -StartIndex ($i + 1) -IndentLevel ($IndentLevel + 1) + if ($subBlock.next_index -eq $i + 1) { + $hashtable[$key] = $null + } else { + $hashtable[$key] = $subBlock.object + } + $i = $subBlock.next_index + } elseif ($content -match '^(.*):\s*(.*)$') { + # Key-value pair + $key = $matches[1].Trim() + $rawValue = $matches[2] + $value = Convert-YamlValue -RawValue $rawValue + $hashtable[$key] = $value + $i++ + } else { + throw "Invalid YAML syntax at line $($i + 1)" + } + } + return @{ + object = $hashtable + next_index = $i + } + } +} diff --git a/src/functions/private/Convert-YamlValue.ps1 b/src/functions/private/Convert-YamlValue.ps1 new file mode 100644 index 0000000..2f3715d --- /dev/null +++ b/src/functions/private/Convert-YamlValue.ps1 @@ -0,0 +1,35 @@ + +# Helper function to parse a scalar value and return the appropriate type +function Convert-YamlValue { + param ( + [string]$RawValue + ) + $trimmed = $RawValue.Trim() + if ($trimmed.StartsWith('"') -and $trimmed.EndsWith('"')) { + # Double-quoted string + return $trimmed.Substring(1, $trimmed.Length - 2) + } elseif ($trimmed.StartsWith("'") -and $trimmed.EndsWith("'")) { + # Single-quoted string + return $trimmed.Substring(1, $trimmed.Length - 2) + } else { + # Unquoted value: detect type + if ($trimmed -eq 'null' -or $trimmed -eq 'Null' -or $trimmed -eq 'NULL' -or $trimmed -eq '') { + return $null + } elseif ($trimmed -eq 'true' -or $trimmed -eq 'True' -or $trimmed -eq 'TRUE') { + return $true + } elseif ($trimmed -eq 'false' -or $trimmed -eq 'False' -or $trimmed -eq 'FALSE') { + return $false + } else { + $intValue = 0 + if ([int]::TryParse($trimmed, [ref]$intValue)) { + return $intValue + } + $doubleValue = 0.0 + if ([double]::TryParse($trimmed, [ref]$doubleValue)) { + return $doubleValue + } + # If not a number, return as string + return $trimmed + } + } +} diff --git a/src/functions/private/ConvertTo-YamlPSCustomObject.ps1 b/src/functions/private/ConvertTo-YamlPSCustomObject.ps1 new file mode 100644 index 0000000..f1b49be --- /dev/null +++ b/src/functions/private/ConvertTo-YamlPSCustomObject.ps1 @@ -0,0 +1,17 @@ +# Helper function to convert a hashtable or array to a PSCustomObject recursively +function ConvertTo-YamlPSCustomObject { + param ( + $Object + ) + if ($Object -is [hashtable]) { + $psobj = [PSCustomObject]@{} + foreach ($key in $Object.Keys) { + $psobj | Add-Member -MemberType NoteProperty -Name $key -Value (ConvertTo-YamlPSCustomObject -Object $Object[$key]) + } + return $psobj + } elseif ($Object -is [array]) { + return $Object | ForEach-Object { ConvertTo-YamlPSCustomObject -Object $_ } + } else { + return $Object + } +} diff --git a/src/functions/private/Get-YamlIndentLevel.ps1 b/src/functions/private/Get-YamlIndentLevel.ps1 new file mode 100644 index 0000000..ec75796 --- /dev/null +++ b/src/functions/private/Get-YamlIndentLevel.ps1 @@ -0,0 +1,11 @@ +# Function to calculate the indentation level of a line (assuming 2 spaces per level) +function Get-YamlIndentLevel { + param ( + [string]$Line + ) + $indent = 0 + while ($indent -lt $Line.Length -and $Line[$indent] -eq ' ') { + $indent++ + } + return [math]::Floor($indent / 2) +} diff --git a/src/functions/private/Merge-YamlObjects.ps1 b/src/functions/private/Merge-YamlObjects.ps1 new file mode 100644 index 0000000..d02a511 --- /dev/null +++ b/src/functions/private/Merge-YamlObjects.ps1 @@ -0,0 +1,28 @@ +# Helper function for recursive object merging +function Merge-YamlObjects { + param ( + $Base, + $Override + ) + + if ($Base -is [hashtable] -and $Override -is [hashtable]) { + $merged = @{} + foreach ($key in $Base.Keys) { + if ($Override.ContainsKey($key)) { + $merged[$key] = Merge-YamlObjects -Base $Base[$key] -Override $Override[$key] + } else { + $merged[$key] = $Base[$key] + } + } + foreach ($key in $Override.Keys) { + if (-not $Base.ContainsKey($key)) { + $merged[$key] = $Override[$key] + } + } + return $merged + } elseif ($Base -is [array] -and $Override -is [array]) { + return $Override # Replace arrays + } else { + return $Override # Override scalar values + } +} diff --git a/src/functions/private/Test-YamlSchema.ps1 b/src/functions/private/Test-YamlSchema.ps1 new file mode 100644 index 0000000..a6c5987 --- /dev/null +++ b/src/functions/private/Test-YamlSchema.ps1 @@ -0,0 +1,38 @@ + + +# Helper function for basic schema validation +function Test-YamlSchema { + param ( + $Object, + $Schema + ) + + if ($Schema.type -eq 'object') { + foreach ($prop in $Schema.properties.PSObject.Properties) { + $propName = $prop.Name + $propSchema = $prop.Value + + # Check for required properties + if ($propSchema.required -and -not $Object.PSObject.Properties[$propName]) { + Write-Error "Missing required property: $propName" + return $false + } + + # Check property types if present + if ($Object.PSObject.Properties[$propName]) { + $value = $Object.$propName + if ($propSchema.type -eq 'string' -and $value -isnot [string]) { + Write-Error "Property $propName should be a string" + return $false + } elseif ($propSchema.type -eq 'integer' -and $value -isnot [int]) { + Write-Error "Property $propName should be an integer" + return $false + } + } + } + return $true + } else { + Write-Error "Schema validation only supports 'object' type at root" + return $false + } +} diff --git a/src/functions/public/ConvertFrom-Yaml.ps1 b/src/functions/public/ConvertFrom-Yaml.ps1 new file mode 100644 index 0000000..3a9321b --- /dev/null +++ b/src/functions/public/ConvertFrom-Yaml.ps1 @@ -0,0 +1,11 @@ +# Function to convert YAML string to PSCustomObject +function ConvertFrom-Yaml { + param ( + [Parameter(Mandatory = $true)] + [string]$Yaml + ) + $lines = $Yaml -split "`n" + $parsed = Convert-YamlBlock -Lines $lines -StartIndex 0 -IndentLevel 0 + $rootObject = $parsed.object + return ConvertTo-YamlPSCustomObject -Object $rootObject +} diff --git a/src/functions/public/ConvertTo-Yaml.ps1 b/src/functions/public/ConvertTo-Yaml.ps1 new file mode 100644 index 0000000..a1a9ce3 --- /dev/null +++ b/src/functions/public/ConvertTo-Yaml.ps1 @@ -0,0 +1,64 @@ +# Function to convert PSCustomObject to YAML string +function ConvertTo-Yaml { + param ( + [Parameter(Mandatory = $true)] + $Object, + [int]$IndentLevel = 0 + ) + + $indent = ' ' * ($IndentLevel * 2) + + if ($Object -is [PSCustomObject]) { + $lines = @() + foreach ($property in $Object.PSObject.Properties) { + $key = $property.Name + $value = $property.Value + if ($value -is [PSCustomObject] -or $value -is [array]) { + $lines += "$indent$key`:" + $lines += ConvertTo-Yaml -Object $value -IndentLevel ($IndentLevel + 1) + } else { + # Handle scalar values + if ($value -is [string]) { + $yamlValue = '"' + $value.Replace('"', '\"') + '"' + } elseif ($value -is [int] -or $value -is [double]) { + $yamlValue = $value.ToString() + } elseif ($value -is [bool]) { + $yamlValue = if ($value) { 'true' } else { 'false' } + } elseif ($null -eq $value) { + $yamlValue = 'null' + } else { + $yamlValue = $value.ToString() + } + $lines += "$indent$key`: $yamlValue" + } + } + return $lines -join "`n" + } elseif ($Object -is [array]) { + $lines = @() + foreach ($item in $Object) { + if ($item -is [PSCustomObject] -or $item -is [array]) { + $lines += "$indent- " + $subLines = ConvertTo-Yaml -Object $item -IndentLevel ($IndentLevel + 1) + $lines += $subLines + } else { + # Handle scalar values + if ($item -is [string]) { + $yamlItem = '"' + $item.Replace('"', '\"') + '"' + } elseif ($item -is [int] -or $item -is [double]) { + $yamlItem = $item.ToString() + } elseif ($item -is [bool]) { + $yamlItem = if ($item) { 'true' } else { 'false' } + } elseif ($null -eq $value) { + $yamlItem = 'null' + } else { + $yamlItem = $item.ToString() + } + $lines += "$indent- $yamlItem" + } + } + return $lines -join "`n" + } else { + # Scalar value (though typically not reached) + return "$indent$Object" + } +} diff --git a/src/functions/public/Export-Yaml.ps1 b/src/functions/public/Export-Yaml.ps1 new file mode 100644 index 0000000..5e8651e --- /dev/null +++ b/src/functions/public/Export-Yaml.ps1 @@ -0,0 +1,11 @@ +function Export-Yaml { + param ( + [Parameter(Mandatory = $true)] + $Object, + [Parameter(Mandatory = $true)] + [string]$Path + ) + + $yaml = ConvertTo-Yaml -Object $Object + Set-Content -Path $Path -Value $yaml +} diff --git a/src/functions/public/Get-PSModuleTest.ps1 b/src/functions/public/Get-PSModuleTest.ps1 deleted file mode 100644 index 0e9aacf..0000000 --- a/src/functions/public/Get-PSModuleTest.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -#Requires -Modules Utilities - -function Get-PSModuleTest { - <# - .SYNOPSIS - Performs tests on a module. - - .EXAMPLE - Test-PSModule -Name 'World' - - "Hello, World!" - #> - [CmdletBinding()] - param ( - # Name of the person to greet. - [Parameter(Mandatory)] - [string] $Name - ) - Write-Output "Hello, $Name!" -} diff --git a/src/functions/public/Import-Yaml.ps1 b/src/functions/public/Import-Yaml.ps1 new file mode 100644 index 0000000..b443482 --- /dev/null +++ b/src/functions/public/Import-Yaml.ps1 @@ -0,0 +1,9 @@ +function Import-Yaml { + param ( + [Parameter(Mandatory = $true)] + [string]$Path + ) + + $yamlContent = Get-Content -Path $Path -Raw + return ConvertFrom-Yaml -Yaml $yamlContent +} diff --git a/src/functions/public/Merge-Yaml.ps1 b/src/functions/public/Merge-Yaml.ps1 new file mode 100644 index 0000000..9216f67 --- /dev/null +++ b/src/functions/public/Merge-Yaml.ps1 @@ -0,0 +1,20 @@ +function Merge-Yaml { + param ( + [Parameter(Mandatory = $true)] + [string]$BaseYaml, + [Parameter(Mandatory = $true)] + [string[]]$YamlArray + ) + + # Parse the base YAML + $baseObject = ConvertFrom-Yaml -Yaml $BaseYaml + + # Merge each YAML in the array + foreach ($yaml in $YamlArray) { + $overrideObject = ConvertFrom-Yaml -Yaml $yaml + $baseObject = Merge-YamlObjects -Base $baseObject -Override $overrideObject + } + + # Convert back to YAML + return ConvertTo-Yaml -Object $baseObject +} diff --git a/src/functions/public/Test-Yaml.ps1 b/src/functions/public/Test-Yaml.ps1 new file mode 100644 index 0000000..3d3409d --- /dev/null +++ b/src/functions/public/Test-Yaml.ps1 @@ -0,0 +1,24 @@ +function Test-Yaml { + param ( + [Parameter(Mandatory = $true)] + [string]$Yaml, + [string]$SchemaPath + ) + + try { + # Attempt to parse the YAML + $object = ConvertFrom-Yaml -Yaml $Yaml + + # If no schema is provided, syntax is valid + if (-not $SchemaPath) { + return $true + } else { + # Load the schema and validate + $schema = Get-Content -Path $SchemaPath -Raw | ConvertFrom-Json + return Test-Schema -Object $object -Schema $schema + } + } catch { + Write-Error "YAML syntax error: $_" + return $false + } +} From 90d0f560f42b7e370fc94121cdfad2213155ad95 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 28 Feb 2025 00:04:26 +0100 Subject: [PATCH 2/7] Add YAML processing functions: Convert, Import, Export, Merge, and Test with detailed documentation --- src/functions/private/Convert-YamlBlock.ps1 | 50 ++++++- .../{public => private}/Export-Yaml.ps1 | 0 .../{public => private}/Import-Yaml.ps1 | 0 .../{public => private}/Merge-Yaml.ps1 | 0 .../{public => private}/Test-Yaml.ps1 | 0 tests/PSModuleTest.Tests.ps1 | 16 --- tests/Yaml.Tests.ps1 | 123 ++++++++++++++++++ 7 files changed, 170 insertions(+), 19 deletions(-) rename src/functions/{public => private}/Export-Yaml.ps1 (100%) rename src/functions/{public => private}/Import-Yaml.ps1 (100%) rename src/functions/{public => private}/Merge-Yaml.ps1 (100%) rename src/functions/{public => private}/Test-Yaml.ps1 (100%) delete mode 100644 tests/PSModuleTest.Tests.ps1 create mode 100644 tests/Yaml.Tests.ps1 diff --git a/src/functions/private/Convert-YamlBlock.ps1 b/src/functions/private/Convert-YamlBlock.ps1 index 3c82706..7fdc74b 100644 --- a/src/functions/private/Convert-YamlBlock.ps1 +++ b/src/functions/private/Convert-YamlBlock.ps1 @@ -1,13 +1,57 @@ function Convert-YamlBlock { <# + .SYNOPSIS + Converts a block of YAML text into a PowerShell object. + .DESCRIPTION + This function processes a block of YAML lines and converts it into a corresponding PowerShell object. + It determines whether the block is a sequence or a mapping and parses it accordingly. If the block + represents a sequence (starting with '-'), it returns an array. If it represents a mapping, it returns + a hashtable. Unexpected indentation or invalid YAML syntax will result in an error. + + .EXAMPLE + Convert-YamlBlock -Lines @("key: value") -StartIndex 0 -IndentLevel 0 + + Output: + ```powershell + object : @{key=value} + next_index : 1 + ``` + + Parses a simple key-value pair YAML block and returns a hashtable. + + .EXAMPLE + Convert-YamlBlock -Lines @("- item1", "- item2") -StartIndex 0 -IndentLevel 0 + + Output: + ```powershell + object : @("item1", "item2") + next_index : 2 + ``` + + Parses a YAML sequence into a PowerShell array. + + .OUTPUTS + hashtable. Returns a hashtable if the YAML block represents a mapping. + + array. Returns an array if the YAML block represents a sequence. + + .LINK + https://psmodule.io/Yaml/Functions/Convert-YamlBlock/ #> + [OutputType([hashtable])] + [CmdletBinding()] param ( + # An array of YAML lines to process. + [Parameter(Mandatory)] + [string[]] $Lines, - [string[]]$Lines, - - [int]$StartIndex, + # The starting index in the array from which parsing should begin. + [Parameter(Mandatory)] + [int] $StartIndex, + # The indentation level to be considered while parsing the YAML block. + [Parameter(Mandatory)] [int] $IndentLevel ) diff --git a/src/functions/public/Export-Yaml.ps1 b/src/functions/private/Export-Yaml.ps1 similarity index 100% rename from src/functions/public/Export-Yaml.ps1 rename to src/functions/private/Export-Yaml.ps1 diff --git a/src/functions/public/Import-Yaml.ps1 b/src/functions/private/Import-Yaml.ps1 similarity index 100% rename from src/functions/public/Import-Yaml.ps1 rename to src/functions/private/Import-Yaml.ps1 diff --git a/src/functions/public/Merge-Yaml.ps1 b/src/functions/private/Merge-Yaml.ps1 similarity index 100% rename from src/functions/public/Merge-Yaml.ps1 rename to src/functions/private/Merge-Yaml.ps1 diff --git a/src/functions/public/Test-Yaml.ps1 b/src/functions/private/Test-Yaml.ps1 similarity index 100% rename from src/functions/public/Test-Yaml.ps1 rename to src/functions/private/Test-Yaml.ps1 diff --git a/tests/PSModuleTest.Tests.ps1 b/tests/PSModuleTest.Tests.ps1 deleted file mode 100644 index 8258bb7..0000000 --- a/tests/PSModuleTest.Tests.ps1 +++ /dev/null @@ -1,16 +0,0 @@ -[Diagnostics.CodeAnalysis.SuppressMessageAttribute( - 'PSReviewUnusedParameter', '', - Justification = 'Required for Pester tests' -)] -[Diagnostics.CodeAnalysis.SuppressMessageAttribute( - 'PSUseDeclaredVarsMoreThanAssignments', '', - Justification = 'Required for Pester tests' -)] -[CmdletBinding()] -param() - -Describe 'Module' { - It 'Function: Get-PSModuleTest' { - Get-PSModuleTest -Name 'World' | Should -Be 'Hello, World!' - } -} diff --git a/tests/Yaml.Tests.ps1 b/tests/Yaml.Tests.ps1 new file mode 100644 index 0000000..5b1d452 --- /dev/null +++ b/tests/Yaml.Tests.ps1 @@ -0,0 +1,123 @@ +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSReviewUnusedParameter', '', + Justification = 'Required for Pester tests' +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseDeclaredVarsMoreThanAssignments', '', + Justification = 'Required for Pester tests' +)] +[CmdletBinding()] +param() + +Describe 'Yaml' { + Describe 'ConvertFrom-Yaml' { + Context 'ConvertFrom-Yaml - Basic scalar values' { + It 'parses a simple YAML key-value pair' { + $yaml = 'name: John Doe' + $result = ConvertFrom-Yaml -Yaml $yaml + $result | Should -BeOfType [PSCustomObject] + $result.name | Should -Be 'John Doe' + } + } + + Context 'ConvertFrom-Yaml - Nested structure' { + It 'parses a nested YAML structure' { + $yaml = @' +person: + name: John Doe + age: 30 +'@ + $result = ConvertFrom-Yaml -Yaml $yaml + $result | Should -BeOfType [PSCustomObject] + $result.person | Should -BeOfType [PSCustomObject] + $result.person.name | Should -Be 'John Doe' + $result.person.age | Should -Be 30 + } + } + + Context 'ConvertFrom-Yaml - Arrays' { + It 'parses a YAML list into a PowerShell array' { + $yaml = @' +fruits: + - Apple + - Banana + - Cherry +'@ + $result = ConvertFrom-Yaml -Yaml $yaml + $result.fruits | Should -BeOfType [object[]] + $result.fruits.Count | Should -Be 3 + $result.fruits[0] | Should -Be 'Apple' + } + } + + Context 'ConvertFrom-Yaml - Complex object' { + It 'parses a complex YAML structure with mixed types' { + $yaml = @' +company: + name: TechCorp + employees: + - name: Alice + age: 28 + skills: ["C#", "PowerShell"] + - name: Bob + age: 35 + skills: + - Python + - Go +'@ + $result = ConvertFrom-Yaml -Yaml $yaml + $result.company | Should -BeOfType [PSCustomObject] + $result.company.name | Should -Be 'TechCorp' + $result.company.employees.Count | Should -Be 2 + $result.company.employees[0].name | Should -Be 'Alice' + $result.company.employees[1].skills | Should -Contain 'Go' + } + } + } + + Describe 'ConvertTo-Yaml' { + Context 'ConvertTo-Yaml - Basic scalar values' { + It 'converts a simple object to YAML' { + $object = [PSCustomObject]@{ name = 'John Doe' } + $yaml = ConvertTo-Yaml -Object $object + $yaml | Should -Be 'name: \'John Doe\"" + } + } + + Context 'ConvertTo-Yaml - Nested structure' { + It 'converts a nested object to YAML' { + $object = [PSCustomObject]@{ person = [PSCustomObject]@{ name = 'John Doe'; age = 30 } } + $yaml = ConvertTo-Yaml -Object $object + $yaml | Should -Match 'person:' + $yaml | Should -Match ' name: \'John Doe\"" + $yaml | Should -Match ' age: 30' + } + } + + Context 'ConvertTo-Yaml - Arrays' { + It 'converts an array to YAML' { + $object = [PSCustomObject]@{ fruits = @('Apple', 'Banana', 'Cherry') } + $yaml = ConvertTo-Yaml -Object $object + $yaml | Should -Match 'fruits:' + $yaml | Should -Match ' - \'Apple\"" + $yaml | Should -Match ' - \'Banana\"" + $yaml | Should -Match ' - \'Cherry\"" + } + } + + Context 'ConvertTo-Yaml - YAML with comments' { + It 'ignores comments in YAML while parsing' { + $yaml = @" +# This is a comment +person: + name: John Doe # Inline comment + age: 30 +"@ + $result = ConvertFrom-Yaml -Yaml $yaml + $result | Should -BeOfType [PSCustomObject] + $result.person.name | Should -Be 'John Doe' + $result.person.age | Should -Be 30 + } + } + } +} From 08cf6d960d7f8cd5448f4671be647be11dbb7d7c Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 28 Feb 2025 00:13:28 +0100 Subject: [PATCH 3/7] Fix test assertion to check for string array type in YAML conversion --- tests/Yaml.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Yaml.Tests.ps1 b/tests/Yaml.Tests.ps1 index 5b1d452..d15e80c 100644 --- a/tests/Yaml.Tests.ps1 +++ b/tests/Yaml.Tests.ps1 @@ -44,7 +44,7 @@ fruits: - Cherry '@ $result = ConvertFrom-Yaml -Yaml $yaml - $result.fruits | Should -BeOfType [object[]] + $result.fruits | Should -BeOfType [string[]] $result.fruits.Count | Should -Be 3 $result.fruits[0] | Should -Be 'Apple' } From 9c67dbb3eb81384941fa05da7b1615c9666b33b3 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 28 Feb 2025 00:17:19 +0100 Subject: [PATCH 4/7] Fix test assertion to correctly check for string array type in YAML conversion --- tests/Yaml.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Yaml.Tests.ps1 b/tests/Yaml.Tests.ps1 index d15e80c..a27d0a9 100644 --- a/tests/Yaml.Tests.ps1 +++ b/tests/Yaml.Tests.ps1 @@ -44,7 +44,7 @@ fruits: - Cherry '@ $result = ConvertFrom-Yaml -Yaml $yaml - $result.fruits | Should -BeOfType [string[]] + ($result.fruits) | Should -BeOfType [string[]] $result.fruits.Count | Should -Be 3 $result.fruits[0] | Should -Be 'Apple' } From a1caf6551ede34092dacffd49465c8e96a9a8ee4 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 28 Feb 2025 00:20:12 +0100 Subject: [PATCH 5/7] Enhance YAML test assertions to verify individual fruit values in the conversion result --- tests/Yaml.Tests.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Yaml.Tests.ps1 b/tests/Yaml.Tests.ps1 index a27d0a9..8aed5ab 100644 --- a/tests/Yaml.Tests.ps1 +++ b/tests/Yaml.Tests.ps1 @@ -44,9 +44,10 @@ fruits: - Cherry '@ $result = ConvertFrom-Yaml -Yaml $yaml - ($result.fruits) | Should -BeOfType [string[]] $result.fruits.Count | Should -Be 3 $result.fruits[0] | Should -Be 'Apple' + $result.fruits[1] | Should -Be 'Banana' + $result.fruits[2] | Should -Be 'Cherry' } } From 29b251c78c95b52ae4068116ef2fc9dba07f6013 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 28 Feb 2025 00:28:16 +0100 Subject: [PATCH 6/7] Update employee skills in YAML tests to include Python and JavaScript --- tests/Yaml.Tests.ps1 | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/Yaml.Tests.ps1 b/tests/Yaml.Tests.ps1 index 8aed5ab..1706573 100644 --- a/tests/Yaml.Tests.ps1 +++ b/tests/Yaml.Tests.ps1 @@ -59,7 +59,9 @@ company: employees: - name: Alice age: 28 - skills: ["C#", "PowerShell"] + skills: + - Python + - JavaScript - name: Bob age: 35 skills: @@ -71,6 +73,12 @@ company: $result.company.name | Should -Be 'TechCorp' $result.company.employees.Count | Should -Be 2 $result.company.employees[0].name | Should -Be 'Alice' + $result.company.employees[0].age | Should -Be 28 + $result.company.employees[0].skills | Should -Contain 'Python' + $result.company.employees[0].skills | Should -Contain 'JavaScript' + $result.company.employees[1].name | Should -Be 'Bob' + $result.company.employees[1].age | Should -Be 35 + $result.company.employees[1].skills | Should -Contain 'Python' $result.company.employees[1].skills | Should -Contain 'Go' } } From a00085c53fcdf92a88621f507cda364e959d099c Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 28 Feb 2025 00:42:23 +0100 Subject: [PATCH 7/7] Add error logging for unexpected indentation and invalid YAML syntax in Convert-YamlBlock.ps1 --- src/functions/private/Convert-YamlBlock.ps1 | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/functions/private/Convert-YamlBlock.ps1 b/src/functions/private/Convert-YamlBlock.ps1 index 7fdc74b..53dd471 100644 --- a/src/functions/private/Convert-YamlBlock.ps1 +++ b/src/functions/private/Convert-YamlBlock.ps1 @@ -85,6 +85,7 @@ break } if ($currentIndent -gt $IndentLevel) { + Write-Error "Content: $line" throw "Unexpected indentation at line $($i + 1)" } $content = $line.Trim() @@ -106,6 +107,7 @@ $i++ } } else { + Write-Error "Content: $line" throw "Expected '-' for sequence item at line $($i + 1)" } } @@ -127,6 +129,7 @@ break } if ($currentIndent -gt $IndentLevel) { + Write-Error "Content: $line" throw "Unexpected indentation at line $($i + 1)" } $content = $line.Trim() @@ -148,6 +151,7 @@ $hashtable[$key] = $value $i++ } else { + Write-Error "Content: $line" throw "Invalid YAML syntax at line $($i + 1)" } }