Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

# PSModule framework outputs folder
outputs/*
output/

# .Net build output
bin/
Expand Down
101 changes: 91 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,115 @@
# {{ NAME }}
# Toml

{{ DESCRIPTION }}
PowerShell module for reading and writing [TOML](https://toml.io) data, with full TOML 1.0.0 specification support.

## Prerequisites

This uses the following external resources:
- PowerShell 7.2 or later
- The [PSModule framework](https://github.com/PSModule/Process-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 Toml
Import-Module -Name Toml
```

## Usage

Here is a list of example that are typical use cases for the module.
### Parse a TOML string

### Example 1: Greet an entity
```powershell
$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
```

Provide examples for typical commands that a user would like to do with the module.
### Import from a TOML file

```powershell
Greet-Entity -Name 'World'
Hello, World!
$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'
```

## 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 |

## Implementation notes

- Backed by [Tomlyn v2.0.0](https://github.com/xoofx/Tomlyn), a conformant .NET TOML 1.0.0 library.
- Duplicate keys and table redefinition are rejected per the TOML 1.0.0 spec.
- Files are written as UTF-8 without BOM.


### Example 2

Provide examples for typical commands that a user would like to do with the module.
Expand Down
106 changes: 106 additions & 0 deletions build.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#!/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.
[string] $ModuleVersion = '0.0.1'
)

$ErrorActionPreference = 'Stop'
$moduleName = 'Toml'
$srcPath = Join-Path $PSScriptRoot 'src'
$outputPath = Join-Path $PSScriptRoot 'output' $moduleName

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

# ── copy assemblies ──────────────────────────────────────────────────────────
$assembliesSrc = Join-Path $srcPath 'assemblies'
$assembliesDst = Join-Path $outputPath 'assemblies'
$null = New-Item -ItemType Directory -Path $assembliesDst -Force
Copy-Item -Path (Join-Path $assembliesSrc '*.dll') -Destination $assembliesDst -ErrorAction SilentlyContinue

# ── 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))
}

# init — emit a corrected version that uses the assembled module's own PSScriptRoot
# The source init uses '../assemblies/Tomlyn.dll' relative to src/init/.
# In the built module, assemblies live next to the psm1, so we rewrite the path.
$initContent = @'
$assemblyPath = Join-Path -Path $PSScriptRoot -ChildPath 'assemblies\Tomlyn.dll'
$resolvedPath = [System.IO.Path]::GetFullPath($assemblyPath)

if (-not [System.AppDomain]::CurrentDomain.GetAssemblies().Where({ $_.GetName().Name -eq 'Tomlyn' })) {
[System.Reflection.Assembly]::LoadFrom($resolvedPath) | Out-Null
Write-Verbose "Loaded Tomlyn assembly from: $resolvedPath"
}
'@
$null = $sb.AppendLine($initContent)

# 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))
}

# 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($false))

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

Write-Host "Built $moduleName $ModuleVersion -> $outputPath"
Write-Host "Public functions: $($publicFunctions -join ', ')"
69 changes: 65 additions & 4 deletions examples/General.ps1
Original file line number Diff line number Diff line change
@@ -1,15 +1,76 @@
<#
.SYNOPSIS
This is a general example of how to use the Toml module.
General usage examples for the Toml module.
#>

# Import the module
Import-Module -Name 'Toml'

# Convert a TOML string to a PowerShell object
$toml = @'
# ── Parse TOML string ──────────────────────────────────────────────────────
$doc = ConvertFrom-Toml -InputObject @'
[database]
host = "localhost"
port = 5432
enabled = true
tags = ["production", "primary"]

[database.credentials]
user = "admin"
'@

$doc.Data.database.host # "localhost"
$doc.Data.database.port # 5432
$doc.Data.database.enabled # True
$doc.Data.database.tags # @("production", "primary")
$doc.Data.database.credentials.user # "admin"

# ── Serialize to TOML ──────────────────────────────────────────────────────
$toml = ConvertTo-Toml -InputObject ([ordered]@{
title = 'My Application'
version = 2
debug = $false
server = [ordered]@{
host = '0.0.0.0'
port = 8080
}
features = @('auth', 'logging', 'metrics')
})
Write-Host $toml

# ── Import from file ───────────────────────────────────────────────────────
# $doc = Import-Toml -Path './config.toml'
# $doc.FilePath # absolute path to the source file
# $doc.Data # [ordered] hashtable of all top-level keys

# ── Export to file ─────────────────────────────────────────────────────────
# Export-Toml -InputObject ([ordered]@{ key = 'value' }) -Path './config.toml'

# ── Round-trip: file → edit → file ────────────────────────────────────────
# $doc = Import-Toml -Path './config.toml'
# $doc.Data['version'] = 3
# Export-Toml -InputObject $doc -Path './config.toml'

# ── Pipeline usage ─────────────────────────────────────────────────────────
# '[server]
# host = "localhost"' | ConvertFrom-Toml | ConvertTo-Toml

# ── All TOML scalar types ──────────────────────────────────────────────────
$types = ConvertFrom-Toml -InputObject @'
a_string = "hello"
an_integer = 42
a_float = 3.14
a_bool = true
an_offset_dt = 1979-05-27T07:32:00Z
a_local_dt = 1979-05-27T07:32:00
a_local_date = 1979-05-27
a_local_time = 07:32:00
'@
ConvertFrom-Toml -InputObject $toml

$types.Data.a_string # [string]
$types.Data.an_integer # [long]
$types.Data.a_float # [double]
$types.Data.a_bool # [bool]
$types.Data.an_offset_dt # [System.DateTimeOffset]
$types.Data.a_local_dt # [System.DateTime] (Kind=Unspecified)
$types.Data.a_local_date # [System.DateTime] (00:00:00)
$types.Data.a_local_time # [System.TimeSpan]
Binary file removed src/assemblies/LsonLib.dll
Binary file not shown.
Binary file added src/assemblies/Tomlyn.dll
Binary file not shown.
35 changes: 35 additions & 0 deletions src/classes/public/TomlDocument.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Represents a parsed TOML document.
# Exposes the root key-value data as an ordered dictionary and records the
# file path when the document was loaded from disk.
class TomlDocument {
# The root key-value pairs of the TOML document, preserving insertion order.
[System.Collections.Specialized.OrderedDictionary] $Data

# The absolute path to the source file, if loaded with Import-Toml.
# $null when the document was created from a string.
[string] $FilePath

TomlDocument() {
$this.Data = [System.Collections.Specialized.OrderedDictionary]::new(
[System.StringComparer]::Ordinal
)
}

TomlDocument([System.Collections.Specialized.OrderedDictionary] $data) {
$this.Data = $data
}

# Returns true when the document contains the given top-level key.
[bool] ContainsKey([string] $key) {
return $this.Data.Contains($key)
}

# Returns the number of top-level keys.
[int] GetCount() {
return $this.Data.Count
}

[string] ToString() {
return "TomlDocument[$($this.Data.Count) key(s)]"
}
}
37 changes: 0 additions & 37 deletions src/formats/CultureInfo.Format.ps1xml

This file was deleted.

Loading
Loading