diff --git a/.gitattributes b/.gitattributes index 9da29e7..d629b64 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,4 @@ -.* export-ignore -README.md export-ignore +/test/ export-ignore +/phpunit.xml export-ignore +/README.md export-ignore +/.* export-ignore diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 29c671d..b643716 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -32,6 +32,7 @@ jobs: with: exclude: | .php-cs-fixer.php + test php-cs: name: PHP Coding Style @@ -50,3 +51,55 @@ jobs: - name: Run PHP-CS-Fixer run: php-cs-fixer check --ansi --no-interaction --using-cache=no --diff --show-progress=none + + + phpunit: + name: PHPUnit + strategy: + matrix: + os: + - ubuntu-latest + php-version: + - "5.3" + - "5.4" + - "5.5" + - "5.6" + - "7.0" + - "7.1" + - "7.2" + - "7.3" + - "7.4" + - "8.0" + - "8.1" + - "8.2" + - "8.3" + include: + - + os: windows-latest + php-version: "5.6" + - + os: windows-latest + php-version: "7.4" + - + os: windows-latest + php-version: "8.3" + runs-on: ${{ matrix.os }} + steps: + - + name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + tools: composer + coverage: none + - + name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + - + name: Install Composer dependencies + run: composer --ansi --no-interaction --no-progress update + - + name: Run PHPUnit + run: composer --ansi --no-interaction run-script test -- -v diff --git a/.gitignore b/.gitignore index 35389b3..696aa73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -/.settings/ /vendor/ -/.buildpath -/.project +/.phpunit.result.cache /composer.lock diff --git a/README.md b/README.md index 2758f2a..44f2890 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# concrete5 Translation Library +# concrete5 Translation Library Useful tools to extract translatable strings from concrete5 projects. @@ -8,4 +8,3 @@ It's used by - since version 5.7.5.4 to translate packages - [concrete5-translation-tools](https://github.com/mlocati/concrete5-translation-tools) - to translate the core (core is fetched from GitHub both for [concrete5](https://github.com/concrete5/concrete5) and [concrete5-legacy](https://github.com/concrete5/concrete5-legacy), parsed with this library and translations are fetched by [Transifex](https://www.transifex.com/concrete5/concrete5)) - \ No newline at end of file diff --git a/composer.json b/composer.json index 5793e15..23ce5a1 100644 --- a/composer.json +++ b/composer.json @@ -36,8 +36,22 @@ "C5TL\\": "src/" } }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^6.5.14 || ^8.5.39" + }, + "autoload-dev": { + "psr-4": { + "C5TL\\Test\\": [ + "test/helpers/", + "test/tests/" + ] + } + }, "config": { "preferred-install": "dist", "optimize-autoloader": true + }, + "scripts": { + "test": "phpunit" } } diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..332e5a4 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,20 @@ + + + + ./test/tests + + + + + ./src + + + diff --git a/src/Parser.php b/src/Parser.php index 29dd9ab..90dba9b 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -14,6 +14,13 @@ abstract class Parser */ private static $cache = array(); + /** + * The parser factory. + * + * @var \C5TL\ParserFactory|null + */ + private static $factory; + /** * Returns the parser name. * @@ -248,37 +255,42 @@ private static function getDirectoryStructureDo($relativePath, $rootDirectory, $ return $result; } + final public static function getParserFactory() + { + if (self::$factory === null) { + self::$factory = new ParserFactory(); + } + + return self::$factory; + } + + final public static function setParserFactory(ParserFactory $value) + { + self::$factory = $value; + } + /** - * Retrieves all the available parsers. + * @deprecated Use ParserFactory * - * @return array[\C5TL\Parser] + * @return \C5TL\Parser[] */ final public static function getAllParsers() { - $result = array(); - $dir = __DIR__ . '/Parser'; - if (is_dir($dir) && is_readable($dir)) { - $matches = null; - foreach (scandir($dir) as $item) { - if (($item[0] !== '.') && preg_match('/^(.+)\.php$/i', $item, $matches)) { - $fqClassName = '\\' . __NAMESPACE__ . '\\Parser\\' . $matches[1]; - $result[] = new $fqClassName(); - } - } - } + $factory = self::getParserFactory(); - return $result; + return $factory->getParsers(); } + /** + * @deprecated Use ParserFactory + * + * @return \C5TL\Parser|null + */ final public static function getByHandle($parserHandle) { - $parser = null; - $fqClassName = '\\' . __NAMESPACE__ . '\\Parser\\' . static::camelifyString($parserHandle); - if (class_exists($fqClassName, true)) { - $parser = new $fqClassName(); - } + $factory = self::getParserFactory(); - return $parser; + return $factory->getParserByHandle($parserHandle); } /** diff --git a/src/Parser/Dynamic.php b/src/Parser/Dynamic.php index b06bd24..d8ffc1a 100644 --- a/src/Parser/Dynamic.php +++ b/src/Parser/Dynamic.php @@ -2,11 +2,22 @@ namespace C5TL\Parser; +use C5TL\Parser\DynamicItem\DynamicItem; + /** * Extract translatable strings from block type templates. */ class Dynamic extends \C5TL\Parser { + private $subParsers = array(); + + public function __construct() + { + foreach ($this->getDefaultSubParsers() as $subParser) { + $this->registerSubParser($subParser); + } + } + /** * {@inheritdoc} * @@ -27,6 +38,16 @@ public function canParseRunningConcrete5() return true; } + /** + * @return $this + */ + public function registerSubParser(DynamicItem $subParser) + { + $this->subParsers[$subParser->getDynamicItemsParserHandler()] = $subParser; + + return $this; + } + /** * {@inheritdoc} * @@ -44,9 +65,27 @@ protected function parseRunningConcrete5Do(\Gettext\Translations $translations, /** * Returns the fully-qualified class names of all the sub-parsers. * - * @return array[\C5TL\Parser\DynamicItem\DynamicItem] + * @return \C5TL\Parser\DynamicItem\DynamicItem[] */ public function getSubParsers() + { + return array_values($this->subParsers); + } + + /** + * @param string|mixed $handle + * + * @return \C5TL\Parser\DynamicItem\DynamicItem|null + */ + public function getSubParserByHandle($handle) + { + return is_string($handle) && isset($this->subParsers[$handle]) ? $this->subParsers[$handle] : null; + } + + /** + * @return \C5TL\Parser\DynamicItem\DynamicItem[] + */ + private function getDefaultSubParsers() { $result = array(); $dir = __DIR__ . '/DynamicItem'; @@ -55,9 +94,7 @@ public function getSubParsers() foreach (scandir($dir) as $item) { if (($item[0] !== '.') && preg_match('/^(.+)\.php$/i', $item, $matches) && ($matches[1] !== 'DynamicItem')) { $fqClassName = '\\' . __NAMESPACE__ . '\\DynamicItem\\' . $matches[1]; - $instance = new $fqClassName(); - /* @var $instance \C5TL\Parser\DynamicItem\DynamicItem */ - $result[$instance->getDynamicItemsParserHandler()] = $instance; + $result[] = new $fqClassName(); } } } diff --git a/src/ParserFactory.php b/src/ParserFactory.php new file mode 100644 index 0000000..b7df725 --- /dev/null +++ b/src/ParserFactory.php @@ -0,0 +1,68 @@ +getDefaultParsers() as $parser) { + $this->registerParser($parser); + } + } + /** + * * @return \C5TL\Parser[] + */ + public function getParsers() + { + return array_values($this->parsers); + } + + /** + * @param string|mixed $handle + * + * @return \C5TL\Parser|null + */ + public function getParserByHandle($handle) + { + return is_string($handle) && isset($this->parsers[$handle]) ? $this->parsers[$handle] : null; + } + + /** + * @return $this + */ + public function registerParser(Parser $parser) + { + $this->parsers[$parser->getParserHandle()] = $parser; + + return $this; + } + + /** + * @return \C5TL\Parser[] + */ + private function getDefaultParsers() + { + $result = array(); + $dir = __DIR__ . '/Parser'; + if (is_dir($dir) && is_readable($dir)) { + $matches = null; + foreach (scandir($dir) as $item) { + if (($item[0] !== '.') && preg_match('/^(.+)\.php$/i', $item, $matches)) { + $fqClassName = '\\' . __NAMESPACE__ . '\\Parser\\' . $matches[1]; + $result[] = new $fqClassName(); + } + } + } + + return $result; + } +} diff --git a/src/Util/ConfigFile.php b/src/Util/ConfigFile.php index b9548c2..873469c 100644 --- a/src/Util/ConfigFile.php +++ b/src/Util/ConfigFile.php @@ -103,6 +103,7 @@ private function readFile($filename) */ private function setCustomizersPosition() { + $m = null; if (!preg_match('/^\s*return[\n\s]+(?:\[|array[\s\n]*\()/ims', $this->contents, $m)) { throw new Exception('Failed to determine the start of the return array'); } diff --git a/test/bootstrap.php b/test/bootstrap.php new file mode 100644 index 0000000..a999bf1 --- /dev/null +++ b/test/bootstrap.php @@ -0,0 +1,11 @@ += 0) { + class_alias('C5TL\\Test\\TestCase8', 'C5TL\\Test\\TestCase'); +} elseif (class_exists('PHPUnit\\Runner\\Version') && version_compare(PHPUnit\Runner\Version::id(), '6') >= 0) { + class_alias('C5TL\\Test\\TestCase6', 'C5TL\\Test\\TestCase'); +} else { + class_alias('C5TL\\Test\\TestCase4', 'C5TL\\Test\\TestCase'); +} diff --git a/test/helpers/TestCase4.php b/test/helpers/TestCase4.php new file mode 100644 index 0000000..d8855e7 --- /dev/null +++ b/test/helpers/TestCase4.php @@ -0,0 +1,16 @@ +getParserByHandle('dynamic'); + $this->assertNotNull($parser); + $this->assertInstanceOf('C5TL\Parser\Dynamic', $parser); + $subParsers = $parser->getSubParsers(); + $this->assertSame(array_values($subParsers), $subParsers); + $this->assertNull($parser->getSubParserByHandle('this does not exist')); + } + + public static function provideRequiredParserHandles() + { + return array( + array('area'), + array('attribute_key'), + array('attribute_key_category'), + array('attribute_set'), + array('attribute_type'), + array('authentication_type'), + array('express_form_field_set'), + array('group'), + array('group_set'), + array('job_set'), + array('permission_access_entity_type'), + array('permission_key'), + array('permission_key_category'), + array('select_attribute_value'), + array('tree'), + ); + } + + /** + * @dataProvider provideRequiredParserHandles + */ + public function testRequiredParsers($handle) + { + $factory = new ParserFactory(); + $parser = $factory->getParserByHandle('dynamic'); + $subParser = $parser->getSubParserByHandle($handle); + $this->assertNotNull($subParser); + $this->assertInstanceOf('C5TL\Parser\DynamicItem\DynamicItem', $subParser); + $this->assertSame($handle, $subParser->getDynamicItemsParserHandler()); + } +} diff --git a/test/tests/FactoryTest.php b/test/tests/FactoryTest.php new file mode 100644 index 0000000..d17ff07 --- /dev/null +++ b/test/tests/FactoryTest.php @@ -0,0 +1,41 @@ +getParsers(); + $this->assertSame('array', gettype($parsers)); + $this->assertNotSame(array(), $parsers); + $this->assertSame(array_values($parsers), $parsers); + $this->assertNull($factory->getParserByHandle('this does not exist')); + } + + public static function provideRequiredParserHandles() + { + return array( + array('block_templates'), + array('cif'), + array('config_files'), + array('dynamic'), + array('php'), + array('theme_presets'), + ); + } + + /** + * @dataProvider provideRequiredParserHandles + */ + public function testRequiredParsers($handle) + { + $factory = new ParserFactory(); + $parser = $factory->getParserByHandle($handle); + $this->assertNotNull($parser); + $this->assertSame($handle, $parser->getParserHandle()); + } +}