diff --git a/docs/reference/api.md b/docs/reference/api.md index b40df52..a95e8e9 100644 --- a/docs/reference/api.md +++ b/docs/reference/api.md @@ -218,14 +218,110 @@ public function __construct( DocumentFormattingMode $documentFormatting = DocumentFormattingMode::Normalized, bool $skipNulls = false, TomlVersion $version = TomlVersion::V11, + bool $integerGrouping = false, + bool $trailingComma = false, + bool $dottedKeys = false, + ArrayStyle $arrayStyle = ArrayStyle::Inline, + int $arrayAutoThreshold = 3, + string $indent = ' ', ) ``` -- `$sortKeys`: Sort keys alphabetically in output -- `$newline`: Newline sequence to use during encoding -- `$documentFormatting`: `Normalized` or `SourceAware` for `encodeDocument()` -- `$skipNulls`: Omit `null` values during `encode()` instead of throwing `EncodeException` -- `$version`: default TOML output mode. Use `TomlVersion::V10` for strict TOML 1.0 encoding rules +### Options Reference + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `sortKeys` | `bool` | `false` | Sort keys alphabetically in output | +| `newline` | `string` | `"\n"` | Newline sequence to use (`"\n"` or `"\r\n"`) | +| `documentFormatting` | `DocumentFormattingMode` | `Normalized` | `Normalized` or `SourceAware` for `encodeDocument()` | +| `skipNulls` | `bool` | `false` | Omit `null` values instead of throwing `EncodeException` | +| `version` | `TomlVersion` | `V11` | TOML version for output rules | +| `integerGrouping` | `bool` | `false` | Add underscores to large integers (e.g., `1_000_000`) | +| `trailingComma` | `bool` | `false` | Add trailing commas to inline arrays | +| `dottedKeys` | `bool` | `false` | Use dotted keys instead of table sections | +| `arrayStyle` | `ArrayStyle` | `Inline` | Array formatting style (see below) | +| `arrayAutoThreshold` | `int` | `3` | Item count threshold for `ArrayStyle::Auto` | +| `indent` | `string` | `' '` | Indentation string for multiline arrays | + +### ArrayStyle + +Controls how arrays are formatted in output. + +```php +use PhpCollective\Toml\Encoder\ArrayStyle; +``` + +| Style | Description | +|-------|-------------| +| `ArrayStyle::Inline` | Arrays on a single line: `[1, 2, 3]` | +| `ArrayStyle::Multiline` | One item per line with indentation | +| `ArrayStyle::Auto` | Multiline if items exceed `arrayAutoThreshold` | + +**Examples:** + +```php +// Inline (default) +$toml = Toml::encode(['ports' => [8080, 8081, 8082]]); +// ports = [8080, 8081, 8082] + +// Multiline +$toml = Toml::encode( + ['ports' => [8080, 8081, 8082]], + new EncoderOptions(arrayStyle: ArrayStyle::Multiline), +); +// ports = [ +// 8080, +// 8081, +// 8082, +// ] + +// Auto (multiline when > 3 items) +$toml = Toml::encode( + ['ports' => [8080, 8081, 8082, 8083, 8084]], + new EncoderOptions(arrayStyle: ArrayStyle::Auto, arrayAutoThreshold: 3), +); +// ports = [ +// 8080, +// 8081, +// 8082, +// 8083, +// 8084, +// ] + +// Custom indent (2 spaces) +$toml = Toml::encode( + ['items' => [1, 2, 3]], + new EncoderOptions(arrayStyle: ArrayStyle::Multiline, indent: ' '), +); +// items = [ +// 1, +// 2, +// 3, +// ] +``` + +### Integer Grouping + +```php +$toml = Toml::encode( + ['large' => 1000000], + new EncoderOptions(integerGrouping: true), +); +// large = 1_000_000 +``` + +### Dotted Keys + +```php +$toml = Toml::encode( + ['database' => ['host' => 'localhost', 'port' => 5432]], + new EncoderOptions(dottedKeys: true), +); +// database.host = "localhost" +// database.port = 5432 +``` + +### TOML 1.0 Mode In strict TOML 1.0 mode, `encode()` normalizes local times and local datetimes to include seconds where possible. `encodeDocument()` in `DocumentFormattingMode::SourceAware` throws `EncodeException` if preserving the parsed source would keep TOML 1.1-only syntax. diff --git a/src/Encoder/ArrayStyle.php b/src/Encoder/ArrayStyle.php new file mode 100644 index 0000000..4059e00 --- /dev/null +++ b/src/Encoder/ArrayStyle.php @@ -0,0 +1,31 @@ +options->skipNulls) { $value = array_filter($value, static fn ($v) => $v !== null); } + $items = array_map(fn ($v) => $this->encodeValue($v), $value); + if ($this->shouldUseMultilineArray($value)) { + return $this->encodeMultilineArrayFromValues($items); + } + $trailing = $this->options->trailingComma && $items !== [] ? ',' : ''; return '[' . implode(', ', $items) . $trailing . ']'; } + /** + * @param array $value + */ + private function shouldUseMultilineArray(array $value): bool + { + return match ($this->options->arrayStyle) { + ArrayStyle::Inline => false, + ArrayStyle::Multiline => true, + ArrayStyle::Auto => count($value) > $this->options->arrayAutoThreshold, + }; + } + + /** + * @param array $items + */ + private function encodeMultilineArrayFromValues(array $items): string + { + if ($items === []) { + return '[]'; + } + + $newline = $this->options->newline; + $indent = $this->options->indent; + $output = '[' . $newline; + + foreach ($items as $item) { + $output .= $indent . $item . ',' . $newline; + } + + return $output . ']'; + } + /** * @param array $value */ diff --git a/src/Encoder/EncoderOptions.php b/src/Encoder/EncoderOptions.php index 17d6d37..baa5cc7 100644 --- a/src/Encoder/EncoderOptions.php +++ b/src/Encoder/EncoderOptions.php @@ -17,6 +17,9 @@ public function __construct( public bool $integerGrouping = false, public bool $trailingComma = false, public bool $dottedKeys = false, + public ArrayStyle $arrayStyle = ArrayStyle::Inline, + public int $arrayAutoThreshold = 3, + public string $indent = ' ', ) { } } diff --git a/tests/Encoder/EncoderTest.php b/tests/Encoder/EncoderTest.php index 4a7167e..02e4ed4 100644 --- a/tests/Encoder/EncoderTest.php +++ b/tests/Encoder/EncoderTest.php @@ -5,6 +5,7 @@ namespace PhpCollective\Toml\Test\Encoder; use DateTimeImmutable; +use PhpCollective\Toml\Encoder\ArrayStyle; use PhpCollective\Toml\Encoder\DocumentFormattingMode; use PhpCollective\Toml\Encoder\Encoder; use PhpCollective\Toml\Encoder\EncoderOptions; @@ -450,4 +451,111 @@ public function testEncodeDottedKeysDisabledByDefault(): void $this->assertStringContainsString('host = "localhost"', $result); $this->assertStringNotContainsString('database.host', $result); } + + public function testEncodeArrayStyleInlineByDefault(): void + { + $encoder = new Encoder(new EncoderOptions()); + + $result = $encoder->encode([ + 'items' => [1, 2, 3, 4, 5], + ]); + + $this->assertStringContainsString('items = [1, 2, 3, 4, 5]', $result); + $this->assertStringNotContainsString("\n ", $result); + } + + public function testEncodeArrayStyleMultiline(): void + { + $encoder = new Encoder(new EncoderOptions(arrayStyle: ArrayStyle::Multiline)); + + $result = $encoder->encode([ + 'items' => [1, 2, 3], + ]); + + $expected = "items = [\n 1,\n 2,\n 3,\n]"; + $this->assertStringContainsString($expected, $result); + } + + public function testEncodeArrayStyleMultilineEmpty(): void + { + $encoder = new Encoder(new EncoderOptions(arrayStyle: ArrayStyle::Multiline)); + + $result = $encoder->encode([ + 'empty' => [], + ]); + + $this->assertStringContainsString('empty = []', $result); + } + + public function testEncodeArrayStyleAutoAboveThreshold(): void + { + $encoder = new Encoder(new EncoderOptions( + arrayStyle: ArrayStyle::Auto, + arrayAutoThreshold: 3, + )); + + $result = $encoder->encode([ + 'items' => [1, 2, 3, 4], + ]); + + $expected = "items = [\n 1,\n 2,\n 3,\n 4,\n]"; + $this->assertStringContainsString($expected, $result); + } + + public function testEncodeArrayStyleAutoBelowThreshold(): void + { + $encoder = new Encoder(new EncoderOptions( + arrayStyle: ArrayStyle::Auto, + arrayAutoThreshold: 3, + )); + + $result = $encoder->encode([ + 'items' => [1, 2, 3], + ]); + + $this->assertStringContainsString('items = [1, 2, 3]', $result); + $this->assertStringNotContainsString("[\n", $result); + } + + public function testEncodeArrayStyleCustomIndent(): void + { + $encoder = new Encoder(new EncoderOptions( + arrayStyle: ArrayStyle::Multiline, + indent: ' ', + )); + + $result = $encoder->encode([ + 'items' => [1, 2], + ]); + + $expected = "items = [\n 1,\n 2,\n]"; + $this->assertStringContainsString($expected, $result); + } + + public function testEncodeArrayStyleWithTabIndent(): void + { + $encoder = new Encoder(new EncoderOptions( + arrayStyle: ArrayStyle::Multiline, + indent: "\t", + )); + + $result = $encoder->encode([ + 'items' => [1, 2], + ]); + + $expected = "items = [\n\t1,\n\t2,\n]"; + $this->assertStringContainsString($expected, $result); + } + + public function testEncodeArrayStyleMultilineWithStrings(): void + { + $encoder = new Encoder(new EncoderOptions(arrayStyle: ArrayStyle::Multiline)); + + $result = $encoder->encode([ + 'hosts' => ['alpha', 'beta', 'gamma'], + ]); + + $expected = "hosts = [\n \"alpha\",\n \"beta\",\n \"gamma\",\n]"; + $this->assertStringContainsString($expected, $result); + } }