diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index caa3163..ace95c4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: true matrix: - php: [7.0, 7.1, 7.2, 7.3, 7.4, 8.0, 8.1, 8.2] + php: [7.0, 7.1, 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.3] name: PHP ${{ matrix.php }} diff --git a/README.md b/README.md index 8724407..36461f2 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ # VDF Converter ![Tests](https://github.com/exayer/vdf-converter/workflows/Tests/badge.svg) -[![Total Downloads](https://img.shields.io/packagist/dt/EXayer/vdf-converter)](https://packagist.org/packages/exayer/vdf-converter) ![Latest Stable Version](https://img.shields.io/packagist/v/exayer/vdf-converter) A memory efficient parser for the Valve Data Format (*.vdf) written in PHP. @@ -89,8 +88,7 @@ $result = iterator_to_array($planets); Some VDFs are known to contain duplicate keys. To keep the result structure the same as the VDF and since the parser is generator based (not keeping in memory pairs) -the duplicated key will be modified - the counter will be concatenated to the end (e.g. 'key__2'). - +the duplicated key will be modified - the counter will be concatenated to the end (e.g. `key__[2]`). ```php $vdf = '{ @@ -114,9 +112,9 @@ $result = iterator_to_array(VdfConverter::fromString($vdf)); // "mercury" => [ // "distance" => "58" // "velocity" => "35" -// "distance__2" => "92" +// "distance__[2]" => "92" // ] -// "mercury__2" => [ +// "mercury__[2]" => [ // "distance" => "108" // ] // "earth" => [ @@ -124,6 +122,35 @@ $result = iterator_to_array(VdfConverter::fromString($vdf)); // ] // ] ``` + +#### Customizing key format + +If you want to customize the formatting key process, you can create your own custom formatter. A formatter is any class that implements `EXayer\VdfConverter\UniqueKey\Formatter`. + +This is what that interface looks like. + +```php +namespace EXayer\VdfConverter\UniqueKey; + +interface Formatter +{ + public function buildKeyName(string $key, int $index): string; +} +``` + +After creating your formatter, you can specify its class name in the `uniqueKeyFormatter` method of the `EXayer\VdfConverter\VdfConverterConfig` object. The config can be passed as second argument to any `from` builder method. Your formatter will then be used by default for all duplicate key handling calls. + +You can also specify a signer for a specific webhook call: + +```php +$config = VdfConverterConfig::create() + ->uniqueKeyFormatter(YourCustomFormatter::class); + +$iterator = VdfConverter::fromString($vdf, $config); +``` + +**Warning**: Do not create formatters that create a key like `__2`, as some VDFs may have keys in this format. + ## Testing ```bash diff --git a/logo.png b/logo.png index 50b7762..666aa59 100644 Binary files a/logo.png and b/logo.png differ diff --git a/src/Parser.php b/src/Parser.php index e5089e6..0aca95c 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -3,6 +3,7 @@ namespace EXayer\VdfConverter; use EXayer\VdfConverter\Exception\CouldNotParseException; +use EXayer\VdfConverter\UniqueKey\UniqueKeyHandler; use Traversable; class Parser implements \IteratorAggregate, PositionAwareInterface, LineColumnAwareInterface @@ -13,17 +14,18 @@ class Parser implements \IteratorAggregate, PositionAwareInterface, LineColumnAw private $lexer; /** - * @var UniqueKey + * @var UniqueKeyHandler */ private $uniqueKey; /** - * @param Traversable $lexer + * @param Traversable $lexer + * @param VdfConverterConfig $config */ - public function __construct(Traversable $lexer) + public function __construct(Traversable $lexer, VdfConverterConfig $config) { $this->lexer = $lexer; - $this->uniqueKey = new UniqueKey(); + $this->uniqueKey = new UniqueKeyHandler($config->getUniqueKeyFormatter()); } /** diff --git a/src/UniqueKey/DefaultFormatter.php b/src/UniqueKey/DefaultFormatter.php new file mode 100644 index 0000000..4e23b0a --- /dev/null +++ b/src/UniqueKey/DefaultFormatter.php @@ -0,0 +1,14 @@ +formatter = $formatter; + } + /** * @param int $level Buffer nesting level. * @param string $key @@ -30,7 +38,7 @@ public function get(int $level, string $key): string return $key; } - return $this->buildName($key, ++$this->storage[$level][$key]); + return $this->formatter->buildKeyName($key, ++$this->storage[$level][$key]); } /** @@ -42,14 +50,4 @@ public function clear(int $level) $this->storage[$level] = []; } } - - /** - * @param string $key - * @param int $index - * @return string - */ - private function buildName(string $key, int $index): string - { - return $key . self::DELIMITER . $index; - } } diff --git a/src/VdfConverter.php b/src/VdfConverter.php index 48d55c6..4d320e8 100644 --- a/src/VdfConverter.php +++ b/src/VdfConverter.php @@ -19,52 +19,59 @@ class VdfConverter implements \IteratorAggregate, PositionAwareInterface private $parser; /** - * @param iterable $bytesIterator + * @param iterable $bytesIterator + * @param VdfConverterConfig|null $config */ - public function __construct($bytesIterator) + public function __construct($bytesIterator, VdfConverterConfig $config = null) { $this->bytesIterator = $bytesIterator; - $this->parser = new Parser(new Lexer($this->bytesIterator)); + + $config = $config ?: new VdfConverterConfig(); + $this->parser = new Parser(new Lexer($this->bytesIterator), $config); } /** - * @param string $string + * @param string $string + * @param VdfConverterConfig|null $config * * @return self */ - public static function fromString(string $string): self + public static function fromString(string $string, VdfConverterConfig $config = null): self { - return new static(new StringChunks($string)); + return new static(new StringChunks($string), $config); } /** - * @param string $fileName + * @param string $fileName + * @param VdfConverterConfig|null $config * * @return self */ - public static function fromFile(string $fileName): self + public static function fromFile(string $fileName, VdfConverterConfig $config = null): self { - return new static(new FileChunks($fileName)); + return new static(new FileChunks($fileName), $config); } /** - * @param resource $stream + * @param resource $stream + * @param VdfConverterConfig|null $config * * @return self */ - public static function fromStream($stream): self + public static function fromStream($stream, VdfConverterConfig $config = null): self { - return new static(new StreamChunks($stream)); + return new static(new StreamChunks($stream), $config); } /** - * @param \Traversable|array $iterable + * @param \Traversable|array $iterable + * @param VdfConverterConfig|null $config * * @return self */ - public static function fromIterable($iterable): self + public static function fromIterable($iterable, VdfConverterConfig $config = null): self { - return new static($iterable); + return new static($iterable, $config); } /** diff --git a/src/VdfConverterConfig.php b/src/VdfConverterConfig.php new file mode 100644 index 0000000..d1379fe --- /dev/null +++ b/src/VdfConverterConfig.php @@ -0,0 +1,42 @@ +uniqueKeyFormatter = $formatterClassName; + + return $this; + } + + /** + * @return Formatter + */ + public function getUniqueKeyFormatter(): Formatter + { + return new $this->uniqueKeyFormatter; + } +} diff --git a/tests/ParserTest.php b/tests/ParserTest.php index b4db7be..6d8cc52 100644 --- a/tests/ParserTest.php +++ b/tests/ParserTest.php @@ -5,6 +5,7 @@ use EXayer\VdfConverter\Exception\CouldNotParseException; use EXayer\VdfConverter\Lexer; use EXayer\VdfConverter\Parser; +use EXayer\VdfConverter\VdfConverterConfig; use PHPUnit\Framework\TestCase; class ParserTest extends TestCase @@ -16,7 +17,7 @@ class ParserTest extends TestCase */ public function testSyntax($vdf, $expectedResult) { - $parser = new Parser(new Lexer(new \ArrayIterator([$vdf]))); + $parser = new Parser(new Lexer(new \ArrayIterator([$vdf])), new VdfConverterConfig()); $result = iterator_to_array($parser); $this->assertEquals($expectedResult, $result); @@ -87,13 +88,13 @@ public function syntaxDataProvider() [ 'a' => [ 'b' => 1, - 'x' => ['x' => 1, 'x__2' => 2], + 'x' => ['x' => 1, 'x__[2]' => 2], 'e' => [ - 'f' => ['x' => 1, '' => 1, '__2' => 2, 'x__2' => 2], - 'f__2' => [], + 'f' => ['x' => 1, '' => 1, '__[2]' => 2, 'x__[2]' => 2], + 'f__[2]' => [], ], - 'x__2' => [], - 'e__2' => [], + 'x__[2]' => [], + 'e__[2]' => [], ], ], ], @@ -103,14 +104,14 @@ public function syntaxDataProvider() [ 'a' => 1, - 'b' => ['c' => 1, 'c__2' => 2], - 'a__2' => [ - 'b' => ['c' => 1, 'c__2' => 2], - 'b__2' => [], + 'b' => ['c' => 1, 'c__[2]' => 2], + 'a__[2]' => [ + 'b' => ['c' => 1, 'c__[2]' => 2], + 'b__[2]' => [], ], - 'a__3' => [], + 'a__[3]' => [], 'c' => [], - 'c__2' => [], + 'c__[2]' => [], ], ], // all duplicates @@ -118,13 +119,13 @@ public function syntaxDataProvider() '{"x" "1" "x" {"x" "1" "x" "2"} "x" {"x" {"x" "1" "x" "2"} "x" {}} "x" {} "x" {}}', [ 'x' => 1, - 'x__2' => ['x' => 1, 'x__2' => 2], - 'x__3' => [ - 'x' => ['x' => 1, 'x__2' => 2], - 'x__2' => [], + 'x__[2]' => ['x' => 1, 'x__[2]' => 2], + 'x__[3]' => [ + 'x' => ['x' => 1, 'x__[2]' => 2], + 'x__[2]' => [], ], - 'x__4' => [], - 'x__5' => [], + 'x__[4]' => [], + 'x__[5]' => [], ], ], ]; @@ -138,7 +139,7 @@ public function testSyntaxError(string $brokenVdf) { $this->expectException(CouldNotParseException::class); - iterator_to_array(new Parser(new Lexer(new \ArrayIterator([$brokenVdf])))); + iterator_to_array(new Parser(new Lexer(new \ArrayIterator([$brokenVdf])), new VdfConverterConfig())); } public function syntaxErrorDataProvider() @@ -162,7 +163,7 @@ public function testUnexpectedEndError(string $vdf) { $this->expectExceptionMessage(CouldNotParseException::unexpectedEnding()->getMessage()); - iterator_to_array(new Parser(new Lexer(new \ArrayIterator([$vdf])))); + iterator_to_array(new Parser(new Lexer(new \ArrayIterator([$vdf])), new VdfConverterConfig())); } public function unexpectedEndExceptionDataProvider() diff --git a/tests/UniqueKey/DefaultFormatterTest.php b/tests/UniqueKey/DefaultFormatterTest.php new file mode 100644 index 0000000..98285a7 --- /dev/null +++ b/tests/UniqueKey/DefaultFormatterTest.php @@ -0,0 +1,17 @@ +buildKeyName('sample_key', 9); + + $this->assertEquals($formattedKey, 'sample_key__[9]'); + } +} \ No newline at end of file diff --git a/tests/UniqueKeyTest.php b/tests/UniqueKey/UniqueKeyTest.php similarity index 81% rename from tests/UniqueKeyTest.php rename to tests/UniqueKey/UniqueKeyTest.php index 3824bf3..188baa7 100644 --- a/tests/UniqueKeyTest.php +++ b/tests/UniqueKey/UniqueKeyTest.php @@ -1,8 +1,9 @@ $key) { @@ -44,7 +45,7 @@ public function storageDataProvider() [ [[1 => 'key'], [1 => 'key']], [], - [1, 'key', 'key__3'] + [1, 'key', 'key__[3]'] ], [ [[1 => 'key'], [1 => 'key'], [2 => 'key'], [3 => 'key']], @@ -54,7 +55,7 @@ public function storageDataProvider() [ [[1 => 'key'], [2 => 'key'], [3 => 'key']], [2, 3], - [1, 'key', 'key__2'] + [1, 'key', 'key__[2]'] ], ]; } diff --git a/tests/VdfConverterConfigTest.php b/tests/VdfConverterConfigTest.php new file mode 100644 index 0000000..78fb014 --- /dev/null +++ b/tests/VdfConverterConfigTest.php @@ -0,0 +1,16 @@ +uniqueKeyFormatter(DefaultFormatter::class); + + $this->assertEquals(new DefaultFormatter(), $config->getUniqueKeyFormatter()); + } +} \ No newline at end of file