diff --git a/.gitignore b/.gitignore
index 456ca0f..4238184 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@
# PSModule framework outputs folder
outputs/*
+output/
# .Net build output
bin/
diff --git a/README.md b/README.md
index 6319793..7104745 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,10 @@
-# {{ 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
@@ -12,23 +12,104 @@ This uses the following external resources:
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.
diff --git a/build.ps1 b/build.ps1
new file mode 100644
index 0000000..b9572f7
--- /dev/null
+++ b/build.ps1
@@ -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 ', ')"
diff --git a/examples/General.ps1 b/examples/General.ps1
index f29e88d..d9b484c 100644
--- a/examples/General.ps1
+++ b/examples/General.ps1
@@ -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]
diff --git a/src/assemblies/LsonLib.dll b/src/assemblies/LsonLib.dll
deleted file mode 100644
index 3661807..0000000
Binary files a/src/assemblies/LsonLib.dll and /dev/null differ
diff --git a/src/assemblies/Tomlyn.dll b/src/assemblies/Tomlyn.dll
new file mode 100644
index 0000000..b76eee4
Binary files /dev/null and b/src/assemblies/Tomlyn.dll differ
diff --git a/src/classes/public/TomlDocument.ps1 b/src/classes/public/TomlDocument.ps1
new file mode 100644
index 0000000..3a6c46a
--- /dev/null
+++ b/src/classes/public/TomlDocument.ps1
@@ -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)]"
+ }
+}
diff --git a/src/formats/CultureInfo.Format.ps1xml b/src/formats/CultureInfo.Format.ps1xml
deleted file mode 100644
index a715e08..0000000
--- a/src/formats/CultureInfo.Format.ps1xml
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
- System.Globalization.CultureInfo
-
- System.Globalization.CultureInfo
-
-
-
-
- 16
-
-
- 16
-
-
-
-
-
-
-
- LCID
-
-
- Name
-
-
- DisplayName
-
-
-
-
-
-
-
-
diff --git a/src/formats/Mygciview.Format.ps1xml b/src/formats/Mygciview.Format.ps1xml
deleted file mode 100644
index 4c972c2..0000000
--- a/src/formats/Mygciview.Format.ps1xml
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
-
- mygciview
-
- System.IO.DirectoryInfo
- System.IO.FileInfo
-
-
- PSParentPath
-
-
-
-
-
- 7
- Left
-
-
-
- 26
- Right
-
-
-
- 26
- Right
-
-
-
- 14
- Right
-
-
-
- Left
-
-
-
-
-
-
-
- ModeWithoutHardLink
-
-
- LastWriteTime
-
-
- CreationTime
-
-
- Length
-
-
- Name
-
-
-
-
-
-
-
-
diff --git a/src/functions/private/Toml/ConvertFrom-TomlDateTime.ps1 b/src/functions/private/Toml/ConvertFrom-TomlDateTime.ps1
new file mode 100644
index 0000000..eb9be73
--- /dev/null
+++ b/src/functions/private/Toml/ConvertFrom-TomlDateTime.ps1
@@ -0,0 +1,59 @@
+function ConvertFrom-TomlDateTime {
+ <#
+ .SYNOPSIS
+ Converts a Tomlyn TomlDateTime to the appropriate PowerShell date/time type.
+
+ .DESCRIPTION
+ Maps each TOML date/time variant to the most natural PowerShell type:
+ - OffsetDateTimeByZ / OffsetDateTimeByNumber -> [System.DateTimeOffset]
+ - LocalDateTime -> [System.DateTime] (Unspecified Kind)
+ - LocalDate -> [System.DateTime] (date only, 00:00:00)
+ - LocalTime -> [System.TimeSpan] (time of day)
+
+ .EXAMPLE
+ ConvertFrom-TomlDateTime -TomlDt $tomlDateTimeValue
+
+ Returns a [DateTimeOffset], [DateTime], or [TimeSpan] depending on kind.
+
+ .INPUTS
+ [Tomlyn.TomlDateTime]
+
+ .OUTPUTS
+ [object] — [System.DateTimeOffset], [System.DateTime], or [System.TimeSpan]
+
+ .NOTES
+ Internal helper. Not exported. No pipeline input.
+ #>
+ [OutputType([System.DateTimeOffset], [System.DateTime], [System.TimeSpan])]
+ [CmdletBinding()]
+ param(
+ # The Tomlyn TomlDateTime to convert.
+ [Parameter(Mandatory)]
+ [Tomlyn.TomlDateTime] $TomlDt
+ )
+
+ switch ($TomlDt.Kind) {
+ ([Tomlyn.TomlDateTimeKind]::OffsetDateTimeByZ) {
+ return $TomlDt.DateTime
+ }
+ ([Tomlyn.TomlDateTimeKind]::OffsetDateTimeByNumber) {
+ return $TomlDt.DateTime
+ }
+ ([Tomlyn.TomlDateTimeKind]::LocalDateTime) {
+ # Strip offset — keep year/month/day/hour/minute/second as-is
+ $utc = $TomlDt.DateTime.DateTime
+ return [System.DateTime]::SpecifyKind($utc, [System.DateTimeKind]::Unspecified)
+ }
+ ([Tomlyn.TomlDateTimeKind]::LocalDate) {
+ $utc = $TomlDt.DateTime.Date
+ return [System.DateTime]::SpecifyKind($utc, [System.DateTimeKind]::Unspecified)
+ }
+ ([Tomlyn.TomlDateTimeKind]::LocalTime) {
+ return $TomlDt.DateTime.TimeOfDay
+ }
+ default {
+ # Fallback: return DateTimeOffset
+ return $TomlDt.DateTime
+ }
+ }
+}
diff --git a/src/functions/private/Toml/ConvertFrom-TomlynTable.ps1 b/src/functions/private/Toml/ConvertFrom-TomlynTable.ps1
new file mode 100644
index 0000000..dccaa19
--- /dev/null
+++ b/src/functions/private/Toml/ConvertFrom-TomlynTable.ps1
@@ -0,0 +1,43 @@
+function ConvertFrom-TomlynTable {
+ <#
+ .SYNOPSIS
+ Converts a Tomlyn TomlTable to an [ordered] hashtable.
+
+ .DESCRIPTION
+ Iterates every key-value pair in the Tomlyn model TomlTable and
+ calls ConvertFrom-TomlynValue recursively, building an
+ [ordered] hashtable that preserves TOML key order.
+
+ .EXAMPLE
+ $table = [Tomlyn.TomlSerializer]::Deserialize[Tomlyn.Model.TomlTable]($toml, $opts)
+ ConvertFrom-TomlynTable -Table $table
+
+ Returns an [ordered] hashtable of the table's contents.
+
+ .INPUTS
+ [Tomlyn.Model.TomlTable]
+
+ .OUTPUTS
+ [System.Collections.Specialized.OrderedDictionary]
+
+ .NOTES
+ Internal helper. Not exported. No pipeline input.
+ #>
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
+ [CmdletBinding()]
+ param(
+ # The Tomlyn TomlTable to convert.
+ [Parameter(Mandatory)]
+ [Tomlyn.Model.TomlTable] $Table
+ )
+
+ $dict = [System.Collections.Specialized.OrderedDictionary]::new(
+ [System.StringComparer]::Ordinal
+ )
+
+ foreach ($kv in $Table) {
+ $dict[$kv.Key] = ConvertFrom-TomlynValue -Value $kv.Value
+ }
+
+ return $dict
+}
diff --git a/src/functions/private/Toml/ConvertFrom-TomlynValue.ps1 b/src/functions/private/Toml/ConvertFrom-TomlynValue.ps1
new file mode 100644
index 0000000..5f76b42
--- /dev/null
+++ b/src/functions/private/Toml/ConvertFrom-TomlynValue.ps1
@@ -0,0 +1,74 @@
+function ConvertFrom-TomlynValue {
+ <#
+ .SYNOPSIS
+ Converts a Tomlyn model value to a native PowerShell value.
+
+ .DESCRIPTION
+ Recursively maps the Tomlyn object model (TomlTable, TomlTableArray,
+ TomlArray, TomlDateTime, and scalars) to corresponding PowerShell types:
+ - Tomlyn.Model.TomlTable -> [System.Collections.Specialized.OrderedDictionary]
+ - Tomlyn.Model.TomlTableArray -> [object[]] of [ordered]
+ - Tomlyn.Model.TomlArray -> [object[]]
+ - Tomlyn.TomlDateTime -> [DateTimeOffset], [datetime], or [timespan]
+ - string, bool, long, double -> their PowerShell equivalents
+
+ .EXAMPLE
+ $table = [Tomlyn.TomlSerializer]::Deserialize[Tomlyn.Model.TomlTable]($toml, $opts)
+ ConvertFrom-TomlynValue -Value $table
+
+ Returns an [ordered] hashtable representing the TOML table.
+
+ .INPUTS
+ [object] — any Tomlyn model value.
+
+ .OUTPUTS
+ [object]
+
+ .NOTES
+ Internal helper. Not exported. No pipeline input.
+ #>
+ [OutputType(
+ [System.Collections.Specialized.OrderedDictionary], [object[]],
+ [string], [long], [double], [bool],
+ [System.DateTimeOffset], [System.DateTime], [System.TimeSpan]
+ )]
+ [CmdletBinding()]
+ param(
+ # The Tomlyn model value to convert.
+ [Parameter(Mandatory)]
+ [AllowNull()]
+ [object] $Value
+ )
+
+ if ($null -eq $Value) {
+ return $null
+ }
+
+ if ($Value -is [Tomlyn.Model.TomlTableArray]) {
+ # Array of tables: [[key]] sections
+ $list = [System.Collections.Generic.List[object]]::new()
+ foreach ($tableItem in $Value) {
+ $list.Add((ConvertFrom-TomlynTable -Table $tableItem))
+ }
+ return , $list.ToArray()
+ }
+
+ if ($Value -is [Tomlyn.Model.TomlTable]) {
+ return ConvertFrom-TomlynTable -Table $Value
+ }
+
+ if ($Value -is [Tomlyn.Model.TomlArray]) {
+ $list = [System.Collections.Generic.List[object]]::new()
+ foreach ($item in $Value) {
+ $list.Add((ConvertFrom-TomlynValue -Value $item))
+ }
+ return , $list.ToArray()
+ }
+
+ if ($Value -is [Tomlyn.TomlDateTime]) {
+ return ConvertFrom-TomlDateTime -TomlDt $Value
+ }
+
+ # Scalar: string, bool, long, double — already a native .NET type
+ return $Value
+}
diff --git a/src/functions/private/Toml/ConvertTo-TomlynArray.ps1 b/src/functions/private/Toml/ConvertTo-TomlynArray.ps1
new file mode 100644
index 0000000..68931f2
--- /dev/null
+++ b/src/functions/private/Toml/ConvertTo-TomlynArray.ps1
@@ -0,0 +1,41 @@
+function ConvertTo-TomlynArray {
+ <#
+ .SYNOPSIS
+ Converts a PowerShell enumerable to a Tomlyn TomlArray.
+
+ .DESCRIPTION
+ Iterates each element of the input collection and calls ConvertTo-TomlynValue
+ recursively. Strings are enumerated character-by-character by default in
+ PowerShell, so strings are handled explicitly before the IEnumerable path.
+
+ .EXAMPLE
+ ConvertTo-TomlynArray -Value @(1, 2, 3)
+
+ Returns a [Tomlyn.Model.TomlArray] with three integer elements.
+
+ .INPUTS
+ [System.Collections.IEnumerable]
+
+ .OUTPUTS
+ [Tomlyn.Model.TomlArray]
+
+ .NOTES
+ Internal helper. Not exported. No pipeline input.
+ #>
+ [OutputType([Tomlyn.Model.TomlArray])]
+ [CmdletBinding()]
+ param(
+ # The collection to convert.
+ [Parameter(Mandatory)]
+ [System.Collections.IEnumerable] $Value
+ )
+
+ $array = [Tomlyn.Model.TomlArray]::new()
+
+ foreach ($item in $Value) {
+ $array.Add((ConvertTo-TomlynValue -Value $item))
+ }
+
+ # Wrap to prevent PowerShell from enumerating TomlArray (IEnumerable)
+ return , $array
+}
diff --git a/src/functions/private/Toml/ConvertTo-TomlynTable.ps1 b/src/functions/private/Toml/ConvertTo-TomlynTable.ps1
new file mode 100644
index 0000000..c49d7ff
--- /dev/null
+++ b/src/functions/private/Toml/ConvertTo-TomlynTable.ps1
@@ -0,0 +1,55 @@
+function ConvertTo-TomlynTable {
+ <#
+ .SYNOPSIS
+ Converts a PowerShell dictionary or PSCustomObject to a Tomlyn TomlTable.
+
+ .DESCRIPTION
+ Iterates the properties/keys of the input and calls ConvertTo-TomlynValue
+ recursively for each value. Accepts [hashtable], [ordered],
+ [System.Collections.Specialized.OrderedDictionary], and [PSCustomObject].
+
+ .EXAMPLE
+ ConvertTo-TomlynTable -Value @{ host = 'localhost'; port = 5432 }
+
+ Returns a [Tomlyn.Model.TomlTable] with two entries.
+
+ .INPUTS
+ [object]
+
+ .OUTPUTS
+ [Tomlyn.Model.TomlTable]
+
+ .NOTES
+ Internal helper. Not exported. No pipeline input.
+ #>
+ [OutputType([Tomlyn.Model.TomlTable])]
+ [CmdletBinding()]
+ param(
+ # The dictionary or PSCustomObject to convert.
+ [Parameter(Mandatory)]
+ [object] $Value
+ )
+
+ $table = [Tomlyn.Model.TomlTable]::new()
+
+ if ($Value -is [System.Management.Automation.PSCustomObject]) {
+ foreach ($prop in $Value.PSObject.Properties) {
+ if ($prop.MemberType -notin 'NoteProperty', 'ScriptProperty', 'Property') {
+ continue
+ }
+ $table[$prop.Name] = ConvertTo-TomlynValue -Value $prop.Value
+ }
+ } elseif ($Value -is [System.Collections.IDictionary]) {
+ foreach ($key in $Value.Keys) {
+ $table[$key.ToString()] = ConvertTo-TomlynValue -Value $Value[$key]
+ }
+ } else {
+ throw [System.InvalidOperationException]::new(
+ "Cannot convert '$($Value.GetType().FullName)' to a TOML table. " +
+ 'Provide a [hashtable], [ordered], or [PSCustomObject].'
+ )
+ }
+
+ # Wrap in array to prevent PowerShell from enumerating TomlTable (which implements IEnumerable)
+ return , $table
+}
diff --git a/src/functions/private/Toml/ConvertTo-TomlynValue.ps1 b/src/functions/private/Toml/ConvertTo-TomlynValue.ps1
new file mode 100644
index 0000000..b89ccf2
--- /dev/null
+++ b/src/functions/private/Toml/ConvertTo-TomlynValue.ps1
@@ -0,0 +1,128 @@
+function ConvertTo-TomlynValue {
+ <#
+ .SYNOPSIS
+ Converts a PowerShell value to the appropriate Tomlyn model object.
+
+ .DESCRIPTION
+ Maps PowerShell types to Tomlyn model objects used by TomlSerializer:
+ - [System.DateTimeOffset] -> Tomlyn.TomlDateTime (OffsetDateTimeByNumber)
+ - [System.DateTime] (date-only = midnight) -> Tomlyn.TomlDateTime (LocalDate)
+ - [System.DateTime] (has time component) -> Tomlyn.TomlDateTime (LocalDateTime)
+ - [System.TimeSpan] -> Tomlyn.TomlDateTime (LocalTime)
+ - [hashtable] / [OrderedDictionary] / [PSCustomObject] / [PSObject] -> TomlTable
+ - [array] / [List] / [IEnumerable] -> TomlArray
+ - [bool] -> bool
+ - [int16/32/64] / [byte] / [sbyte] -> long
+ - [single/double] -> double
+ - [string] -> string
+ - everything else -> string via ToString()
+
+ .EXAMPLE
+ ConvertTo-TomlynValue -Value 42
+ Returns [long] 42.
+
+ .EXAMPLE
+ ConvertTo-TomlynValue -Value @{ key = 'val' }
+ Returns a [Tomlyn.Model.TomlTable].
+
+ .INPUTS
+ [object]
+
+ .OUTPUTS
+ [object]
+
+ .NOTES
+ Internal helper. Not exported. No pipeline input.
+ #>
+ [OutputType([object])]
+ [CmdletBinding()]
+ param(
+ # The PowerShell value to convert.
+ [Parameter(Mandatory)]
+ [AllowNull()]
+ [object] $Value
+ )
+
+ if ($null -eq $Value) {
+ throw [System.ArgumentNullException]::new(
+ 'Value',
+ 'TOML does not support null values. Remove the key or supply a valid value.'
+ )
+ }
+
+ # Unwrap PSObject wrapper
+ if ($Value -is [System.Management.Automation.PSObject] -and
+ $Value -isnot [System.Management.Automation.PSCustomObject]) {
+ $Value = $Value.BaseObject
+ }
+
+ if ($Value -is [System.DateTimeOffset]) {
+ return [Tomlyn.TomlDateTime]::new(
+ $Value,
+ 0,
+ [Tomlyn.TomlDateTimeKind]::OffsetDateTimeByNumber
+ )
+ }
+
+ if ($Value -is [System.DateTime]) {
+ if ($Value.TimeOfDay -eq [System.TimeSpan]::Zero) {
+ # Date-only: use the (year, month, day) constructor for LocalDate
+ return [Tomlyn.TomlDateTime]::new($Value.Year, $Value.Month, $Value.Day)
+ } else {
+ # Date + time: use (DateTime) constructor for LocalDateTime
+ return [Tomlyn.TomlDateTime]::new(
+ [System.DateTime]::SpecifyKind($Value, [System.DateTimeKind]::Unspecified)
+ )
+ }
+ }
+
+ if ($Value -is [System.TimeSpan]) {
+ # LocalTime — build a DateTimeOffset on epoch with the time component
+ $dto = [System.DateTimeOffset]::new(
+ 1970, 1, 1,
+ $Value.Hours, $Value.Minutes, $Value.Seconds,
+ [System.TimeSpan]::Zero
+ )
+ return [Tomlyn.TomlDateTime]::new($dto, 0, [Tomlyn.TomlDateTimeKind]::LocalTime)
+ }
+
+ if ($Value -is [bool]) {
+ return $Value
+ }
+
+ if ($Value -is [System.Int16] -or
+ $Value -is [System.Int32] -or
+ $Value -is [System.Int64] -or
+ $Value -is [System.Byte] -or
+ $Value -is [System.SByte] -or
+ $Value -is [System.UInt16] -or
+ $Value -is [System.UInt32] -or
+ $Value -is [System.UInt64]) {
+ return [long]$Value
+ }
+
+ if ($Value -is [System.Single] -or $Value -is [System.Double]) {
+ return [double]$Value
+ }
+
+ if ($Value -is [string]) {
+ return $Value
+ }
+
+ if ($Value -is [System.Collections.IDictionary] -or
+ $Value -is [System.Management.Automation.PSCustomObject]) {
+ $subTable = ConvertTo-TomlynTable -Value $Value
+ # Use , to prevent PowerShell from iterating the TomlTable (IEnumerable) in the pipeline
+ return , $subTable
+ }
+
+ # Array and list types
+ if ($Value -is [System.Collections.IEnumerable]) {
+ $subArray = ConvertTo-TomlynArray -Value $Value
+ # Use , to prevent PowerShell from iterating the TomlArray (IEnumerable) in the pipeline
+ return , $subArray
+ }
+
+ # Fallback: stringify
+ return $Value.ToString()
+}
diff --git a/src/functions/public/ConvertFrom-Toml.ps1 b/src/functions/public/ConvertFrom-Toml.ps1
deleted file mode 100644
index 7e67845..0000000
--- a/src/functions/public/ConvertFrom-Toml.ps1
+++ /dev/null
@@ -1,24 +0,0 @@
-function ConvertFrom-Toml {
- <#
- .SYNOPSIS
- Converts a TOML string to a PowerShell object.
-
- .DESCRIPTION
- Converts a TOML formatted string into a PowerShell hashtable or object.
-
- .EXAMPLE
- ConvertFrom-Toml -InputObject '[database]
- host = "localhost"
- port = 5432'
-
- Converts a TOML string to a PowerShell object.
- #>
- [CmdletBinding()]
- param (
- # The TOML string to convert.
- [Parameter(Mandatory)]
- [string] $InputObject
- )
- $null = $InputObject
- throw [System.NotImplementedException] 'ConvertFrom-Toml is not yet implemented.'
-}
diff --git a/src/functions/public/Toml/ConvertFrom-Toml.ps1 b/src/functions/public/Toml/ConvertFrom-Toml.ps1
new file mode 100644
index 0000000..da96dfb
--- /dev/null
+++ b/src/functions/public/Toml/ConvertFrom-Toml.ps1
@@ -0,0 +1,101 @@
+function ConvertFrom-Toml {
+ <#
+ .SYNOPSIS
+ Converts a TOML string to a TomlDocument object.
+
+ .DESCRIPTION
+ Parses a TOML-formatted string using the Tomlyn library and returns a
+ TomlDocument whose Data property contains an ordered dictionary of the
+ document's key-value pairs. TOML tables become nested ordered dictionaries,
+ arrays become object arrays, and TOML scalar types are mapped to PowerShell
+ types as follows:
+
+ | 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 |
+
+ Full TOML 1.0.0 specification is supported.
+
+ .EXAMPLE
+ $doc = ConvertFrom-Toml -InputObject '[database]
+ host = "localhost"
+ port = 5432'
+
+ $doc.Data.database.host # returns 'localhost'
+ $doc.Data.database.port # returns 5432
+
+ Parses a simple TOML document with a table and two keys.
+
+ .EXAMPLE
+ $toml = Get-Content 'config.toml' -Raw
+ $config = ConvertFrom-Toml -InputObject $toml
+ $config.Data.server.timeout
+
+ Reads a TOML file and accesses a nested value.
+
+ .INPUTS
+ [string]
+
+ .OUTPUTS
+ [TomlDocument]
+
+ .NOTES
+ Throws [Tomlyn.TomlException] or [System.InvalidOperationException] on
+ invalid TOML input. Error messages include line and column information
+ from the Tomlyn parser.
+ #>
+ [OutputType([TomlDocument])]
+ [CmdletBinding()]
+ param(
+ # The TOML-formatted string to parse. Must be valid TOML 1.0.0.
+ [Parameter(Mandatory, ValueFromPipeline)]
+ [ValidateNotNullOrEmpty()]
+ [string] $InputObject
+ )
+
+ process {
+ $opts = [Tomlyn.TomlSerializerOptions]::new()
+ $tomlTable = $null
+
+ try {
+ # SyntaxParser.ParseStrict validates the full document and throws a
+ # detailed exception on duplicate keys or table redefinition, which
+ # TomlSerializer.Deserialize does not enforce by itself.
+ $null = [Tomlyn.Parsing.SyntaxParser]::ParseStrict($InputObject, $opts, $null, $true)
+ } catch {
+ throw [System.InvalidOperationException]::new(
+ "Failed to parse TOML: $($_.Exception.Message)",
+ $_.Exception
+ )
+ }
+
+ try {
+ $tomlTable = [Tomlyn.TomlSerializer]::Deserialize[Tomlyn.Model.TomlTable](
+ $InputObject,
+ $opts
+ )
+ } catch [Tomlyn.TomlException] {
+ throw [System.InvalidOperationException]::new(
+ "Failed to parse TOML: $($_.Exception.Message)",
+ $_.Exception
+ )
+ } catch {
+ throw [System.InvalidOperationException]::new(
+ "Unexpected error parsing TOML: $($_.Exception.Message)",
+ $_.Exception
+ )
+ }
+
+ $data = ConvertFrom-TomlynTable -Table $tomlTable
+ [TomlDocument]::new($data)
+ }
+}
diff --git a/src/functions/public/Toml/ConvertTo-Toml.ps1 b/src/functions/public/Toml/ConvertTo-Toml.ps1
new file mode 100644
index 0000000..4739b4e
--- /dev/null
+++ b/src/functions/public/Toml/ConvertTo-Toml.ps1
@@ -0,0 +1,97 @@
+function ConvertTo-Toml {
+ <#
+ .SYNOPSIS
+ Converts a PowerShell object graph to a TOML string.
+
+ .DESCRIPTION
+ Serializes a [TomlDocument], [hashtable], [ordered] dictionary, or
+ [PSCustomObject] into a TOML-formatted string using the Tomlyn library.
+
+ Accepted input types and their TOML encoding:
+ - [string] -> TOML String
+ - [bool] -> TOML Boolean (true / false)
+ - [int16/32/64] / [byte] -> TOML Integer
+ - [single] / [double] -> TOML Float
+ - [System.DateTimeOffset] -> TOML Offset date-time
+ - [System.DateTime] (no time component) -> TOML Local date
+ - [System.DateTime] (has time) -> TOML Local date-time
+ - [System.TimeSpan] -> TOML Local time
+ - [hashtable] / [ordered] / [PSCustomObject] -> TOML Table
+ - [array] / [List] / IEnumerable -> TOML Array
+ - [TomlDocument] -> TOML document from .Data
+
+ Null values are not supported by TOML and cause a terminating error.
+
+ .EXAMPLE
+ ConvertTo-Toml -InputObject ([ordered]@{
+ title = 'Config'
+ server = [ordered]@{ host = 'localhost'; port = 8080 }
+ })
+
+ Produces:
+ title = "Config"
+ [server]
+ host = "localhost"
+ port = 8080
+
+ .EXAMPLE
+ $doc = ConvertFrom-Toml -InputObject $tomlString
+ ConvertTo-Toml -InputObject $doc
+
+ Round-trips a parsed TOML document back to a TOML string.
+
+ .INPUTS
+ [object]
+
+ .OUTPUTS
+ [string]
+
+ .NOTES
+ The output format follows Tomlyn's default serialization, which produces
+ valid TOML 1.0.0. Key order is preserved when the input is an [ordered]
+ dictionary or [TomlDocument].
+ #>
+ [OutputType([string])]
+ [CmdletBinding()]
+ param(
+ # The object to serialize to TOML. Accepts TomlDocument, hashtable,
+ # ordered dictionary, or PSCustomObject.
+ [Parameter(Mandatory, ValueFromPipeline)]
+ [ValidateNotNull()]
+ [object] $InputObject
+ )
+
+ process {
+ # Unwrap TomlDocument to its data dict
+ $source = if ($InputObject -is [TomlDocument]) {
+ $InputObject.Data
+ } else {
+ $InputObject
+ }
+
+ $tomlTable = $null
+ try {
+ $tomlTable = ConvertTo-TomlynTable -Value $source
+ } catch [System.ArgumentNullException] {
+ throw [System.InvalidOperationException]::new(
+ "Cannot serialize object: $($_.Exception.Message)",
+ $_.Exception
+ )
+ } catch {
+ throw [System.InvalidOperationException]::new(
+ "Failed to build TOML model: $($_.Exception.Message)",
+ $_.Exception
+ )
+ }
+
+ $opts = [Tomlyn.TomlSerializerOptions]::new()
+ try {
+ [Tomlyn.TomlSerializer]::Serialize[Tomlyn.Model.TomlTable]($tomlTable, $opts)
+ } catch {
+ throw [System.InvalidOperationException]::new(
+ "Failed to serialize TOML: $($_.Exception.Message)",
+ $_.Exception
+ )
+ }
+ }
+}
diff --git a/src/functions/public/Toml/Export-Toml.ps1 b/src/functions/public/Toml/Export-Toml.ps1
new file mode 100644
index 0000000..b4ce236
--- /dev/null
+++ b/src/functions/public/Toml/Export-Toml.ps1
@@ -0,0 +1,72 @@
+function Export-Toml {
+ <#
+ .SYNOPSIS
+ Serializes an object graph to a TOML file.
+
+ .DESCRIPTION
+ Converts the input object to a TOML string using ConvertTo-Toml and
+ writes it to the specified file path. Intermediate directories are
+ created when they do not exist.
+
+ The file is written in UTF-8 encoding without a BOM, which is the
+ recommended encoding for TOML files.
+
+ .EXAMPLE
+ $config = [ordered]@{
+ title = 'My App'
+ server = [ordered]@{ host = 'localhost'; port = 8080 }
+ }
+ Export-Toml -InputObject $config -Path 'config.toml'
+
+ Writes a TOML file with a string key and a nested table.
+
+ .EXAMPLE
+ Import-Toml 'input.toml' | Export-Toml -Path 'output.toml'
+
+ Round-trips a TOML file: parse then re-serialize.
+
+ .INPUTS
+ [object] — pipeline input supported.
+
+ .OUTPUTS
+ [void] — nothing is written to the output stream.
+
+ .NOTES
+ Throws when:
+ - the object graph contains null values (TOML has no null)
+ - the object graph contains types that cannot be serialized to TOML
+ - the file cannot be created or written
+ #>
+ [CmdletBinding(SupportsShouldProcess)]
+ param(
+ # The object to serialize. Accepts TomlDocument, hashtable,
+ # ordered dictionary, or PSCustomObject.
+ [Parameter(Mandatory, ValueFromPipeline)]
+ [ValidateNotNull()]
+ [object] $InputObject,
+
+ # Destination file path. Created (including parent directories) if absent.
+ [Parameter(Mandatory, Position = 0)]
+ [ValidateNotNullOrEmpty()]
+ [string] $Path
+ )
+
+ process {
+ $tomlString = ConvertTo-Toml -InputObject $InputObject
+
+ $resolvedPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
+ $parent = [System.IO.Path]::GetDirectoryName($resolvedPath)
+
+ if (-not [System.IO.Directory]::Exists($parent)) {
+ $null = [System.IO.Directory]::CreateDirectory($parent)
+ }
+
+ if ($PSCmdlet.ShouldProcess($resolvedPath, 'Write TOML file')) {
+ [System.IO.File]::WriteAllText(
+ $resolvedPath,
+ $tomlString,
+ [System.Text.UTF8Encoding]::new($false) # UTF-8 without BOM
+ )
+ }
+ }
+}
diff --git a/src/functions/public/Toml/Import-Toml.ps1 b/src/functions/public/Toml/Import-Toml.ps1
new file mode 100644
index 0000000..51037e0
--- /dev/null
+++ b/src/functions/public/Toml/Import-Toml.ps1
@@ -0,0 +1,55 @@
+function Import-Toml {
+ <#
+ .SYNOPSIS
+ Imports and parses a TOML file into a TomlDocument.
+
+ .DESCRIPTION
+ Reads the content of a TOML file from disk and parses it using
+ ConvertFrom-Toml. The returned TomlDocument includes the resolved
+ absolute file path in its FilePath property.
+
+ UTF-8 encoding (with or without BOM) is used when reading the file.
+
+ .EXAMPLE
+ $config = Import-Toml -Path 'config.toml'
+ $config.Data.database.host
+
+ Reads config.toml and returns a TomlDocument. The nested value is
+ accessed via the Data property.
+
+ .EXAMPLE
+ Import-Toml -Path 'settings.toml' | ForEach-Object { $_.Data }
+
+ Pipes the document and inspects the data dictionary.
+
+ .INPUTS
+ [string] — pipeline input supported for the Path parameter.
+
+ .OUTPUTS
+ [TomlDocument]
+
+ .NOTES
+ Throws when:
+ - the file does not exist
+ - the file cannot be read
+ - the file content is not valid TOML 1.0.0
+ #>
+ [OutputType([TomlDocument])]
+ [CmdletBinding()]
+ param(
+ # Path to the TOML file to import. Accepts relative and absolute paths.
+ [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
+ [ValidateNotNullOrEmpty()]
+ [string] $Path
+ )
+
+ process {
+ $resolvedPath = Resolve-Path -Path $Path -ErrorAction Stop
+
+ $content = [System.IO.File]::ReadAllText($resolvedPath.ProviderPath)
+
+ $doc = ConvertFrom-Toml -InputObject $content
+ $doc.FilePath = $resolvedPath.ProviderPath
+ $doc
+ }
+}
diff --git a/src/init/initializer.ps1 b/src/init/initializer.ps1
index 28396fb..c3cf3f5 100644
--- a/src/init/initializer.ps1
+++ b/src/init/initializer.ps1
@@ -1,3 +1,7 @@
-Write-Verbose '-------------------------------'
-Write-Verbose '--- THIS IS AN INITIALIZER ---'
-Write-Verbose '-------------------------------'
+$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"
+}
diff --git a/src/modules/OtherPSModule.psm1 b/src/modules/OtherPSModule.psm1
deleted file mode 100644
index 5d6af8e..0000000
--- a/src/modules/OtherPSModule.psm1
+++ /dev/null
@@ -1,19 +0,0 @@
-function Get-OtherPSModule {
- <#
- .SYNOPSIS
- Performs tests on a module.
-
- .DESCRIPTION
- A longer description of the function.
-
- .EXAMPLE
- Get-OtherPSModule -Name 'World'
- #>
- [CmdletBinding()]
- param(
- # Name of the person to greet.
- [Parameter(Mandatory)]
- [string] $Name
- )
- Write-Output "Hello, $Name!"
-}
diff --git a/src/scripts/loader.ps1 b/src/scripts/loader.ps1
deleted file mode 100644
index 973735a..0000000
--- a/src/scripts/loader.ps1
+++ /dev/null
@@ -1,3 +0,0 @@
-Write-Verbose '-------------------------'
-Write-Verbose '--- THIS IS A LOADER ---'
-Write-Verbose '-------------------------'
diff --git a/src/types/DirectoryInfo.Types.ps1xml b/src/types/DirectoryInfo.Types.ps1xml
deleted file mode 100644
index aef538b..0000000
--- a/src/types/DirectoryInfo.Types.ps1xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
- System.IO.FileInfo
-
-
- Status
- Success
-
-
-
-
- System.IO.DirectoryInfo
-
-
- Status
- Success
-
-
-
-
diff --git a/src/types/FileInfo.Types.ps1xml b/src/types/FileInfo.Types.ps1xml
deleted file mode 100644
index 4cfaf6b..0000000
--- a/src/types/FileInfo.Types.ps1xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
- System.IO.FileInfo
-
-
- Age
-
- ((Get-Date) - ($this.CreationTime)).Days
-
-
-
-
-
diff --git a/src/variables/private/PrivateVariables.ps1 b/src/variables/private/PrivateVariables.ps1
deleted file mode 100644
index f1fc2c3..0000000
--- a/src/variables/private/PrivateVariables.ps1
+++ /dev/null
@@ -1,47 +0,0 @@
-$script:HabitablePlanets = @(
- @{
- Name = 'Earth'
- Mass = 5.97
- Diameter = 12756
- DayLength = 24.0
- },
- @{
- Name = 'Mars'
- Mass = 0.642
- Diameter = 6792
- DayLength = 24.7
- },
- @{
- Name = 'Proxima Centauri b'
- Mass = 1.17
- Diameter = 11449
- DayLength = 5.15
- },
- @{
- Name = 'Kepler-442b'
- Mass = 2.34
- Diameter = 11349
- DayLength = 5.7
- },
- @{
- Name = 'Kepler-452b'
- Mass = 5.0
- Diameter = 17340
- DayLength = 20.0
- }
-)
-
-$script:InhabitedPlanets = @(
- @{
- Name = 'Earth'
- Mass = 5.97
- Diameter = 12756
- DayLength = 24.0
- },
- @{
- Name = 'Mars'
- Mass = 0.642
- Diameter = 6792
- DayLength = 24.7
- }
-)
diff --git a/src/variables/public/Moons.ps1 b/src/variables/public/Moons.ps1
deleted file mode 100644
index dd0f33c..0000000
--- a/src/variables/public/Moons.ps1
+++ /dev/null
@@ -1,6 +0,0 @@
-$script:Moons = @(
- @{
- Planet = 'Earth'
- Name = 'Moon'
- }
-)
diff --git a/src/variables/public/Planets.ps1 b/src/variables/public/Planets.ps1
deleted file mode 100644
index 5927bc5..0000000
--- a/src/variables/public/Planets.ps1
+++ /dev/null
@@ -1,20 +0,0 @@
-$script:Planets = @(
- @{
- Name = 'Mercury'
- Mass = 0.330
- Diameter = 4879
- DayLength = 4222.6
- },
- @{
- Name = 'Venus'
- Mass = 4.87
- Diameter = 12104
- DayLength = 2802.0
- },
- @{
- Name = 'Earth'
- Mass = 5.97
- Diameter = 12756
- DayLength = 24.0
- }
-)
diff --git a/src/variables/public/SolarSystems.ps1 b/src/variables/public/SolarSystems.ps1
deleted file mode 100644
index acbcedf..0000000
--- a/src/variables/public/SolarSystems.ps1
+++ /dev/null
@@ -1,17 +0,0 @@
-$script:SolarSystems = @(
- @{
- Name = 'Solar System'
- Planets = $script:Planets
- Moons = $script:Moons
- },
- @{
- Name = 'Alpha Centauri'
- Planets = @()
- Moons = @()
- },
- @{
- Name = 'Sirius'
- Planets = @()
- Moons = @()
- }
-)
diff --git a/tests/ConvertFrom-Toml.Tests.ps1 b/tests/ConvertFrom-Toml.Tests.ps1
new file mode 100644
index 0000000..c563543
--- /dev/null
+++ b/tests/ConvertFrom-Toml.Tests.ps1
@@ -0,0 +1,420 @@
+[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
+ 'PSReviewUnusedParameter', '',
+ Justification = 'Required for Pester tests'
+)]
+[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
+ 'PSUseDeclaredVarsMoreThanAssignments', '',
+ Justification = 'Required for Pester tests'
+)]
+[CmdletBinding()]
+param()
+
+Describe 'Toml' {
+ BeforeAll {
+ . (Join-Path $PSScriptRoot 'bootstrap.ps1')
+ }
+
+ Describe 'ConvertFrom-Toml' {
+
+ Context 'ConvertFrom-Toml - return type' {
+ It 'ConvertFrom-Toml - returns a TomlDocument for a minimal document' {
+ $result = ConvertFrom-Toml -InputObject 'key = "value"'
+ $result.GetType().Name | Should -Be 'TomlDocument'
+ }
+
+ It 'ConvertFrom-Toml - Data property is an OrderedDictionary' {
+ $result = ConvertFrom-Toml -InputObject 'key = "value"'
+ $result.Data | Should -BeOfType [System.Collections.Specialized.OrderedDictionary]
+ }
+
+ It 'ConvertFrom-Toml - FilePath is null when parsed from string' {
+ $result = ConvertFrom-Toml -InputObject 'key = "value"'
+ $result.FilePath | Should -BeNullOrEmpty
+ }
+
+ It 'ConvertFrom-Toml - accepts pipeline input' {
+ $result = 'key = "value"' | ConvertFrom-Toml
+ $result.GetType().Name | Should -Be 'TomlDocument'
+ }
+ }
+
+ Context 'ConvertFrom-Toml - strings' {
+ It 'ConvertFrom-Toml - parses basic string' {
+ $result = ConvertFrom-Toml -InputObject 'key = "hello"'
+ $result.Data['key'] | Should -Be 'hello'
+ }
+
+ It 'ConvertFrom-Toml - parses literal string' {
+ $result = ConvertFrom-Toml -InputObject "key = 'C:\path'"
+ $result.Data['key'] | Should -Be 'C:\path'
+ }
+
+ It 'ConvertFrom-Toml - parses basic string with escape sequences' {
+ $result = ConvertFrom-Toml -InputObject 'key = "tab\there"'
+ $result.Data['key'] | Should -Be "tab`there"
+ }
+
+ It 'ConvertFrom-Toml - parses basic string with quoted escape' {
+ $result = ConvertFrom-Toml -InputObject 'key = "say \"hi\""'
+ $result.Data['key'] | Should -Be 'say "hi"'
+ }
+
+ It 'ConvertFrom-Toml - parses Unicode escape sequence' {
+ $result = ConvertFrom-Toml -InputObject 'key = "\u03B1"'
+ $result.Data['key'] | Should -Be ([char]0x03B1).ToString()
+ }
+
+ It 'ConvertFrom-Toml - parses multi-line basic string' {
+ $toml = "key = `"`"`"`nline one`nline two`n`"`"`""
+ $result = ConvertFrom-Toml -InputObject $toml
+ $result.Data['key'] | Should -Match 'line one'
+ $result.Data['key'] | Should -Match 'line two'
+ }
+
+ It 'ConvertFrom-Toml - parses multi-line literal string' {
+ $toml = "key = '''" + [System.Environment]::NewLine + "raw\nno escape" + [System.Environment]::NewLine + "'''"
+ $result = ConvertFrom-Toml -InputObject $toml
+ $result.Data['key'] | Should -Match 'raw\\nno escape'
+ }
+
+ It 'ConvertFrom-Toml - parses empty string' {
+ $result = ConvertFrom-Toml -InputObject 'key = ""'
+ $result.Data['key'] | Should -Be ''
+ }
+
+ It 'ConvertFrom-Toml - parses strings from file' {
+ $path = Join-Path $PSScriptRoot 'data\strings.toml'
+ $result = ConvertFrom-Toml -InputObject (Get-Content $path -Raw)
+ $result.Data['literal'] | Should -Be 'C:\Users\nodejs\templates'
+ $result.Data['empty'] | Should -Be ''
+ }
+ }
+
+ Context 'ConvertFrom-Toml - integers' {
+ It 'ConvertFrom-Toml - parses decimal integer' {
+ $result = ConvertFrom-Toml -InputObject 'n = 42'
+ $result.Data['n'] | Should -Be 42
+ $result.Data['n'] | Should -BeOfType [long]
+ }
+
+ It 'ConvertFrom-Toml - parses negative integer' {
+ $result = ConvertFrom-Toml -InputObject 'n = -17'
+ $result.Data['n'] | Should -Be -17
+ }
+
+ It 'ConvertFrom-Toml - parses zero' {
+ $result = ConvertFrom-Toml -InputObject 'n = 0'
+ $result.Data['n'] | Should -Be 0
+ }
+
+ It 'ConvertFrom-Toml - parses integer with underscore separator' {
+ $result = ConvertFrom-Toml -InputObject 'n = 1_000_000'
+ $result.Data['n'] | Should -Be 1000000
+ }
+
+ It 'ConvertFrom-Toml - parses hexadecimal integer' {
+ $result = ConvertFrom-Toml -InputObject 'n = 0xFF'
+ $result.Data['n'] | Should -Be 255
+ }
+
+ It 'ConvertFrom-Toml - parses octal integer' {
+ $result = ConvertFrom-Toml -InputObject 'n = 0o17'
+ $result.Data['n'] | Should -Be 15
+ }
+
+ It 'ConvertFrom-Toml - parses binary integer' {
+ $result = ConvertFrom-Toml -InputObject 'n = 0b1010'
+ $result.Data['n'] | Should -Be 10
+ }
+
+ It 'ConvertFrom-Toml - parses all integer forms from file' {
+ $path = Join-Path $PSScriptRoot 'data\integers.toml'
+ $result = ConvertFrom-Toml -InputObject (Get-Content $path -Raw)
+ $result.Data['decimal'] | Should -Be 42
+ $result.Data['negative'] | Should -Be -17
+ $result.Data['hex'] | Should -Be 3735928559 # 0xDEADBEEF as unsigned long
+ $result.Data['octal'] | Should -Be 493 # 0o755
+ $result.Data['binary'] | Should -Be 214 # 0b11010110
+ }
+ }
+
+ Context 'ConvertFrom-Toml - floats' {
+ It 'ConvertFrom-Toml - parses positive float' {
+ $result = ConvertFrom-Toml -InputObject 'n = 3.14'
+ $result.Data['n'] | Should -Be 3.14
+ $result.Data['n'] | Should -BeOfType [double]
+ }
+
+ It 'ConvertFrom-Toml - parses negative float' {
+ $result = ConvertFrom-Toml -InputObject 'n = -0.01'
+ $result.Data['n'] | Should -Be -0.01
+ }
+
+ It 'ConvertFrom-Toml - parses scientific notation' {
+ $result = ConvertFrom-Toml -InputObject 'n = 5e+22'
+ $result.Data['n'] | Should -Be 5e22
+ }
+
+ It 'ConvertFrom-Toml - parses inf' {
+ $result = ConvertFrom-Toml -InputObject 'n = inf'
+ $result.Data['n'] | Should -Be ([double]::PositiveInfinity)
+ }
+
+ It 'ConvertFrom-Toml - parses -inf' {
+ $result = ConvertFrom-Toml -InputObject 'n = -inf'
+ $result.Data['n'] | Should -Be ([double]::NegativeInfinity)
+ }
+
+ It 'ConvertFrom-Toml - parses nan' {
+ $result = ConvertFrom-Toml -InputObject 'n = nan'
+ [double]::IsNaN($result.Data['n']) | Should -BeTrue
+ }
+
+ It 'ConvertFrom-Toml - parses floats from file' {
+ $path = Join-Path $PSScriptRoot 'data\floats.toml'
+ $result = ConvertFrom-Toml -InputObject (Get-Content $path -Raw)
+ ([double]$result.Data['positive'] - 3.1415) | Should -BeLessThan 0.0001
+ ([double]$result.Data['negative'] - (-0.01)) | Should -BeLessThan 0.0001
+ }
+ }
+
+ Context 'ConvertFrom-Toml - booleans' {
+ It 'ConvertFrom-Toml - parses true' {
+ $result = ConvertFrom-Toml -InputObject 'flag = true'
+ $result.Data['flag'] | Should -Be $true
+ $result.Data['flag'] | Should -BeOfType [bool]
+ }
+
+ It 'ConvertFrom-Toml - parses false' {
+ $result = ConvertFrom-Toml -InputObject 'flag = false'
+ $result.Data['flag'] | Should -Be $false
+ }
+ }
+
+ Context 'ConvertFrom-Toml - datetimes' {
+ It 'ConvertFrom-Toml - parses offset datetime with Z suffix as DateTimeOffset' {
+ $result = ConvertFrom-Toml -InputObject 'dt = 1979-05-27T07:32:00Z'
+ $result.Data['dt'] | Should -BeOfType [System.DateTimeOffset]
+ ([System.DateTimeOffset]$result.Data['dt']).Year | Should -Be 1979
+ ([System.DateTimeOffset]$result.Data['dt']).Month | Should -Be 5
+ ([System.DateTimeOffset]$result.Data['dt']).Day | Should -Be 27
+ }
+
+ It 'ConvertFrom-Toml - parses offset datetime with numeric offset as DateTimeOffset' {
+ $result = ConvertFrom-Toml -InputObject 'dt = 1979-05-27T07:32:00+05:30'
+ $result.Data['dt'] | Should -BeOfType [System.DateTimeOffset]
+ $offset = ([System.DateTimeOffset]$result.Data['dt']).Offset
+ $offset.Hours | Should -Be 5
+ $offset.Minutes | Should -Be 30
+ }
+
+ It 'ConvertFrom-Toml - parses local datetime as DateTime with Unspecified kind' {
+ $result = ConvertFrom-Toml -InputObject 'dt = 1979-05-27T07:32:00'
+ $result.Data['dt'] | Should -BeOfType [System.DateTime]
+ ([System.DateTime]$result.Data['dt']).Kind | Should -Be ([System.DateTimeKind]::Unspecified)
+ ([System.DateTime]$result.Data['dt']).Year | Should -Be 1979
+ }
+
+ It 'ConvertFrom-Toml - parses local date as DateTime with zero time' {
+ $result = ConvertFrom-Toml -InputObject 'dt = 1979-05-27'
+ $result.Data['dt'] | Should -BeOfType [System.DateTime]
+ $dt = [System.DateTime]$result.Data['dt']
+ $dt.Year | Should -Be 1979
+ $dt.Month | Should -Be 5
+ $dt.Day | Should -Be 27
+ $dt.Hour | Should -Be 0
+ $dt.Minute | Should -Be 0
+ $dt.Second | Should -Be 0
+ }
+
+ It 'ConvertFrom-Toml - parses local time as TimeSpan' {
+ $result = ConvertFrom-Toml -InputObject 'tm = 07:32:00'
+ $result.Data['tm'] | Should -BeOfType [System.TimeSpan]
+ ([System.TimeSpan]$result.Data['tm']).Hours | Should -Be 7
+ ([System.TimeSpan]$result.Data['tm']).Minutes | Should -Be 32
+ }
+
+ It 'ConvertFrom-Toml - parses all datetime types from file' {
+ $path = Join-Path $PSScriptRoot 'data\datetimes.toml'
+ $result = ConvertFrom-Toml -InputObject (Get-Content $path -Raw)
+ $result.Data['offset_dt_z'] | Should -BeOfType [System.DateTimeOffset]
+ $result.Data['offset_dt_num'] | Should -BeOfType [System.DateTimeOffset]
+ $result.Data['local_dt'] | Should -BeOfType [System.DateTime]
+ $result.Data['local_date'] | Should -BeOfType [System.DateTime]
+ $result.Data['local_time'] | Should -BeOfType [System.TimeSpan]
+ }
+ }
+
+ Context 'ConvertFrom-Toml - arrays' {
+ It 'ConvertFrom-Toml - parses integer array' {
+ $result = ConvertFrom-Toml -InputObject 'arr = [1, 2, 3]'
+ $result.Data['arr'] | Should -HaveCount 3
+ $result.Data['arr'][0] | Should -Be 1
+ $result.Data['arr'][2] | Should -Be 3
+ }
+
+ It 'ConvertFrom-Toml - parses string array' {
+ $result = ConvertFrom-Toml -InputObject 'arr = ["a", "b", "c"]'
+ $result.Data['arr'][0] | Should -Be 'a'
+ $result.Data['arr'][1] | Should -Be 'b'
+ }
+
+ It 'ConvertFrom-Toml - parses empty array' {
+ $result = ConvertFrom-Toml -InputObject 'arr = []'
+ $result.Data['arr'] | Should -HaveCount 0
+ }
+
+ It 'ConvertFrom-Toml - parses nested array' {
+ $result = ConvertFrom-Toml -InputObject 'arr = [[1, 2], [3, 4]]'
+ $result.Data['arr'] | Should -HaveCount 2
+ $result.Data['arr'][0] | Should -HaveCount 2
+ $result.Data['arr'][0][1] | Should -Be 2
+ }
+
+ It 'ConvertFrom-Toml - parses multiline array' {
+ $toml = "arr = [`n 1,`n 2,`n 3,`n]"
+ $result = ConvertFrom-Toml -InputObject $toml
+ $result.Data['arr'] | Should -HaveCount 3
+ }
+
+ It 'ConvertFrom-Toml - parses arrays from file' {
+ $path = Join-Path $PSScriptRoot 'data\arrays.toml'
+ $result = ConvertFrom-Toml -InputObject (Get-Content $path -Raw)
+ $result.Data['integers'] | Should -HaveCount 3
+ $result.Data['strings'] | Should -HaveCount 3
+ $result.Data['empty'] | Should -HaveCount 0
+ $result.Data['nested'] | Should -HaveCount 2
+ }
+ }
+
+ Context 'ConvertFrom-Toml - tables' {
+ It 'ConvertFrom-Toml - parses standard table' {
+ $toml = "[server]`nhost = `"localhost`""
+ $result = ConvertFrom-Toml -InputObject $toml
+ $result.Data['server'] | Should -BeOfType [System.Collections.Specialized.OrderedDictionary]
+ $result.Data['server']['host'] | Should -Be 'localhost'
+ }
+
+ It 'ConvertFrom-Toml - parses inline table' {
+ $result = ConvertFrom-Toml -InputObject 'point = { x = 1, y = 2 }'
+ $result.Data['point'] | Should -BeOfType [System.Collections.Specialized.OrderedDictionary]
+ $result.Data['point']['x'] | Should -Be 1
+ $result.Data['point']['y'] | Should -Be 2
+ }
+
+ It 'ConvertFrom-Toml - parses nested tables via dotted header' {
+ $toml = "[a.b.c]`nkey = `"deep`""
+ $result = ConvertFrom-Toml -InputObject $toml
+ $result.Data['a']['b']['c']['key'] | Should -Be 'deep'
+ }
+
+ It 'ConvertFrom-Toml - tables from file' {
+ $path = Join-Path $PSScriptRoot 'data\tables.toml'
+ $result = ConvertFrom-Toml -InputObject (Get-Content $path -Raw)
+ $result.Data['simple']['key'] | Should -Be 'value'
+ $result.Data['dotted']['key']['works'] | Should -Be $true
+ $result.Data['inline_parent']['inline']['one'] | Should -Be 1
+ }
+ }
+
+ Context 'ConvertFrom-Toml - array of tables' {
+ It 'ConvertFrom-Toml - parses array of tables' {
+ $path = Join-Path $PSScriptRoot 'data\array-of-tables.toml'
+ $result = ConvertFrom-Toml -InputObject (Get-Content $path -Raw)
+ $result.Data['products'] | Should -HaveCount 2
+ $result.Data['products'][0]['name'] | Should -Be 'Hammer'
+ $result.Data['products'][1]['name'] | Should -Be 'Nail'
+ }
+
+ It 'ConvertFrom-Toml - parses nested array of tables' {
+ $path = Join-Path $PSScriptRoot 'data\array-of-tables.toml'
+ $result = ConvertFrom-Toml -InputObject (Get-Content $path -Raw)
+ $result.Data['fruits'][0]['varieties'] | Should -HaveCount 2
+ $result.Data['fruits'][0]['varieties'][0]['name'] | Should -Be 'red delicious'
+ }
+ }
+
+ Context 'ConvertFrom-Toml - keys' {
+ It 'ConvertFrom-Toml - parses bare key' {
+ $result = ConvertFrom-Toml -InputObject 'bare_key = "value"'
+ $result.Data['bare_key'] | Should -Be 'value'
+ }
+
+ It 'ConvertFrom-Toml - parses quoted key' {
+ $result = ConvertFrom-Toml -InputObject '"quoted key" = "value"'
+ $result.Data['quoted key'] | Should -Be 'value'
+ }
+
+ It 'ConvertFrom-Toml - parses dotted key' {
+ $result = ConvertFrom-Toml -InputObject 'a.b = "value"'
+ $result.Data['a']['b'] | Should -Be 'value'
+ }
+
+ It 'ConvertFrom-Toml - preserves key order' {
+ $toml = "z = 1`ny = 2`nx = 3"
+ $result = ConvertFrom-Toml -InputObject $toml
+ $keys = $result.Data.Keys
+ $keys[0] | Should -Be 'z'
+ $keys[1] | Should -Be 'y'
+ $keys[2] | Should -Be 'x'
+ }
+ }
+
+ Context 'ConvertFrom-Toml - comments and whitespace' {
+ It 'ConvertFrom-Toml - ignores inline comments' {
+ $result = ConvertFrom-Toml -InputObject 'key = "value" # this is a comment'
+ $result.Data['key'] | Should -Be 'value'
+ }
+
+ It 'ConvertFrom-Toml - ignores full-line comments' {
+ $toml = "# comment`nkey = `"value`""
+ $result = ConvertFrom-Toml -InputObject $toml
+ $result.Data['key'] | Should -Be 'value'
+ }
+
+ It 'ConvertFrom-Toml - handles CRLF line endings' {
+ $toml = "a = 1`r`nb = 2"
+ $result = ConvertFrom-Toml -InputObject $toml
+ $result.Data['a'] | Should -Be 1
+ $result.Data['b'] | Should -Be 2
+ }
+ }
+
+ Context 'ConvertFrom-Toml - full example file' {
+ It 'ConvertFrom-Toml - parses full example document' {
+ $path = Join-Path $PSScriptRoot 'data\full-example.toml'
+ $result = ConvertFrom-Toml -InputObject (Get-Content $path -Raw)
+ $result.Data['title'] | Should -Be 'TOML Example'
+ $result.Data['owner']['name'] | Should -Be 'Tom Preston-Werner'
+ $result.Data['database']['enabled'] | Should -Be $true
+ $result.Data['database']['ports'] | Should -HaveCount 3
+ $result.Data['servers']['alpha']['ip'] | Should -Be '10.0.0.1'
+ $result.Data['servers']['beta']['role'] | Should -Be 'backend'
+ }
+ }
+
+ Context 'ConvertFrom-Toml - error handling' {
+ It 'ConvertFrom-Toml - throws on invalid TOML syntax' {
+ { ConvertFrom-Toml -InputObject 'invalid = = "bad"' } | Should -Throw
+ }
+
+ It 'ConvertFrom-Toml - throws on duplicate key' {
+ $toml = "key = 1`nkey = 2"
+ { ConvertFrom-Toml -InputObject $toml } | Should -Throw
+ }
+
+ It 'ConvertFrom-Toml - throws on missing value' {
+ { ConvertFrom-Toml -InputObject 'key =' } | Should -Throw
+ }
+
+ It 'ConvertFrom-Toml - throws on empty string input' {
+ { ConvertFrom-Toml -InputObject '' } | Should -Throw
+ }
+
+ It 'ConvertFrom-Toml - throws on redefining a table as a key' {
+ $toml = "[a]`nkey = 1`n[a]`nother = 2"
+ { ConvertFrom-Toml -InputObject $toml } | Should -Throw
+ }
+ }
+ }
+}
diff --git a/tests/ConvertTo-Toml.Tests.ps1 b/tests/ConvertTo-Toml.Tests.ps1
new file mode 100644
index 0000000..04d455b
--- /dev/null
+++ b/tests/ConvertTo-Toml.Tests.ps1
@@ -0,0 +1,212 @@
+[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
+ 'PSReviewUnusedParameter', '',
+ Justification = 'Required for Pester tests'
+)]
+[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
+ 'PSUseDeclaredVarsMoreThanAssignments', '',
+ Justification = 'Required for Pester tests'
+)]
+[CmdletBinding()]
+param()
+
+Describe 'Toml' {
+ BeforeAll {
+ . (Join-Path $PSScriptRoot 'bootstrap.ps1')
+ }
+
+ Describe 'ConvertTo-Toml' {
+
+ Context 'ConvertTo-Toml - return type' {
+ It 'ConvertTo-Toml - returns a string' {
+ $result = ConvertTo-Toml -InputObject ([ordered]@{ key = 'value' })
+ $result | Should -BeOfType [string]
+ }
+
+ It 'ConvertTo-Toml - result is non-empty for non-empty input' {
+ $result = ConvertTo-Toml -InputObject ([ordered]@{ key = 'value' })
+ $result | Should -Not -BeNullOrEmpty
+ }
+
+ It 'ConvertTo-Toml - accepts pipeline input' {
+ $result = [ordered]@{ key = 'value' } | ConvertTo-Toml
+ $result | Should -BeOfType [string]
+ }
+ }
+
+ Context 'ConvertTo-Toml - string serialization' {
+ It 'ConvertTo-Toml - serializes a simple string key' {
+ $result = ConvertTo-Toml -InputObject ([ordered]@{ title = 'Hello' })
+ $result | Should -Match 'title = "Hello"'
+ }
+
+ It 'ConvertTo-Toml - output is valid TOML that round-trips' {
+ $original = [ordered]@{ key = 'value' }
+ $toml = ConvertTo-Toml -InputObject $original
+ $roundTrip = ConvertFrom-Toml -InputObject $toml
+ $roundTrip.Data['key'] | Should -Be 'value'
+ }
+ }
+
+ Context 'ConvertTo-Toml - integer serialization' {
+ It 'ConvertTo-Toml - serializes integer' {
+ $result = ConvertTo-Toml -InputObject ([ordered]@{ n = [long]42 })
+ $result | Should -Match 'n = 42'
+ }
+
+ It 'ConvertTo-Toml - integer round-trips correctly' {
+ $original = [ordered]@{ n = [long]12345 }
+ $toml = ConvertTo-Toml -InputObject $original
+ $rt = ConvertFrom-Toml -InputObject $toml
+ $rt.Data['n'] | Should -Be 12345
+ }
+ }
+
+ Context 'ConvertTo-Toml - float serialization' {
+ It 'ConvertTo-Toml - serializes float' {
+ $result = ConvertTo-Toml -InputObject ([ordered]@{ pi = 3.14 })
+ $result | Should -Match 'pi = '
+ $result | Should -Match '3.14'
+ }
+
+ It 'ConvertTo-Toml - float round-trips correctly' {
+ $original = [ordered]@{ n = 2.718 }
+ $toml = ConvertTo-Toml -InputObject $original
+ $rt = ConvertFrom-Toml -InputObject $toml
+ ([double]$rt.Data['n'] - 2.718) | Should -BeLessThan 0.001
+ }
+ }
+
+ Context 'ConvertTo-Toml - boolean serialization' {
+ It 'ConvertTo-Toml - serializes true' {
+ $result = ConvertTo-Toml -InputObject ([ordered]@{ flag = $true })
+ $result | Should -Match 'flag = true'
+ }
+
+ It 'ConvertTo-Toml - serializes false' {
+ $result = ConvertTo-Toml -InputObject ([ordered]@{ flag = $false })
+ $result | Should -Match 'flag = false'
+ }
+ }
+
+ Context 'ConvertTo-Toml - datetime serialization' {
+ It 'ConvertTo-Toml - serializes DateTimeOffset as offset datetime' {
+ $dto = [System.DateTimeOffset]::new(1979, 5, 27, 7, 32, 0, [System.TimeSpan]::Zero)
+ $result = ConvertTo-Toml -InputObject ([ordered]@{ dt = $dto })
+ $result | Should -Match '1979-05-27'
+ }
+
+ It 'ConvertTo-Toml - DateTimeOffset round-trips' {
+ $dto = [System.DateTimeOffset]::new(2024, 3, 15, 12, 0, 0, [System.TimeSpan]::FromHours(2))
+ $original = [ordered]@{ dt = $dto }
+ $toml = ConvertTo-Toml -InputObject $original
+ $rt = ConvertFrom-Toml -InputObject $toml
+ $rt.Data['dt'] | Should -BeOfType [System.DateTimeOffset]
+ ([System.DateTimeOffset]$rt.Data['dt']).Year | Should -Be 2024
+ }
+
+ It 'ConvertTo-Toml - serializes DateTime with time as local datetime' {
+ $dt = [System.DateTime]::new(2024, 6, 1, 15, 30, 0, [System.DateTimeKind]::Unspecified)
+ $result = ConvertTo-Toml -InputObject ([ordered]@{ dt = $dt })
+ $result | Should -Match '2024-06-01'
+ }
+
+ It 'ConvertTo-Toml - serializes date-only DateTime as local date' {
+ $dt = [System.DateTime]::new(2024, 6, 1, 0, 0, 0, [System.DateTimeKind]::Unspecified)
+ $result = ConvertTo-Toml -InputObject ([ordered]@{ dt = $dt })
+ $result | Should -Match '2024-06-01'
+ }
+
+ It 'ConvertTo-Toml - serializes TimeSpan as local time' {
+ $ts = [System.TimeSpan]::new(0, 8, 30, 0)
+ $result = ConvertTo-Toml -InputObject ([ordered]@{ tm = $ts })
+ $result | Should -Match '08:30:00'
+ }
+ }
+
+ Context 'ConvertTo-Toml - nested table serialization' {
+ It 'ConvertTo-Toml - serializes nested ordered dict as TOML table' {
+ $obj = [ordered]@{
+ server = [ordered]@{ host = 'localhost'; port = [long]8080 }
+ }
+ $result = ConvertTo-Toml -InputObject $obj
+ $result | Should -Match '\[server\]'
+ $result | Should -Match 'host = "localhost"'
+ }
+
+ It 'ConvertTo-Toml - nested table round-trips' {
+ $original = [ordered]@{
+ database = [ordered]@{ host = 'db.example.com'; port = [long]5432 }
+ }
+ $toml = ConvertTo-Toml -InputObject $original
+ $rt = ConvertFrom-Toml -InputObject $toml
+ $rt.Data['database']['host'] | Should -Be 'db.example.com'
+ $rt.Data['database']['port'] | Should -Be 5432
+ }
+ }
+
+ Context 'ConvertTo-Toml - array serialization' {
+ It 'ConvertTo-Toml - serializes array of integers' {
+ $obj = [ordered]@{ ports = @([long]80, [long]443, [long]8080) }
+ $result = ConvertTo-Toml -InputObject $obj
+ $result | Should -Match '80'
+ $result | Should -Match '443'
+ }
+
+ It 'ConvertTo-Toml - integer array round-trips' {
+ $original = [ordered]@{ nums = @([long]1, [long]2, [long]3) }
+ $toml = ConvertTo-Toml -InputObject $original
+ $rt = ConvertFrom-Toml -InputObject $toml
+ $rt.Data['nums'] | Should -HaveCount 3
+ $rt.Data['nums'][0] | Should -Be 1
+ }
+ }
+
+ Context 'ConvertTo-Toml - PSCustomObject input' {
+ It 'ConvertTo-Toml - serializes PSCustomObject' {
+ $obj = [PSCustomObject]@{ name = 'Alice'; age = [long]30 }
+ $result = ConvertTo-Toml -InputObject $obj
+ $result | Should -Match 'name = "Alice"'
+ $result | Should -Match 'age = 30'
+ }
+ }
+
+ Context 'ConvertTo-Toml - TomlDocument input' {
+ It 'ConvertTo-Toml - accepts TomlDocument from ConvertFrom-Toml' {
+ $toml = "title = `"Test`"`n[section]`nvalue = 42"
+ $doc = ConvertFrom-Toml -InputObject $toml
+ $result = ConvertTo-Toml -InputObject $doc
+ $result | Should -Match 'title = "Test"'
+ $result | Should -Match '\[section\]'
+ }
+ }
+
+ Context 'ConvertTo-Toml - round-trip' {
+ It 'ConvertTo-Toml - full example round-trips losslessly for string/integer/boolean/float' {
+ $path = Join-Path $PSScriptRoot 'data\full-example.toml'
+ $original = ConvertFrom-Toml -InputObject (Get-Content $path -Raw)
+ $toml = ConvertTo-Toml -InputObject $original
+ $rt = ConvertFrom-Toml -InputObject $toml
+ $rt.Data['title'] | Should -Be 'TOML Example'
+ $rt.Data['owner']['name'] | Should -Be 'Tom Preston-Werner'
+ $rt.Data['database']['enabled'] | Should -Be $true
+ $rt.Data['database']['ports'] | Should -HaveCount 3
+ $rt.Data['servers']['alpha']['ip'] | Should -Be '10.0.0.1'
+ }
+
+ It 'ConvertTo-Toml - array of integers round-trips' {
+ $path = Join-Path $PSScriptRoot 'data\arrays.toml'
+ $original = ConvertFrom-Toml -InputObject (Get-Content $path -Raw)
+ $toml = ConvertTo-Toml -InputObject $original
+ $rt = ConvertFrom-Toml -InputObject $toml
+ $rt.Data['integers'] | Should -HaveCount 3
+ $rt.Data['strings'] | Should -HaveCount 3
+ }
+ }
+
+ Context 'ConvertTo-Toml - error handling' {
+ It 'ConvertTo-Toml - throws on null input' {
+ { ConvertTo-Toml -InputObject $null } | Should -Throw
+ }
+ }
+ }
+}
diff --git a/tests/Export-Toml.Tests.ps1 b/tests/Export-Toml.Tests.ps1
new file mode 100644
index 0000000..9cd06aa
--- /dev/null
+++ b/tests/Export-Toml.Tests.ps1
@@ -0,0 +1,160 @@
+[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
+ 'PSReviewUnusedParameter', '',
+ Justification = 'Required for Pester tests'
+)]
+[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
+ 'PSUseDeclaredVarsMoreThanAssignments', '',
+ Justification = 'Required for Pester tests'
+)]
+[CmdletBinding()]
+param()
+
+Describe 'Toml' {
+ BeforeAll {
+ . (Join-Path $PSScriptRoot 'bootstrap.ps1')
+ }
+
+ Describe 'Export-Toml' {
+
+ Context 'Export-Toml - basic write' {
+ It 'Export-Toml - creates a file at the given path' {
+ $path = Join-Path $TestDrive 'output.toml'
+ Export-Toml -InputObject ([ordered]@{ key = 'value' }) -Path $path
+ Test-Path -Path $path | Should -BeTrue
+ }
+
+ It 'Export-Toml - file content is valid TOML' {
+ $path = Join-Path $TestDrive 'output.toml'
+ Export-Toml -InputObject ([ordered]@{ title = 'Hello' }) -Path $path
+ $content = Get-Content -Path $path -Raw
+ $content | Should -Match 'title = "Hello"'
+ }
+
+ It 'Export-Toml - returns nothing to the output stream' {
+ $path = Join-Path $TestDrive 'void.toml'
+ $result = Export-Toml -InputObject ([ordered]@{ k = 'v' }) -Path $path
+ $result | Should -BeNullOrEmpty
+ }
+
+ It 'Export-Toml - creates parent directory when it does not exist' {
+ $path = Join-Path $TestDrive 'nested\subdir\output.toml'
+ Export-Toml -InputObject ([ordered]@{ k = 'v' }) -Path $path
+ Test-Path -Path $path | Should -BeTrue
+ }
+ }
+
+ Context 'Export-Toml - round-trip via Import-Toml' {
+ It 'Export-Toml - round-trip preserves string values' {
+ $inputDoc = [ordered]@{ greeting = 'Hello World' }
+ $path = Join-Path $TestDrive 'rt-string.toml'
+ Export-Toml -InputObject $inputDoc -Path $path
+ $result = Import-Toml -Path $path
+ $result.Data['greeting'] | Should -Be 'Hello World'
+ }
+
+ It 'Export-Toml - round-trip preserves integer values' {
+ $inputDoc = [ordered]@{ count = [long]42 }
+ $path = Join-Path $TestDrive 'rt-int.toml'
+ Export-Toml -InputObject $inputDoc -Path $path
+ $result = Import-Toml -Path $path
+ $result.Data['count'] | Should -Be 42
+ }
+
+ It 'Export-Toml - round-trip preserves boolean values' {
+ $inputDoc = [ordered]@{ enabled = $true; disabled = $false }
+ $path = Join-Path $TestDrive 'rt-bool.toml'
+ Export-Toml -InputObject $inputDoc -Path $path
+ $result = Import-Toml -Path $path
+ $result.Data['enabled'] | Should -Be $true
+ $result.Data['disabled'] | Should -Be $false
+ }
+
+ It 'Export-Toml - round-trip preserves float values' {
+ $inputDoc = [ordered]@{ pi = 3.14159 }
+ $path = Join-Path $TestDrive 'rt-float.toml'
+ Export-Toml -InputObject $inputDoc -Path $path
+ $result = Import-Toml -Path $path
+ ([double]$result.Data['pi'] - 3.14159) | Should -BeLessThan 0.00001
+ }
+
+ It 'Export-Toml - round-trip preserves nested tables' {
+ $inputDoc = [ordered]@{
+ server = [ordered]@{ host = 'localhost'; port = [long]8080 }
+ }
+ $path = Join-Path $TestDrive 'rt-nested.toml'
+ Export-Toml -InputObject $inputDoc -Path $path
+ $result = Import-Toml -Path $path
+ $result.Data['server']['host'] | Should -Be 'localhost'
+ $result.Data['server']['port'] | Should -Be 8080
+ }
+
+ It 'Export-Toml - round-trip preserves integer arrays' {
+ $inputDoc = [ordered]@{ ports = @([long]80, [long]443) }
+ $path = Join-Path $TestDrive 'rt-array.toml'
+ Export-Toml -InputObject $inputDoc -Path $path
+ $result = Import-Toml -Path $path
+ $result.Data['ports'] | Should -HaveCount 2
+ $result.Data['ports'][0] | Should -Be 80
+ }
+
+ It 'Export-Toml - full file round-trip' {
+ $srcPath = Join-Path $PSScriptRoot 'data\full-example.toml'
+ $original = Import-Toml -Path $srcPath
+
+ $outPath = Join-Path $TestDrive 'rt-full.toml'
+ Export-Toml -InputObject $original -Path $outPath
+
+ $rt = Import-Toml -Path $outPath
+ $rt.Data['title'] | Should -Be 'TOML Example'
+ $rt.Data['owner']['name'] | Should -Be 'Tom Preston-Werner'
+ $rt.Data['database']['enabled'] | Should -Be $true
+ $rt.Data['servers']['alpha']['ip'] | Should -Be '10.0.0.1'
+ }
+ }
+
+ Context 'Export-Toml - UTF-8 encoding' {
+ It 'Export-Toml - writes UTF-8 without BOM' {
+ $path = Join-Path $TestDrive 'utf8.toml'
+ Export-Toml -InputObject ([ordered]@{ key = 'αβγ' }) -Path $path
+ $bytes = [System.IO.File]::ReadAllBytes($path)
+ # UTF-8 BOM is EF BB BF — should not be present
+ if ($bytes.Length -ge 3) {
+ ($bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) | Should -BeFalse
+ }
+ }
+
+ It 'Export-Toml - round-trips Unicode string content correctly' {
+ $path = Join-Path $TestDrive 'unicode.toml'
+ Export-Toml -InputObject ([ordered]@{ msg = 'こんにちは' }) -Path $path
+ $result = Import-Toml -Path $path
+ $result.Data['msg'] | Should -Be 'こんにちは'
+ }
+ }
+
+ Context 'Export-Toml - pipeline input' {
+ It 'Export-Toml - accepts TomlDocument from pipeline' {
+ $srcPath = Join-Path $PSScriptRoot 'data\booleans.toml'
+ $outPath = Join-Path $TestDrive 'from-pipeline.toml'
+ Import-Toml -Path $srcPath | Export-Toml -Path $outPath
+ Test-Path $outPath | Should -BeTrue
+ $rt = Import-Toml -Path $outPath
+ $rt.Data['enabled'] | Should -Be $true
+ }
+ }
+
+ Context 'Export-Toml - WhatIf support' {
+ It 'Export-Toml - WhatIf does not create file' {
+ $path = Join-Path $TestDrive 'whatif.toml'
+ Export-Toml -InputObject ([ordered]@{ k = 'v' }) -Path $path -WhatIf
+ Test-Path -Path $path | Should -BeFalse
+ }
+ }
+
+ Context 'Export-Toml - error handling' {
+ It 'Export-Toml - throws on null InputObject' {
+ $path = Join-Path $TestDrive 'null.toml'
+ { Export-Toml -InputObject $null -Path $path } | Should -Throw
+ }
+ }
+ }
+}
diff --git a/tests/Import-Toml.Tests.ps1 b/tests/Import-Toml.Tests.ps1
new file mode 100644
index 0000000..8668d83
--- /dev/null
+++ b/tests/Import-Toml.Tests.ps1
@@ -0,0 +1,114 @@
+[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
+ 'PSReviewUnusedParameter', '',
+ Justification = 'Required for Pester tests'
+)]
+[Diagnostics.CodeAnalysis.SuppressMessageAttribute(
+ 'PSUseDeclaredVarsMoreThanAssignments', '',
+ Justification = 'Required for Pester tests'
+)]
+[CmdletBinding()]
+param()
+
+Describe 'Toml' {
+ Describe 'Import-Toml' {
+ BeforeAll {
+ . (Join-Path $PSScriptRoot 'bootstrap.ps1')
+ $testDataDir = Join-Path $PSScriptRoot 'data'
+ }
+
+ Context 'Import-Toml - return type' {
+ It 'Import-Toml - returns a TomlDocument' {
+ $path = Join-Path $testDataDir 'full-example.toml'
+ $result = Import-Toml -Path $path
+ $result.GetType().Name | Should -Be 'TomlDocument'
+ }
+
+ It 'Import-Toml - sets FilePath to resolved absolute path' {
+ $path = Join-Path $testDataDir 'full-example.toml'
+ $result = Import-Toml -Path $path
+ $result.FilePath | Should -Not -BeNullOrEmpty
+ [System.IO.Path]::IsPathRooted($result.FilePath) | Should -BeTrue
+ }
+
+ It 'Import-Toml - Data property is an OrderedDictionary' {
+ $path = Join-Path $testDataDir 'full-example.toml'
+ $result = Import-Toml -Path $path
+ $result.Data | Should -BeOfType [System.Collections.Specialized.OrderedDictionary]
+ }
+ }
+
+ Context 'Import-Toml - file content' {
+ It 'Import-Toml - reads all keys from full example file' {
+ $path = Join-Path $testDataDir 'full-example.toml'
+ $result = Import-Toml -Path $path
+ $result.Data['title'] | Should -Be 'TOML Example'
+ $result.Data['owner']['name'] | Should -Be 'Tom Preston-Werner'
+ $result.Data['database']['enabled'] | Should -Be $true
+ }
+
+ It 'Import-Toml - reads integer file correctly' {
+ $path = Join-Path $testDataDir 'integers.toml'
+ $result = Import-Toml -Path $path
+ $result.Data['decimal'] | Should -Be 42
+ $result.Data['negative'] | Should -Be -17
+ $result.Data['hex'] | Should -Be 3735928559 # 0xDEADBEEF as unsigned long
+ }
+
+ It 'Import-Toml - reads float file correctly' {
+ $path = Join-Path $testDataDir 'floats.toml'
+ $result = Import-Toml -Path $path
+ ([double]$result.Data['positive'] - 3.1415) | Should -BeLessThan 0.0001
+ [double]::IsNaN($result.Data['special_nan']) | Should -BeTrue
+ }
+
+ It 'Import-Toml - reads boolean file correctly' {
+ $path = Join-Path $testDataDir 'booleans.toml'
+ $result = Import-Toml -Path $path
+ $result.Data['enabled'] | Should -Be $true
+ $result.Data['disabled'] | Should -Be $false
+ }
+
+ It 'Import-Toml - reads datetime file correctly' {
+ $path = Join-Path $testDataDir 'datetimes.toml'
+ $result = Import-Toml -Path $path
+ $result.Data['offset_dt_z'] | Should -BeOfType [System.DateTimeOffset]
+ $result.Data['local_date'] | Should -BeOfType [System.DateTime]
+ $result.Data['local_time'] | Should -BeOfType [System.TimeSpan]
+ }
+
+ It 'Import-Toml - reads array file correctly' {
+ $path = Join-Path $testDataDir 'arrays.toml'
+ $result = Import-Toml -Path $path
+ $result.Data['integers'] | Should -HaveCount 3
+ $result.Data['empty'] | Should -HaveCount 0
+ }
+
+ It 'Import-Toml - reads array-of-tables file correctly' {
+ $path = Join-Path $testDataDir 'array-of-tables.toml'
+ $result = Import-Toml -Path $path
+ $result.Data['products'] | Should -HaveCount 2
+ $result.Data['products'][0]['name'] | Should -Be 'Hammer'
+ }
+ }
+
+ Context 'Import-Toml - pipeline' {
+ It 'Import-Toml - accepts path from pipeline' {
+ $path = Join-Path $testDataDir 'booleans.toml'
+ $result = $path | Import-Toml
+ $result.GetType().Name | Should -Be 'TomlDocument'
+ }
+ }
+
+ Context 'Import-Toml - error handling' {
+ It 'Import-Toml - throws when file does not exist' {
+ { Import-Toml -Path 'nonexistent-file-that-does-not-exist.toml' } | Should -Throw
+ }
+
+ It 'Import-Toml - throws when file contains invalid TOML' {
+ $badToml = Join-Path $TestDrive 'bad.toml'
+ Set-Content -Path $badToml -Value 'invalid = = "bad"'
+ { Import-Toml -Path $badToml } | Should -Throw
+ }
+ }
+ }
+}
diff --git a/tests/Toml.Tests.ps1 b/tests/Toml.Tests.ps1
index dfdbc33..c77ae33 100644
--- a/tests/Toml.Tests.ps1
+++ b/tests/Toml.Tests.ps1
@@ -10,7 +10,15 @@
param()
Describe 'Module' {
- It 'Function: ConvertFrom-Toml - Throws NotImplementedException' {
- { ConvertFrom-Toml -InputObject '[database]' } | Should -Throw
+ BeforeAll {
+ . (Join-Path $PSScriptRoot 'bootstrap.ps1')
+ }
+
+ It 'Module has expected commands' {
+ $commands = Get-Command -Module Toml | Select-Object -ExpandProperty Name | Sort-Object
+ $commands | Should -Contain 'ConvertFrom-Toml'
+ $commands | Should -Contain 'ConvertTo-Toml'
+ $commands | Should -Contain 'Import-Toml'
+ $commands | Should -Contain 'Export-Toml'
}
}
diff --git a/tests/bootstrap.ps1 b/tests/bootstrap.ps1
new file mode 100644
index 0000000..41f1bd8
--- /dev/null
+++ b/tests/bootstrap.ps1
@@ -0,0 +1,17 @@
+<#
+ .SYNOPSIS
+ Bootstrap helper for local Pester runs.
+
+ .DESCRIPTION
+ Imports the built Toml module from ./output/Toml/ before tests run.
+ Call from a BeforeAll block in every test file.
+#>
+[CmdletBinding()]
+param()
+
+$outputManifest = Join-Path $PSScriptRoot '..' 'output' 'Toml' 'Toml.psd1'
+if (-not (Test-Path $outputManifest)) {
+ throw "Module manifest not found at '$outputManifest'. Run build.ps1 first."
+}
+
+Import-Module $outputManifest -Force -ErrorAction Stop
diff --git a/tests/data/array-of-tables.toml b/tests/data/array-of-tables.toml
new file mode 100644
index 0000000..5117a59
--- /dev/null
+++ b/tests/data/array-of-tables.toml
@@ -0,0 +1,21 @@
+[[products]]
+name = "Hammer"
+sku = 738594937
+
+[[products]]
+name = "Nail"
+sku = 284758393
+color = "gray"
+
+[[fruits]]
+name = "apple"
+
+[fruits.physical]
+color = "red"
+shape = "round"
+
+[[fruits.varieties]]
+name = "red delicious"
+
+[[fruits.varieties]]
+name = "granny smith"
diff --git a/tests/data/arrays.toml b/tests/data/arrays.toml
new file mode 100644
index 0000000..1ce7fd0
--- /dev/null
+++ b/tests/data/arrays.toml
@@ -0,0 +1,11 @@
+# Array types
+integers = [1, 2, 3]
+strings = ["a", "b", "c"]
+mixed_types = [1, "two", 3.0, true]
+nested = [[1, 2], [3, 4, 5]]
+empty = []
+multiline = [
+ 1,
+ 2,
+ 3,
+]
diff --git a/tests/data/booleans.toml b/tests/data/booleans.toml
new file mode 100644
index 0000000..db0df2c
--- /dev/null
+++ b/tests/data/booleans.toml
@@ -0,0 +1,3 @@
+# Boolean values
+enabled = true
+disabled = false
diff --git a/tests/data/datetimes.toml b/tests/data/datetimes.toml
new file mode 100644
index 0000000..5a53ae5
--- /dev/null
+++ b/tests/data/datetimes.toml
@@ -0,0 +1,6 @@
+# Date and time types
+offset_dt_z = 1979-05-27T07:32:00Z
+offset_dt_num = 1979-05-27T07:32:00+05:30
+local_dt = 1979-05-27T07:32:00
+local_date = 1979-05-27
+local_time = 07:32:00
diff --git a/tests/data/floats.toml b/tests/data/floats.toml
new file mode 100644
index 0000000..3da6357
--- /dev/null
+++ b/tests/data/floats.toml
@@ -0,0 +1,9 @@
+# Float values
+positive = 3.1415
+negative = -0.01
+exponent = 5e+22
+large = 1e6
+combo = 6.626e-34
+special_inf = inf
+special_neg_inf = -inf
+special_nan = nan
diff --git a/tests/data/full-example.toml b/tests/data/full-example.toml
new file mode 100644
index 0000000..00a6050
--- /dev/null
+++ b/tests/data/full-example.toml
@@ -0,0 +1,22 @@
+# Full TOML 1.0.0 example - valid document
+title = "TOML Example"
+
+[owner]
+name = "Tom Preston-Werner"
+dob = 1979-05-27T07:32:00-08:00
+
+[database]
+enabled = true
+ports = [ 8001, 8001, 8002 ]
+data = [ ["delta", "phi"], [3.14] ]
+temp_targets = { cpu = 79.5, case = 72.0 }
+
+[servers]
+
+[servers.alpha]
+ip = "10.0.0.1"
+role = "frontend"
+
+[servers.beta]
+ip = "10.0.0.2"
+role = "backend"
diff --git a/tests/data/integers.toml b/tests/data/integers.toml
new file mode 100644
index 0000000..9d9e862
--- /dev/null
+++ b/tests/data/integers.toml
@@ -0,0 +1,9 @@
+# All integer representations
+decimal = 42
+negative = -17
+positive = +99
+zero = 0
+large = 9_007_199_254_740_992
+hex = 0xDEADBEEF
+octal = 0o755
+binary = 0b11010110
diff --git a/tests/data/strings.toml b/tests/data/strings.toml
new file mode 100644
index 0000000..ae05e4c
--- /dev/null
+++ b/tests/data/strings.toml
@@ -0,0 +1,12 @@
+# Strings - all four TOML string types
+basic = "I'm a string. \"You can quote me\". Name\tTab\nNewLine."
+literal = 'C:\Users\nodejs\templates'
+multiline_basic = """
+Roses are red
+Violets are blue"""
+multiline_literal = '''
+First paragraph.
+
+Second paragraph.'''
+escaped_unicode = "\u03B1\u03B2\u03B3"
+empty = ""
diff --git a/tests/data/tables.toml b/tests/data/tables.toml
new file mode 100644
index 0000000..c857d63
--- /dev/null
+++ b/tests/data/tables.toml
@@ -0,0 +1,9 @@
+# Table types
+[simple]
+key = "value"
+
+[dotted.key]
+works = true
+
+[inline_parent]
+inline = { one = 1, two = 2 }