Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
da33548
Add ConvertFrom-Yaml and ConvertTo-Yaml Pester tests and remove place…
MariusStorhaug May 2, 2026
c3e8c11
Implement ConvertFrom-Yaml with mappings, sequences, scalars, and fro…
MariusStorhaug May 2, 2026
b4245b4
Implement ConvertTo-Yaml with mappings, sequences, scalars, and quoti…
MariusStorhaug May 2, 2026
a2e28bc
Update README and example with Yaml usage
MariusStorhaug May 2, 2026
4093211
Align scalar resolution with YAML 1.2.2 core schema (case-sensitive t…
MariusStorhaug May 2, 2026
80420d3
Drop frontmatter extraction; require valid YAML input and tolerate do…
MariusStorhaug May 2, 2026
732b6e6
Remove markdown and frontmatter references from module code and README
MariusStorhaug May 2, 2026
84344f7
Fix PSSA and codespell linter warnings: switch to switch-statement in…
MariusStorhaug May 2, 2026
2c3d26d
Fix PSScriptAnalyzer violations: add comment help and OutputType decl…
MariusStorhaug May 2, 2026
a1c9b4e
Fix Build-Docs lint: remove extra bullet indent in ConvertFrom-Yaml d…
MariusStorhaug May 2, 2026
b4113eb
Fix SourceCode tests: split private helpers into one-function-per-file
MariusStorhaug May 2, 2026
8956c19
Fix mapping key parsing: preserve raw text without type resolution, u…
MariusStorhaug May 2, 2026
7ce8fd0
Fix empty collection serialization, depth-exceeded indent, and [] / {…
MariusStorhaug May 3, 2026
da93f35
Add tests for handling various YAML data types and structures in Conv…
MariusStorhaug May 3, 2026
03cad1f
Enforce -Depth on nested sequences by routing through ConvertTo-YamlN…
MariusStorhaug May 3, 2026
82bedcd
Merge branch 'main' into feature/2-convert-yaml-functions
MariusStorhaug May 4, 2026
7030c97
Fix PSUseConsistentWhitespace: add spaces after unary comma operators…
MariusStorhaug May 4, 2026
29bba1d
Fix NaN/Infinity as plain strings, sequence extra-dash-space child in…
MariusStorhaug May 4, 2026
ba4fee0
Support indentless sequences, add unconsumed-lines check, add tests
MariusStorhaug May 4, 2026
73aa66f
Add \xHH hex escape parsing, fix PSAvoidLongLines, update help text a…
MariusStorhaug May 4, 2026
f3e279e
Fix null indentation in ConvertTo-YamlNode, reject tabs in indentatio…
MariusStorhaug May 4, 2026
9c0aafb
Fix quote tracking in Find-YamlMappingColon and Remove-YamlInlineComm…
MariusStorhaug May 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 59 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,40 +1,87 @@
# {{ NAME }}
# Yaml

{{ DESCRIPTION }}
A PowerShell module for working with YAML — parse YAML strings into PowerShell objects and serialize PowerShell objects back into YAML.

The module ships two cmdlets that mirror PowerShell's built-in `ConvertFrom-Json` / `ConvertTo-Json`, so the experience is familiar:

- `ConvertFrom-Yaml` — parses a YAML string into a `[PSCustomObject]` (or an ordered hashtable with `-AsHashtable`).
- `ConvertTo-Yaml` — serializes a PowerShell object, hashtable, or array into a YAML-formatted string.

No external dependencies — pure PowerShell. Aligned with the [YAML 1.2.2 core schema](https://yaml.org/spec/1.2.2/#103-core-schema).

## Prerequisites

This uses the following external resources:

- The [PSModule framework](https://github.com/PSModule) for building, testing and publishing the module.

## Installation

To install the module from the PowerShell Gallery, you can use the following command:

```powershell
Install-PSResource -Name {{ NAME }}
Import-Module -Name {{ NAME }}
Install-PSResource -Name Yaml
Import-Module -Name Yaml
```

## Usage

Here is a list of example that are typical use cases for the module.
The module provides two cmdlets that mirror PowerShell's built-in `ConvertFrom-Json` / `ConvertTo-Json`:

| Cmdlet | Alias | Purpose |
| ------------------- | ----------------- | ---------------------------------------- |
| `ConvertFrom-Yaml` | `ConvertFrom-Yml` | Parse a YAML string into an object. |
| `ConvertTo-Yaml` | `ConvertTo-Yml` | Serialize an object into a YAML string. |

> [!IMPORTANT]
> The input to `ConvertFrom-Yaml` must be a valid YAML string. The cmdlet does not read files — use `Get-Content -Raw` or similar to read the file first, then pipe the string into `ConvertFrom-Yaml`.

### YAML specification

The module aligns with [**YAML 1.2.2**](https://yaml.org/spec/1.2.2/) (October 2021) — the latest revision of the YAML specification — and follows the [**core schema**](https://yaml.org/spec/1.2.2/#103-core-schema) for scalar resolution.

Practical implications of the core schema:

### Example 1: Greet an entity
- `true` and `false` (lowercase only) parse as `[bool]`. `True`, `TRUE`, `yes`, `no`, `on`, `off`, etc. are plain strings.
- `null`, `~`, and an empty value parse as `$null`. `Null`, `NULL` are plain strings.
- Integers and floats parse to their native types using invariant culture.
- Anything else is a string. Quoted strings (`'...'`, `"..."`) always preserve the string type.

Provide examples for typical commands that a user would like to do with the module.
The supported YAML subset covers block-style mappings, block-style sequences, nested structures, single- and double-quoted scalars (with `\n`, `\t`, `\r`, `\0`, `\\`, `\"`, `\xHH` escapes in double quotes), document start (`---`) and end (`...`) markers, and full-line / inline `#` comments. Flow style (`[a, b]`, `{a: 1}`), block scalars (`|`, `>`), anchors, aliases, tags, multi-document streams, and `!!timestamp` are not yet supported.

### Example 1: Parse a YAML string

```powershell
$yaml = @'
name: Alice
age: 30
skills:
- PowerShell
- YAML
'@

$yaml | ConvertFrom-Yaml
```

### Example 2: Parse YAML as an ordered hashtable

```powershell
Greet-Entity -Name 'World'
Hello, World!
Get-Content config.yaml -Raw | ConvertFrom-Yaml -AsHashtable
```

### Example 2
### Example 3: Convert an object to YAML

```powershell
[ordered]@{
name = 'Alice'
skills = @('PowerShell', 'YAML')
} | ConvertTo-Yaml
```

Provide examples for typical commands that a user would like to do with the module.
### Example 4: Force a top-level YAML sequence

```powershell
Import-Module -Name PSModuleTemplate
Get-Process | Select-Object -First 3 Name, Id | ConvertTo-Yaml -AsArray
```

### Find more examples
Expand Down
42 changes: 30 additions & 12 deletions examples/General.ps1
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
<#
.SYNOPSIS
This is a general example of how to use the module.
.SYNOPSIS
Example usage of the Yaml module: parse YAML to objects and convert objects back to YAML.
#>

# Import the module
Import-Module -Name 'PSModule'
Import-Module -Name 'Yaml'

# Define the path to the font file
$FontFilePath = 'C:\Fonts\CodeNewRoman\CodeNewRomanNerdFontPropo-Regular.tff'
# 1. Parse a YAML string into a PSCustomObject
$yaml = @'
name: Alice
age: 30
skills:
- PowerShell
- YAML
'@

# Install the font
Install-Font -Path $FontFilePath -Verbose
$person = $yaml | ConvertFrom-Yaml
$person.name # Alice
$person.skills[0] # PowerShell

# List installed fonts
Get-Font -Name 'CodeNewRomanNerdFontPropo-Regular'
# 2. Parse YAML as an ordered hashtable
$hash = $yaml | ConvertFrom-Yaml -AsHashtable
$hash['age'] # 30

# Uninstall the font
Get-Font -Name 'CodeNewRomanNerdFontPropo-Regular' | Uninstall-Font -Verbose
# 3. Convert an object to YAML
[ordered]@{
name = 'Alice'
age = 30
skills = @('PowerShell', 'YAML')
} | ConvertTo-Yaml

# 4. Force a top-level sequence with -AsArray
Get-Process | Select-Object -First 3 Name, Id | ConvertTo-Yaml -AsArray

# 5. Round-trip
$obj = [ordered]@{ a = 1; b = @('x', 'y') }
$obj | ConvertTo-Yaml | ConvertFrom-Yaml -AsHashtable
69 changes: 69 additions & 0 deletions src/functions/private/ConvertFrom-YamlLineStream.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
function ConvertFrom-YamlLineStream {
<#
.SYNOPSIS
Splits YAML text into significant lines, dropping comments and blank lines.

.DESCRIPTION
Returns an array of `[pscustomobject]` records with `Indent`, `Content`, and `Number` properties.
- Lines that are empty or whitespace-only are skipped.
- Lines whose first non-whitespace character is `#` are skipped.
- Inline comments (` #...` outside quotes) are stripped from the content.
- Tabs in indentation are not allowed (YAML spec); a terminating error is thrown if one is found.
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseOutputTypeCorrectly', '',
Justification = 'Comma-unary operator preserves List type; PSScriptAnalyzer misdetects as Object[].')]
[CmdletBinding()]
[OutputType([System.Collections.Generic.List[pscustomobject]])]
param(
[Parameter(Mandatory)]
[AllowEmptyString()]
[string] $Text
)

$result = [System.Collections.Generic.List[pscustomobject]]::new()
$normalized = $Text -replace "`r`n", "`n"
$rawLines = $normalized -split "`n"

for ($i = 0; $i -lt $rawLines.Count; $i++) {
$raw = $rawLines[$i]

if ([string]::IsNullOrWhiteSpace($raw)) {
continue
}

# Compute indent (spaces before first non-space). Tabs are invalid per YAML spec.
$indent = 0
while ($indent -lt $raw.Length -and ($raw[$indent] -eq ' ' -or $raw[$indent] -eq "`t")) {
if ($raw[$indent] -eq "`t") {
throw "YAML forbids tab characters in indentation (line $($i + 1)). Use spaces instead."
}
$indent++
}

$content = $raw.Substring($indent)

if ($content.StartsWith('#')) {
continue
}

# Strip inline comments while respecting single/double quotes.
$stripped = Remove-YamlInlineComment -Line $content
if ([string]::IsNullOrWhiteSpace($stripped)) {
continue
}

# Skip YAML document markers: --- (start) and ... (end).
$trimmed = $stripped.Trim()
if ($trimmed -eq '---' -or $trimmed -eq '...') {
continue
}

$result.Add([pscustomobject]@{
Indent = $indent
Content = $stripped.TrimEnd()
Number = $i + 1
})
}

return , $result
}
88 changes: 88 additions & 0 deletions src/functions/private/ConvertFrom-YamlMapping.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
function ConvertFrom-YamlMapping {
<#
.SYNOPSIS
Parses a YAML block-style mapping into a PSCustomObject or OrderedDictionary.
#>
[CmdletBinding()]
[OutputType([System.Collections.Specialized.OrderedDictionary], [pscustomobject])]
param(
[Parameter(Mandatory)] [pscustomobject] $Context,
[Parameter(Mandatory)] [int] $Indent,
[Parameter(Mandatory)] [int] $Depth
)

$lines = $Context.Lines
$map = [ordered]@{}

while ($Context.Index -lt $lines.Count) {
$line = $lines[$Context.Index]
if ($line.Indent -lt $Indent) { break }
if ($line.Indent -gt $Indent) {
throw "ConvertFrom-Yaml: unexpected indentation at line $($line.Number)."
}
if ($line.Content.StartsWith('- ') -or $line.Content -eq '-') {
# A sequence at the same indent as a mapping key is a sibling, not part of mapping.
break
}

$colonIdx = Find-YamlMappingColon -Content $line.Content
if ($colonIdx -lt 0) {
throw "ConvertFrom-Yaml: expected mapping key at line $($line.Number): '$($line.Content)'."
}

$rawKey = $line.Content.Substring(0, $colonIdx).Trim()
if ($rawKey.Length -ge 2 -and $rawKey[0] -eq "'" -and $rawKey[-1] -eq "'") {
$key = ($rawKey.Substring(1, $rawKey.Length - 2)) -replace "''", "'"
} elseif ($rawKey.Length -ge 2 -and $rawKey[0] -eq '"' -and $rawKey[-1] -eq '"') {
$key = Expand-YamlDoubleQuoted -Text ($rawKey.Substring(1, $rawKey.Length - 2))
} else {
$key = $rawKey
}
$rest = $line.Content.Substring($colonIdx + 1).Trim()

$Context.Index++

if ($rest.Length -gt 0) {
if ($rest -ceq '{}') {
$map[$key] = if ($Context.AsHashtable) { [ordered]@{} } else { [pscustomobject][ordered]@{} }
} elseif ($rest -ceq '[]') {
$map[$key] = @()
} else {
$map[$key] = ConvertFrom-YamlScalar -Raw $rest
}
continue
}

# Value on subsequent indented lines (mapping or sequence) or null.
if ($Context.Index -ge $lines.Count) {
$map[$key] = $null
continue
}

$next = $lines[$Context.Index]
if ($next.Indent -lt $Indent) {
$map[$key] = $null
continue
}
if ($next.Indent -eq $Indent) {
# Indentless sequences: YAML allows a sequence to start at the same indent as the
# parent mapping key (e.g. "key:\n- a\n- b"). Parse these as the key's value.
if ($next.Content.StartsWith('- ') -or $next.Content -eq '-') {
$map[$key] = ConvertFrom-YamlSequence -Context $Context -Indent $Indent -Depth ($Depth + 1)
continue
}
$map[$key] = $null
continue
}

$childIndent = $next.Indent
$value = ConvertFrom-YamlNode -Context $Context -Indent $childIndent -Depth ($Depth + 1)
$map[$key] = $value
}

if ($Context.AsHashtable) {
return $map
}

return [pscustomobject]$map
}
40 changes: 40 additions & 0 deletions src/functions/private/ConvertFrom-YamlNode.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
function ConvertFrom-YamlNode {
<#
.SYNOPSIS
Recursive-descent parser for the YAML line stream produced by ConvertFrom-YamlLineStream.

.DESCRIPTION
Reads a node starting at the current line index and at the given indentation level.
Returns either a mapping (PSCustomObject or OrderedDictionary), a sequence (array),
or a scalar.
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[pscustomobject] $Context,

[Parameter(Mandatory)]
[int] $Indent,

[Parameter(Mandatory)]
[int] $Depth
)

if ($Depth -gt $Context.MaxDepth) {
throw "ConvertFrom-Yaml: maximum nesting depth ($($Context.MaxDepth)) exceeded."
}

$lines = $Context.Lines
if ($Context.Index -ge $lines.Count) {
return $null
}

$current = $lines[$Context.Index]

# Determine node kind from the first line at this indent.
if ($current.Content.StartsWith('- ') -or $current.Content -eq '-') {
return ConvertFrom-YamlSequence -Context $Context -Indent $Indent -Depth $Depth
}

return ConvertFrom-YamlMapping -Context $Context -Indent $Indent -Depth $Depth
}
Loading
Loading