From 2eafdeb7b08766186f88a7e5bf4139d7c55cc2b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Droz?= Date: Fri, 20 Jul 2018 14:43:08 -0300 Subject: [PATCH 1/4] symfony-console arguments handling + WordPress Timber bridge * This commit uses symfony-console for manage arguments specific to Twig-Gettext-Extractor which used to be broken. * The $input is also passed to the extractor for possible futur use (eg: handling --debug) * Better Twig loaded. Twig Environment use a ChainLoader which is initialized according to file/paths passed to the command line. * It's possible to add a template directory the Twig-way and compile for one (or multiple templates). * The list of Twig templates to compile can now be passed through stdin, using --files stdin * Twig_SimpleFilter is fixed. true is not an accepted value anymore. Empty value callback avoid the cryptic Twig error: `Function 1() does not exist` * A --require flag is added to add custom PHP code before compilation. * Using the above, a brigde with WordPress+Timber is added. It loads Timber Twig extensions and add a couple of WordPress specific parameters to xgettext. * integrate #53 (debugging) --- README.md | 2 +- Twig/Gettext/Extractor.php | 24 ++++++--- composer.json | 13 ++--- twig-gettext-extractor | 108 ++++++++++++++++++++++++------------- wp-timber-bridge.php | 16 ++++++ 5 files changed, 113 insertions(+), 50 deletions(-) create mode 100644 wp-timber-bridge.php diff --git a/README.md b/README.md index 7f688c7..29e8428 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ with the following options: - Language: `Twig` - List of extensions: `*.twig` - Invocation: - - Parser command: `/vendor/bin/twig-gettext-extractor --sort-output --force-po -o %o %C %K -L PHP --files %F` (replace `` with absolute path to your project) + - Parser command: `/vendor/bin/twig-gettext-extractor --files %F -- --sort-output --force-po -o %o %C %K -L PHP` (replace `` with absolute path to your project) - An item in keyword list: `-k%k` - An item in input file list: `%f` - Source code charset: `--from-code=%c` diff --git a/Twig/Gettext/Extractor.php b/Twig/Gettext/Extractor.php index e5247d9..ed6ec46 100644 --- a/Twig/Gettext/Extractor.php +++ b/Twig/Gettext/Extractor.php @@ -32,13 +32,18 @@ class Extractor */ protected $parameters; + /** + * @var `Symfony\Component\Console\Input\ArgvInput + */ + protected $input; + private $executable; - public function __construct(\Twig_Environment $environment) + public function __construct(\Twig_Environment $environment, $input = null) { $this->environment = $environment; - $this->reset(); + $this->input = $input; } /** @@ -57,6 +62,9 @@ protected function reset() public function addTemplate($path) { $this->environment->loadTemplate($path); + if ($this->environment->isDebug()) { + fprintf(STDERR, $path . PHP_EOL); + } } public function addGettextParameter($parameter) @@ -71,11 +79,15 @@ public function setGettextParameters(array $parameters) public function extract() { - $command = $this->executable ?: 'xgettext'; + $command = $this->executable; $command .= ' ' . implode(' ', $this->parameters); $command .= ' ' . $this->environment->getCache() . '/*/*.php'; $error = 0; + if ($this->environment->isDebug()) { + fprintf(STDERR, $command . PHP_EOL); + } + $output = system($command, $error); if (0 !== $error) { throw new \RuntimeException(sprintf( @@ -85,13 +97,13 @@ public function extract() $output )); } - - $this->reset(); } public function __destruct() { $filesystem = new Filesystem(); - $filesystem->remove($this->environment->getCache()); + if (! $this->environment->isDebug()) { + $filesystem->remove($this->environment->getCache()); + } } } diff --git a/composer.json b/composer.json index 61428d5..629cf2f 100644 --- a/composer.json +++ b/composer.json @@ -13,12 +13,13 @@ "php": "^7.1", "twig/twig": "^2.0", "twig/extensions": "~1.0", - "symfony/twig-bridge": "^4.0", - "symfony/routing": "^4.0", - "symfony/filesystem": "^4.0", - "symfony/translation": "^4.0", - "symfony/form": "^4.0", - "symfony/asset": "^4.0" + "symfony/twig-bridge": "~3.0|~4.0", + "symfony/routing": "~3.0|~4.0", + "symfony/filesystem": "~3.0|~4.0", + "symfony/translation": "~3.0|~4.0", + "symfony/form": "~3.0|~4.0", + "symfony/asset": "~2.8|~3.0|~4.0", + "symfony/console": "^4.1" }, "require-dev": { "symfony/config": "^4.0", diff --git a/twig-gettext-extractor b/twig-gettext-extractor index d45aed5..db51be6 100755 --- a/twig-gettext-extractor +++ b/twig-gettext-extractor @@ -14,6 +14,7 @@ * Extracts translations from twig templates. * * @author Саша Стаменковић + * @author Raphaël Droz */ if (file_exists($a = __DIR__ . '/../../autoload.php')) { @@ -22,9 +23,46 @@ if (file_exists($a = __DIR__ . '/../../autoload.php')) { require_once __DIR__ . '/vendor/autoload.php'; } -$twig = new Twig_Environment(new Twig\Gettext\Loader\Filesystem(DIRECTORY_SEPARATOR), [ - 'cache' => implode(DIRECTORY_SEPARATOR, [sys_get_temp_dir(), 'cache', uniqid()]), - 'auto_reload' => true, +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputDefinition; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputOption; + + +// You can add more extensions here, or via command line with the --functions and --filter options +$input = new ArgvInput($argv, new InputDefinition([ + new InputOption('exec', null, InputOption::VALUE_REQUIRED, 'xgettext binary', 'xgettext'), + new InputOption('files', null, InputOption::VALUE_REQUIRED, 'template files'), + new InputOption('dir', null, InputOption::VALUE_REQUIRED, 'template directory', '.'), + new InputOption('functions', null, InputOption::VALUE_REQUIRED, 'custom Twig extensions to load'), + new InputOption('filters', null, InputOption::VALUE_REQUIRED, 'custom Twig filter to support'), + new InputOption('require', null, InputOption::VALUE_REQUIRED, 'Additional PHP file to require'), + new InputOption('debug', 'd', InputOption::VALUE_NONE), + new InputArgument('gettext_parameters', InputArgument::IS_ARRAY, 'Additional xgettext parameters', []) +])); + +// Twig Loaders options +$loaders = new Twig_Loader_Chain(); + +if ($input->getOption('dir')) { + $directory_loader = new \Twig_Loader_Filesystem( explode(',', $input->getOption('dir')) ); + $loaders->addLoader($directory_loader); +} +if ($input->getOption('files')) { + $files = []; + foreach(explode(',', $input->getOption('files')) as $f) { + if (file_exists($f)) { + $files[$f] = file_get_contents($f); + } + } + $loaders->addLoader(new Twig_Loader_Array($files)); +} + +// Loader initialized => Initialize Twig Environment +$cachedir = implode(DIRECTORY_SEPARATOR, [sys_get_temp_dir(), 'cache', uniqid()]); +$twig = new Twig_Environment($loaders, [ + 'cache' => $cachedir, + 'auto_reload' => true ]); $twig->addExtension(new Twig_Extensions_Extension_I18n()); $twig->addExtension(new Symfony\Bridge\Twig\Extension\TranslationExtension( @@ -38,43 +76,39 @@ $twig->addExtension(new Symfony\Bridge\Twig\Extension\AssetExtension( new Symfony\Component\Asset\Packages() )); -// You can add more extensions here, or via command line with the --functions and --filter options - -array_shift($_SERVER['argv']); +if ($input->getOption('debug')) { + $twig->enableDebug(); +} +if ($input->getOption('functions')) { + foreach (explode(',', $input->getOption('functions')) as $functionName) { + $twig->addFunction(new \Twig_SimpleFunction($functionName, true)); + } +} +if ($input->getOption('filters')) { + foreach (explode(',', $input->getOption('filters')) as $filterName) { + $twig->addFilter(new \Twig_SimpleFilter($filterName, function($e) { return ""; })); + } +} -$setFunctions = false; -$setFilters = false; -$addTemplate = false; -$setExecutable = false; +// Twig Environment is up => initialize extractor +$extractor = new Twig\Gettext\Extractor($twig, $input); +$extractor->setGettextParameters($input->getArgument('gettext_parameters')); +$extractor->setExecutable($input->getOption('exec')); -$extractor = new Twig\Gettext\Extractor($twig); +if ($input->getOption('require')) { + require_once($input->getOption('require')); +} -foreach ($_SERVER['argv'] as $arg) { - if ('--files' === $arg) { - $addTemplate = true; - } else if ($addTemplate) { - $extractor->addTemplate(getcwd() . DIRECTORY_SEPARATOR . $arg); - } else if ('--exec' === $arg) { - $setExecutable = true; - } else if ($setExecutable) { - $extractor->setExecutable($arg); - $setExecutable = false; - } else if ('--functions' === $arg) { - $setFunctions = true; - } else if ($setFunctions) { - foreach (explode(',', $arg) as $functionName) { - $twig->addFunction(new \Twig_SimpleFunction($functionName, true)); - } - $setFunctions = false; - } else if ('--filters' === $arg) { - $setFilters = true; - } else if ($setFilters) { - foreach (explode(',', $arg) as $filterName) { - $twig->addFilter(new \Twig_SimpleFilter($filterName, true)); - } - $setFilters = false; - } else { - $extractor->addGettextParameter($arg); +// compile +if ($input->getOption('files')) { + if ($input->getOption('files') == 'stdin') { + $files = explode("\n", trim(file_get_contents('php://stdin'))); + } + else { + $files = explode(',', $input->getOption('files')); + } + foreach($files as $f) { + $extractor->addTemplate($f); } } diff --git a/wp-timber-bridge.php b/wp-timber-bridge.php new file mode 100644 index 0000000..3869b22 --- /dev/null +++ b/wp-timber-bridge.php @@ -0,0 +1,16 @@ + +// Bridge between Twig-Gettext-Extractor and WordPress/Timber: +// find templates/ -type f -name '*twig' | TIMBER_DIR=/path/to/wp/plugins/timber-library twig-gettext-extractor --require wp-timber-bridge.php --files stdin -- -o - + +function add_action() { } +include_once(getenv('TIMBER_DIR') . '/lib/Twig_Function.php'); +include_once(getenv('TIMBER_DIR') . '/lib/Twig.php'); +$x = new \Timber\Twig(); +$x->add_timber_functions($twig); + +foreach(['__','_e','_n','_x','_ex','_nx','esc_attr__','esc_attr_e','esc_attr_x','esc_html__','esc_html_e','esc_html_x','_n_noop','_nx_noop','translate_nooped_plural'] as $a) { + $extractor->addGettextParameter('-k' . $a); +} +$extractor->addGettextParameter('-L'); +$extractor->addGettextParameter('PHP'); From e5aa095347b608baf0e7f08867ac52045c430429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Droz?= Date: Fri, 20 Jul 2018 16:24:22 -0300 Subject: [PATCH 2/4] phpdoc --- Twig/Gettext/Extractor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Twig/Gettext/Extractor.php b/Twig/Gettext/Extractor.php index ed6ec46..c4eae57 100644 --- a/Twig/Gettext/Extractor.php +++ b/Twig/Gettext/Extractor.php @@ -33,7 +33,7 @@ class Extractor protected $parameters; /** - * @var `Symfony\Component\Console\Input\ArgvInput + * @var \Symfony\Component\Console\Input\InputInterface */ protected $input; From 9086ff552ce10e4eb553b41c7b6a1450f4f8d5c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Droz?= Date: Fri, 20 Jul 2018 17:40:40 -0300 Subject: [PATCH 3/4] composer.json --- composer.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.json b/composer.json index 629cf2f..3d9e40f 100644 --- a/composer.json +++ b/composer.json @@ -13,13 +13,13 @@ "php": "^7.1", "twig/twig": "^2.0", "twig/extensions": "~1.0", - "symfony/twig-bridge": "~3.0|~4.0", - "symfony/routing": "~3.0|~4.0", - "symfony/filesystem": "~3.0|~4.0", - "symfony/translation": "~3.0|~4.0", - "symfony/form": "~3.0|~4.0", - "symfony/asset": "~2.8|~3.0|~4.0", - "symfony/console": "^4.1" + "symfony/twig-bridge": "^4.0", + "symfony/routing": "^4.0", + "symfony/filesystem": "^4.0", + "symfony/translation": "^4.0", + "symfony/form": "^4.0", + "symfony/asset": "^4.0", + "symfony/console": "^4.0" }, "require-dev": { "symfony/config": "^4.0", From 2d097d6cde52bc5a60188aeb7545797bb65fe91f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Droz?= Date: Sat, 21 Jul 2018 00:59:30 -0300 Subject: [PATCH 4/4] removed passing $input to Extractor --- Twig/Gettext/Extractor.php | 8 +------- twig-gettext-extractor | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Twig/Gettext/Extractor.php b/Twig/Gettext/Extractor.php index c4eae57..13aacbe 100644 --- a/Twig/Gettext/Extractor.php +++ b/Twig/Gettext/Extractor.php @@ -32,18 +32,12 @@ class Extractor */ protected $parameters; - /** - * @var \Symfony\Component\Console\Input\InputInterface - */ - protected $input; - private $executable; - public function __construct(\Twig_Environment $environment, $input = null) + public function __construct(\Twig_Environment $environment) { $this->environment = $environment; - $this->input = $input; } /** diff --git a/twig-gettext-extractor b/twig-gettext-extractor index db51be6..6fed9bf 100755 --- a/twig-gettext-extractor +++ b/twig-gettext-extractor @@ -91,7 +91,7 @@ if ($input->getOption('filters')) { } // Twig Environment is up => initialize extractor -$extractor = new Twig\Gettext\Extractor($twig, $input); +$extractor = new Twig\Gettext\Extractor($twig); $extractor->setGettextParameters($input->getArgument('gettext_parameters')); $extractor->setExecutable($input->getOption('exec'));