Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c178a73
Implement Toml module with full TOML 1.0.0 support
MariusStorhaug Jul 19, 2026
1c52086
Merge origin/main into build-toml-module
MariusStorhaug Jul 23, 2026
dc1d3ba
feat!: Pure PowerShell TOML 1.0.0 module β€” full parser, serializer, a…
MariusStorhaug Jul 23, 2026
2d14f86
docs: Add .LINK to all public function comment-based help
MariusStorhaug Jul 23, 2026
5ee6f97
refactor: Move functions to flat public/private layout, clean README
MariusStorhaug Jul 23, 2026
f9d70da
refactor: Remove obsolete src scaffolding files
MariusStorhaug Jul 23, 2026
19e20a5
test: Consolidate all tests into single Toml.Tests.ps1
MariusStorhaug Jul 23, 2026
6af7f93
chore: Restore zensical.toml to .github/
MariusStorhaug Jul 23, 2026
9f42c39
chore: Remove mkdocs.yml replaced by zensical.toml
MariusStorhaug Jul 23, 2026
a6a32c7
style: Align with MSXOrg and PSModule coding standards
MariusStorhaug Jul 24, 2026
ab48673
🩹 [Patch]: Add Test-Toml to validate TOML without throwing (#22)
MariusStorhaug Jul 26, 2026
2ea9455
πŸš€ [Feature]: Merge-Toml combines TOML documents into one (#21)
MariusStorhaug Jul 26, 2026
8e594b1
πŸš€ [Minor]: Format-Toml normalizes TOML to canonical form (#20)
MariusStorhaug Jul 26, 2026
4c774e3
Fix CI failures: UTF-8 BOM on all PS1 files, class exporter block, li…
MariusStorhaug Jul 26, 2026
88777b7
Fix PSUseOutputTypeCorrectly and PSUseConsistentIndentation lint viol…
MariusStorhaug Jul 26, 2026
79bcfff
Fix PSAvoidLongLines and revert broken ToArray overload in Split-Toml…
MariusStorhaug Jul 26, 2026
fac67cc
fix: add TomlValueKind enum and resolve remaining lint violations
MariusStorhaug Jul 26, 2026
5b804ad
fix: resolve Lint-Repository and Build-Docs failures
MariusStorhaug Jul 26, 2026
b6900c0
fix: add mkdocs.yml template for Build-Site
MariusStorhaug Jul 26, 2026
c39e33f
Upgrade Process-PSModule to v6.1.14 and align site/help links
MariusStorhaug Jul 26, 2026
c41ac1b
Align build script with Process-PSModule v6 version stamping
MariusStorhaug Jul 26, 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
1 change: 1 addition & 0 deletions .github/PSModule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ Linter:
VALIDATE_JSCPD: false
VALIDATE_JSON_PRETTIER: false
VALIDATE_MARKDOWN_PRETTIER: false
VALIDATE_YAML: false
VALIDATE_YAML_PRETTIER: false
4 changes: 2 additions & 2 deletions .github/workflows/Process-PSModule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ permissions:

jobs:
Process-PSModule:
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@fb1bdb8fefd243292f779d2a856a38db6fe6daf4 # v6.1.13
uses: PSModule/Process-PSModule/.github/workflows/workflow.yml@b11b310e461f08ee99055b0827ef7909dea7110a # v6.1.14
secrets:
APIKEY: ${{ secrets.APIKEY }}
APIKey: ${{ secrets.APIKey || secrets.APIKEY }}
File renamed without changes.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@

# PSModule framework outputs folder
outputs/*
output/

# .Net build output
bin/
obj/
libs/

# Pester test output
testResults.xml
163 changes: 153 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,174 @@
# Toml

Toml is a PowerShell module for reading TOML file content from disk so scripts can load and process configuration text.
PowerShell module for reading and writing [TOML](https://toml.io) data, with full TOML 1.0.0 specification support.

## Prerequisites

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

## Installation

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

```powershell
Install-PSResource -Name Toml
Import-Module -Name Toml
```

## Capabilities
## Usage

Use this command to read TOML content from a file path:
### Parse a TOML string

```powershell
Get-Toml -Path '.\settings.toml'
$doc = ConvertFrom-Toml -InputObject @'
[database]
host = "localhost"
port = 5432
enabled = true
'@

$doc.Data.database.host # "localhost"
$doc.Data.database.port # 5432 [long]
$doc.Data.database.enabled # $true
```

## Documentation
### Import from a TOML file

```powershell
$doc = Import-Toml -Path './config.toml'
$doc.FilePath # absolute path to the file
$doc.Data # OrderedDictionary of all top-level keys
```

### Serialize an object to TOML

```powershell
$toml = ConvertTo-Toml -InputObject ([ordered]@{
title = 'My App'
version = 1
server = [ordered]@{
host = 'localhost'
port = 8080
}
})
# title = "My App"
# version = 1
#
# [server]
# host = "localhost"
# port = 8080
```

### Export to a TOML file

```powershell
$config = [ordered]@{
name = 'example'
enabled = $true
}
Export-Toml -InputObject $config -Path './output.toml'
```

### Round-trip: file β†’ modify β†’ file

```powershell
$doc = Import-Toml -Path './config.toml'
$doc.Data['version'] = 2
Export-Toml -InputObject $doc -Path './config.toml'
```

### Normalize TOML text

```powershell
Get-Content 'Cargo.toml' -Raw | Format-Toml
Format-Toml -Path 'Cargo.toml' -Indent 4
```

Documentation is published at [psmodule.io/Toml](https://psmodule.io/Toml/).
`Format-Toml` parses then re-serializes TOML text, producing consistent key
quoting and canonical scalar forms β€” equivalent to
`ConvertFrom-Toml | ConvertTo-Toml` as a single call. TOML itself has no
indentation semantics; `-Indent` (default `2`) only controls how many spaces
are used to visually nest table headers and their keys by depth. Set
`-Indent 0` for the flat, unindented form.

Use PowerShell help and command discovery for details:
### Merge two TOML documents

```powershell
Get-Command -Module Toml
Get-Help -Name Get-Toml -Examples
$defaults = @'
[server]
host = "localhost"
port = 8080
'@

$overrides = @'
[server]
port = 9090
'@

Merge-Toml -BaseObject $defaults -OverrideObject $overrides
# [server]
# host = "localhost"
# port = 9090
```

`Merge-Toml` also accepts `-Path`/`-LiteralPath` for merging files, and a `-Strategy` of `LastWins` (default), `FirstWins`, or `ErrorOnConflict` for resolving scalar key conflicts. Nested tables are always deep-merged and arrays of tables are always concatenated.

## TOML type mapping

| TOML type | PowerShell type |
|----------------------|----------------------------|
| String | `[string]` |
| Integer | `[long]` |
| Float | `[double]` |
| Boolean | `[bool]` |
| Offset date-time | `[System.DateTimeOffset]` |
| Local date-time | `[System.DateTime]` |
| Local date | `[System.DateTime]` |
| Local time | `[System.TimeSpan]` |
| Array | `[object[]]` |
| Table / Inline table | `[ordered]` hashtable |
| Array of tables | `[object[]]` of hashtables |

## Commands

| Command | Description |
|---------------------|------------------------------------------|
| `ConvertFrom-Toml` | Parse TOML text β†’ `TomlDocument` |
| `ConvertTo-Toml` | Serialize object β†’ TOML text |
| `Import-Toml` | Read TOML file β†’ `TomlDocument` |
| `Export-Toml` | Write object or `TomlDocument` to file |
| `Format-Toml` | Normalize TOML text to canonical form |
| `Test-Toml` | Validate TOML text without throwing |
| `Merge-Toml` | Merge two TOML documents into one |

## Implementation notes

- Pure PowerShell parser and serializer β€” no external TOML runtime dependency.
- Duplicate keys and table redefinition are rejected per the TOML 1.0.0 spec.
- Files are written as UTF-8 without BOM.

## More examples

See the [examples](examples) folder for runnable scripts. Use PowerShell help for per-command examples:

```powershell
Get-Help ConvertFrom-Toml -Examples
Get-Help Import-Toml -Examples
```

## Documentation

Full documentation is available at [psmodule.io/Toml](https://psmodule.io/Toml).

## Contributing

Coder or not, you can contribute to the project! We welcome all contributions.

### For Users

If you experience unexpected behavior, errors, or missing functionality, please open an issue on the [issues tab](https://github.com/PSModule/Toml/issues).

### For Developers

Please read the [Contribution guidelines](CONTRIBUTING.md) and pick up an existing issue or submit a new one.
153 changes: 153 additions & 0 deletions build.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
ο»Ώ#!/usr/bin/env pwsh
#Requires -Version 7.0
<#
.SYNOPSIS
Builds the Toml module from source into ./output/Toml/.

.DESCRIPTION
Assembles all source files (classes, init, private functions, public functions)
into a single psm1 and creates a manifest, so tests can be run locally without
the PSModule CI pipeline.
#>
[CmdletBinding()]
param(
# Version to stamp into the manifest.
[Alias('ModuleVersion')]
[string] $Version,

# Optional prerelease label to stamp into PrivateData.PSData.Prerelease.
[string] $Prerelease
)

$ErrorActionPreference = 'Stop'
$moduleName = 'Toml'
$srcPath = Join-Path $PSScriptRoot 'src'
$outputPath = Join-Path -Path $PSScriptRoot -ChildPath 'output' -AdditionalChildPath $moduleName
$resolvedVersion = if (-not [string]::IsNullOrWhiteSpace($Version)) {
$Version
} elseif (-not [string]::IsNullOrWhiteSpace($env:PSMODULE_BUILD_PSMODULE_INPUT_Version)) {
$env:PSMODULE_BUILD_PSMODULE_INPUT_Version
} else {
'0.0.1'
}
$resolvedPrerelease = if (-not [string]::IsNullOrWhiteSpace($Prerelease)) {
$Prerelease
} elseif (-not [string]::IsNullOrWhiteSpace($env:PSMODULE_BUILD_PSMODULE_INPUT_Prerelease)) {
$env:PSMODULE_BUILD_PSMODULE_INPUT_Prerelease
} else {
''
}

# ── clean / create output directory ─────────────────────────────────────────
if (Test-Path $outputPath) {
Remove-Item $outputPath -Recurse -Force
}
$null = New-Item -ItemType Directory -Path $outputPath -Force

# ── build psm1 ───────────────────────────────────────────────────────────────
$psm1Path = Join-Path $outputPath "$moduleName.psm1"
$sb = [System.Text.StringBuilder]::new()

# header
$headerPath = Join-Path $srcPath 'header.ps1'
if (Test-Path $headerPath) {
$null = $sb.AppendLine((Get-Content $headerPath -Raw))
} else {
$null = $sb.AppendLine('$ErrorActionPreference = ''Stop''')
$null = $sb.AppendLine()
}

# init
foreach ($f in (Get-ChildItem (Join-Path $srcPath 'init') -Filter '*.ps1' -ErrorAction SilentlyContinue)) {
$null = $sb.AppendLine((Get-Content $f.FullName -Raw))
}

# classes private then public
foreach ($visibility in @('private', 'public')) {
$classPath = Join-Path $srcPath "classes/$visibility"
foreach ($f in (Get-ChildItem $classPath -Filter '*.ps1' -Recurse -ErrorAction SilentlyContinue)) {
$null = $sb.AppendLine((Get-Content $f.FullName -Raw))
}
}

# private functions
foreach ($f in (Get-ChildItem (Join-Path $srcPath 'functions/private') -Filter '*.ps1' -Recurse -ErrorAction SilentlyContinue)) {
$null = $sb.AppendLine((Get-Content $f.FullName -Raw))
}

# public functions
$publicFunctions = [System.Collections.Generic.List[string]]::new()
foreach ($f in (Get-ChildItem (Join-Path $srcPath 'functions/public') -Filter '*.ps1' -Recurse -ErrorAction SilentlyContinue)) {
$null = $sb.AppendLine((Get-Content $f.FullName -Raw))
$publicFunctions.Add([System.IO.Path]::GetFileNameWithoutExtension($f.Name))
}

# finally
$finallyPath = Join-Path $srcPath 'finally.ps1'
if (Test-Path $finallyPath) {
$null = $sb.AppendLine((Get-Content $finallyPath -Raw))
}

# class exporter β€” registers public classes as type accelerators (required by PSModule framework)
$null = $sb.AppendLine(@'
#region Class exporter
$TypeAcceleratorsClass = [psobject].Assembly.GetType('System.Management.Automation.TypeAccelerators')
$ExistingTypeAccelerators = $TypeAcceleratorsClass::Get
$ExportableEnums = @(
[TomlValueKind]
)
$ExportableEnums | ForEach-Object { Write-Verbose "Exporting enum '$($_.FullName)'." }
foreach ($Type in $ExportableEnums) {
if ($Type.FullName -in $ExistingTypeAccelerators.Keys) {
Write-Verbose "Enum already exists [$($Type.FullName)]. Skipping."
} else {
Write-Verbose "Importing enum '$Type'."
$TypeAcceleratorsClass::Add($Type.FullName, $Type)
}
}
$ExportableClasses = @(
[TomlDocument]
)
$ExportableClasses | ForEach-Object { Write-Verbose "Exporting class '$($_.FullName)'." }
foreach ($Type in $ExportableClasses) {
if ($Type.FullName -in $ExistingTypeAccelerators.Keys) {
Write-Verbose "Class already exists [$($Type.FullName)]. Skipping."
} else {
Write-Verbose "Importing class '$Type'."
$TypeAcceleratorsClass::Add($Type.FullName, $Type)
}
}
$MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = {
foreach ($Type in ($ExportableEnums + $ExportableClasses)) {
$null = $TypeAcceleratorsClass::Remove($Type.FullName)
}
}.GetNewClosure()
#endregion Class exporter
'@)

# Export-ModuleMember
$null = $sb.AppendLine("Export-ModuleMember -Function @($($publicFunctions | ForEach-Object { "'$_'" } | Join-String -Separator ', '))")

[System.IO.File]::WriteAllText($psm1Path, $sb.ToString(), [System.Text.UTF8Encoding]::new($true))

# ── write manifest ────────────────────────────────────────────────────────────
$psd1Path = Join-Path $outputPath "$moduleName.psd1"
$manifest = @{
Path = $psd1Path
ModuleVersion = $resolvedVersion
RootModule = "$moduleName.psm1"
FunctionsToExport = $publicFunctions.ToArray()
PowerShellVersion = '7.6'
Description = 'PowerShell module for reading and writing TOML data.'
Author = 'PSModule'
CompanyName = 'PSModule'
GUID = '6f1b6f8d-1234-4321-abcd-ef0123456789'
}
New-ModuleManifest @manifest

if (-not [string]::IsNullOrWhiteSpace($resolvedPrerelease)) {
Write-Output "Built $moduleName $resolvedVersion-$resolvedPrerelease -> $outputPath"
} else {
Write-Output "Built $moduleName $resolvedVersion -> $outputPath"
}
Write-Output "Public functions: $($publicFunctions -join ', ')"
Loading
Loading