From 8818a0d03019cb89f14ea2775dca16f122feeaa5 Mon Sep 17 00:00:00 2001 From: Mathieu Santostefano Date: Mon, 30 Aug 2021 17:03:28 +0200 Subject: [PATCH 1/2] Build a .phar version --- .gitignore | 1 + README.md | 35 +++++++++ box.json | 45 ++++++++++++ composer.json | 3 +- jolitypo | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 box.json create mode 100755 jolitypo diff --git a/.gitignore b/.gitignore index b932afd..cd2a620 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /.phpunit.result.cache /composer.lock /vendor/ +/build/ diff --git a/README.md b/README.md index da8e8e1..1a12400 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,33 @@ $fixer = new Fixer(array("Trademark", "SmartQuotes")); $fixedContent = $fixer->fixString('Here is a "protip(c)"!'); // Here is a “protip©”! ``` +CLI usage +========= + +You can run a standalone version of JoliTypo by downloading [the PHAR version](https://github.com/jolicode/JoliTypo/releases/latest) + +Run `jolitypo.phar --help` to know how to configure the Fixer. + +``` +Fix Microtypography glitches inside your HTML content. + +Usage: ./jolitypo.phar [--rules="Ellipsis,Dimension,Unit,Dash,SmartQuotes,NoSpaceBeforeComma,CurlyQuote,Hyphen,Trademark"] --locale locale --file file [--quiet] [--help] + +Required Arguments: + --locale locale + Locale of the content to fix. + --file file + File to fix. + +Optional Arguments: + --rules="Ellipsis,Dimension,Unit,Dash,SmartQuotes,NoSpaceBeforeComma,CurlyQuote,Hyphen,Trademark" + Rules used to fix the content, comma separated. + --quiet + Do not output anything. + --help + Show this help. +``` + Installation ============ @@ -71,6 +98,7 @@ Integrations - (Built-in) [Symfony Bundle](src/JoliTypo/Bridge/Symfony) - (Built-in) [Twig extension](src/JoliTypo/Bridge/Twig) +- (Built-in) [CLI](https://github.com/jolicode/JoliTypo/releases/latest) - [Wordpress plugin](http://wordpress.org/plugins/typofr/) - [Drupal module](https://github.com/Anaethelion/JoliTypo-for-Drupal) - [Joomla plugin](https://github.com/YGomiero/typographe) @@ -247,6 +275,13 @@ Add your own Fixer / Contribute a Fixer - Implement `JoliTypo\FixerInterface`; - Send your Pull request. +Build the PHAR version +====================== + +Install [Box](https://github.com/box-project/box) and its dependencies with `composer bin box require --dev humbug/box` + +Then, run `composer run compile` + ### Contribution guidelines - You MUST write code in english; diff --git a/box.json b/box.json new file mode 100644 index 0000000..2b67730 --- /dev/null +++ b/box.json @@ -0,0 +1,45 @@ +{ + "chmod": "0755", + "directories": [ + "src" + ], + "files": [ + "LICENSE", + "vendor/autoload.php" + ], + "finder": [ + { + "name": "*.php", + "in": "vendor/composer" + }, + { + "name": "*.php", + "exclude": ["tests"], + "in": "vendor/org_heigl" + }, + { + "name": "*.properties", + "exclude": ["tests"], + "in": "vendor/org_heigl" + }, + { + "name": "*.ini", + "exclude": ["tests"], + "in": "vendor/org_heigl" + }, + { + "name": "*.dic", + "exclude": ["tests"], + "in": "vendor/org_heigl" + }, + { + "name": "*.txt", + "exclude": ["tests"], + "in": "vendor/org_heigl" + } + ], + "git-version": "package_version", + "main": "jolitypo", + "output": "build/jolitypo.phar", + "stub": true +} diff --git a/composer.json b/composer.json index 64276b3..d249da6 100644 --- a/composer.json +++ b/composer.json @@ -36,6 +36,7 @@ }, "scripts": { "test": "vendor/bin/simple-phpunit -c phpunit.xml.dist", - "cs": "vendor/bin/php-cs-fixer fix" + "cs": "vendor/bin/php-cs-fixer fix", + "compile": "bin/box.phar compile" } } diff --git a/jolitypo b/jolitypo new file mode 100755 index 0000000..3d8306e --- /dev/null +++ b/jolitypo @@ -0,0 +1,193 @@ +#!/usr/bin/env php + ['Ellipsis', 'Dimension', 'Unit', 'Dash', 'SmartQuotes', 'NoSpaceBeforeComma', 'CurlyQuote', 'Hyphen', 'Trademark'], + 'fr_FR' => ['Ellipsis', 'Dimension', 'Unit', 'Dash', 'SmartQuotes', 'FrenchNoBreakSpace', 'NoSpaceBeforeComma', 'CurlyQuote', 'Hyphen', 'Trademark'], + 'fr_CA' => ['Ellipsis', 'Dimension', 'Unit', 'Dash', 'SmartQuotes', 'NoSpaceBeforeComma', 'CurlyQuote', 'Hyphen', 'Trademark'], + 'de_DE' => ['Ellipsis', 'Dimension', 'Unit', 'Dash', 'SmartQuotes', 'NoSpaceBeforeComma', 'CurlyQuote', 'Hyphen', 'Trademark'], + ]; + const ARGUMENTS = [ + 'rules' => [ + 'name' => 'rules', + 'info' => 'Rules used to fix the content, comma separated.', + 'required' => false, + 'example' => "Ellipsis,Dimension,Unit,Dash,SmartQuotes,NoSpaceBeforeComma,CurlyQuote,Hyphen,Trademark", + ], + 'locale' => [ + 'name' => 'locale', + 'info' => 'Locale of the content to fix.', + 'required' => true, + ], + 'file' => [ + 'name' => 'file', + 'info' => 'File to fix.', + 'required' => true, + ], + 'quiet' => [ + 'name' => 'quiet', + 'info' => 'Do not output anything.', + 'required' => false, + ], + 'help' => [ + 'name' => 'help', + 'info' => 'Show this help.', + 'required' => false, + 'flag' => true, + ], + ]; + + private $arguments = []; + private $command; + + public function __construct() + { + $this->command = $_SERVER['argv'][0]; + } + + public function parse() + { + $options = ''; + $longOptions = array_map(function ($rule) { + $flag = $rule['required'] ? ':' : '::'; + + return $rule['name'] . $flag; + }, self::ARGUMENTS); + + $this->arguments = getopt($options, $longOptions) ?: []; + } + + public function getOption(string $name) + { + return $this->arguments[$name] ?? false; + } + + public function hasOption(string $name): bool + { + return isset($this->arguments[$name]); + } + + public function validate(): bool + { + $valid = true; + + foreach (self::ARGUMENTS as $arg) { + if ($arg['required'] && !$this->hasOption($arg['name'])) { + $this->log("Please specify fixer {$arg['name']} with the option --{$arg['name']}"); + $valid = false; + } + } + + return $valid; + } + + public function showUsage() + { + $required = []; + $optional = []; + $usage = $this->command; + + foreach (self::ARGUMENTS as $name => $arg) { + $prefix = $postfix = ''; + if ($arg['required']) { + $required[$name] = $arg; + } else { + $optional[$name] = $arg; + $prefix = '['; + $postfix = ']'; + } + $usage .= ' ' . $prefix . $this->formatUsage($name, $arg) . $postfix; + } + + $this->log(self::DESCRIPTION); + $this->log(PHP_EOL . 'Usage: ' . trim($usage)); + + $this->log(PHP_EOL . 'Required Arguments:'); + foreach ($required as $name => $info) { + $value = $this->formatUsage($name, $info); + $this->log("\t$value"); + $this->log("\t\t{$info['info']}"); + } + + $this->log(PHP_EOL . 'Optional Arguments:'); + foreach ($optional as $name => $info) { + $value = $this->formatUsage($name, $info); + $this->log("\t$value"); + $this->log("\t\t{$info['info']}"); + } + } + + public function log(string $message) + { + if (!$this->hasOption('quiet')) { + echo $message . PHP_EOL; + } + } + + private function formatUsage($name, $rule): string + { + $example = $rule['example'] ?? $name; + $value = $rule['required'] ? " $name" : "=\"$example\""; + $value = isset($rule['flag']) && $rule['flag'] ? '' : $value; + + return '--' . $name . $value; + } +} + +$cli = new Cli(); + +try { + $cli->parse(); +} catch (Exception $e) { + $cli->log($e->getMessage()); + exit(1); +} + +if ($cli->hasOption('help')) { + $cli->showUsage(); + exit(0); +} + +if (!$cli->validate()) { + exit(1); +} + +$locale = $cli->getOption('locale'); +$file = $cli->getOption('file'); +$rules = $cli->getOption('rules'); + +if ($rules) { + $rules = explode(',', $rules); +} elseif (array_key_exists($locale, Cli::RECOMMENDED_RULES_BY_LOCALE)) { + $rules = Cli::RECOMMENDED_RULES_BY_LOCALE[$locale]; +} else { + $cli->log(sprintf('There is no recommended rules for "%s" locale. Please specify manually the rules to apply.', $locale)); + exit(1); +} + +if (!file_exists($file)) { + $cli->log(sprintf('The file "%s" does not exist.', $file)); + exit(1); +} + +$fixer = new Fixer($rules); +$fixer->setLocale($locale); + +$fixedContent = $fixer->fix(file_get_contents($file)); +file_put_contents($file, $fixedContent); + +$cli->log(sprintf('"%s" content has been fixed with success!', $file)); + From bcf636960f9c43b220a16a19180fe7a38b706aae Mon Sep 17 00:00:00 2001 From: Mathieu Santostefano Date: Mon, 30 Aug 2021 18:33:32 +0200 Subject: [PATCH 2/2] Use symfony/console --- box.json | 38 +++----- composer.json | 4 +- jolitypo | 237 ++++++++++++-------------------------------------- 3 files changed, 70 insertions(+), 209 deletions(-) diff --git a/box.json b/box.json index 2b67730..9a7de5d 100644 --- a/box.json +++ b/box.json @@ -9,36 +9,18 @@ ], "finder": [ { - "name": "*.php", - "in": "vendor/composer" - }, - { - "name": "*.php", - "exclude": ["tests"], - "in": "vendor/org_heigl" - }, - { - "name": "*.properties", - "exclude": ["tests"], - "in": "vendor/org_heigl" - }, - { - "name": "*.ini", - "exclude": ["tests"], - "in": "vendor/org_heigl" - }, - { - "name": "*.dic", - "exclude": ["tests"], - "in": "vendor/org_heigl" - }, - { - "name": "*.txt", - "exclude": ["tests"], - "in": "vendor/org_heigl" + "name": [ + "*.php", + "*.txt", + "*.properties", + "*.ini", + "*.dic" + ], + "exclude": ["tests", "Tests", "phpunit"], + "in": "vendor" } ], - "git-version": "package_version", + "git-version": "git_version", "main": "jolitypo", "output": "build/jolitypo.phar", "stub": true diff --git a/composer.json b/composer.json index d249da6..8e1e7fc 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,9 @@ "php": ">=7.2.0", "ext-mbstring": "*", "lib-libxml": "*", - "org_heigl/hyphenator": "~2.6.0" + "org_heigl/hyphenator": "~2.6.0", + "symfony/console": "^5.3", + "symfony/finder": "^5.3" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2", diff --git a/jolitypo b/jolitypo index 3d8306e..dbaa3ee 100755 --- a/jolitypo +++ b/jolitypo @@ -1,193 +1,70 @@ #!/usr/bin/env php setName('JoliTypo') + ->setDescription('Fix Microtypography glitches inside your HTML content.') + ->addArgument('path', InputArgument::REQUIRED, 'Path of file(s) to fix.') + ->addOption('locale', 'l', InputOption::VALUE_REQUIRED, 'Locale of the content to fix. Ex: "fr_FR"') + ->addOption('depth', 'd', InputOption::VALUE_OPTIONAL, 'Depth to find files if "path" is a directory.', 1) + ->addOption('rules', 'r', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Set of rules to apply.', []) + ->setCode(function (InputInterface $input, OutputInterface $output) { + $io = new SymfonyStyle($input, $output); + + $path = $input->getArgument('path'); + $depth = $input->getOption('depth'); + $locale = $input->getOption('locale'); + $rules = $input->getOption('rules'); + + $recommendedRulesByLocale = [ + 'en_GB' => ['Ellipsis', 'Dimension', 'Unit', 'Dash', 'SmartQuotes', 'NoSpaceBeforeComma', 'CurlyQuote', 'Hyphen', 'Trademark'], + 'fr_FR' => ['Ellipsis', 'Dimension', 'Unit', 'Dash', 'SmartQuotes', 'FrenchNoBreakSpace', 'NoSpaceBeforeComma', 'CurlyQuote', 'Hyphen', 'Trademark'], + 'fr_CA' => ['Ellipsis', 'Dimension', 'Unit', 'Dash', 'SmartQuotes', 'NoSpaceBeforeComma', 'CurlyQuote', 'Hyphen', 'Trademark'], + 'de_DE' => ['Ellipsis', 'Dimension', 'Unit', 'Dash', 'SmartQuotes', 'NoSpaceBeforeComma', 'CurlyQuote', 'Hyphen', 'Trademark'], + ]; + + if (!is_dir($path) && !file_exists($path)) { + $io->error(sprintf('The path "%s" does not exist.', $path)); + exit(Command::FAILURE); + } -if (file_exists(__DIR__ . '/vendor/autoload.php')) { - $loader = require(__DIR__ . '/vendor/autoload.php'); -} elseif (file_exists(__DIR__ . '/../../../vendor/autoload.php')) { - $loader = require(__DIR__ . '/../../../vendor/autoload.php'); -} else { - throw new \RuntimeException('Unable to load autoloader.'); -} - -final class Cli -{ - const DESCRIPTION = 'Fix Microtypography glitches inside your HTML content.'; - const RECOMMENDED_RULES_BY_LOCALE = [ - 'en_GB' => ['Ellipsis', 'Dimension', 'Unit', 'Dash', 'SmartQuotes', 'NoSpaceBeforeComma', 'CurlyQuote', 'Hyphen', 'Trademark'], - 'fr_FR' => ['Ellipsis', 'Dimension', 'Unit', 'Dash', 'SmartQuotes', 'FrenchNoBreakSpace', 'NoSpaceBeforeComma', 'CurlyQuote', 'Hyphen', 'Trademark'], - 'fr_CA' => ['Ellipsis', 'Dimension', 'Unit', 'Dash', 'SmartQuotes', 'NoSpaceBeforeComma', 'CurlyQuote', 'Hyphen', 'Trademark'], - 'de_DE' => ['Ellipsis', 'Dimension', 'Unit', 'Dash', 'SmartQuotes', 'NoSpaceBeforeComma', 'CurlyQuote', 'Hyphen', 'Trademark'], - ]; - const ARGUMENTS = [ - 'rules' => [ - 'name' => 'rules', - 'info' => 'Rules used to fix the content, comma separated.', - 'required' => false, - 'example' => "Ellipsis,Dimension,Unit,Dash,SmartQuotes,NoSpaceBeforeComma,CurlyQuote,Hyphen,Trademark", - ], - 'locale' => [ - 'name' => 'locale', - 'info' => 'Locale of the content to fix.', - 'required' => true, - ], - 'file' => [ - 'name' => 'file', - 'info' => 'File to fix.', - 'required' => true, - ], - 'quiet' => [ - 'name' => 'quiet', - 'info' => 'Do not output anything.', - 'required' => false, - ], - 'help' => [ - 'name' => 'help', - 'info' => 'Show this help.', - 'required' => false, - 'flag' => true, - ], - ]; - - private $arguments = []; - private $command; - - public function __construct() - { - $this->command = $_SERVER['argv'][0]; - } - - public function parse() - { - $options = ''; - $longOptions = array_map(function ($rule) { - $flag = $rule['required'] ? ':' : '::'; - - return $rule['name'] . $flag; - }, self::ARGUMENTS); - - $this->arguments = getopt($options, $longOptions) ?: []; - } - - public function getOption(string $name) - { - return $this->arguments[$name] ?? false; - } - - public function hasOption(string $name): bool - { - return isset($this->arguments[$name]); - } - - public function validate(): bool - { - $valid = true; - - foreach (self::ARGUMENTS as $arg) { - if ($arg['required'] && !$this->hasOption($arg['name'])) { - $this->log("Please specify fixer {$arg['name']} with the option --{$arg['name']}"); - $valid = false; - } + if ($rules) { + $rules = explode(',', $rules); + } elseif (array_key_exists($locale, $recommendedRulesByLocale)) { + $rules = $recommendedRulesByLocale[$locale]; + } else { + $io->error(sprintf('There is no recommended rules for "%s" locale. Please specify manually the rules to apply.', $locale)); + exit(Command::FAILURE); } - return $valid; - } + $fixer = new Fixer($rules); + $fixer->setLocale($locale); - public function showUsage() - { - $required = []; - $optional = []; - $usage = $this->command; + if (is_dir($path)) { + $finder = new Finder(); - foreach (self::ARGUMENTS as $name => $arg) { - $prefix = $postfix = ''; - if ($arg['required']) { - $required[$name] = $arg; - } else { - $optional[$name] = $arg; - $prefix = '['; - $postfix = ']'; + foreach ($finder->path($path)->depth($depth)->files() as $file) { + $fixedContent = $fixer->fix(file_get_contents($file->getRealPath())); + file_put_contents($file->getRealPath(), $fixedContent); } - $usage .= ' ' . $prefix . $this->formatUsage($name, $arg) . $postfix; - } - - $this->log(self::DESCRIPTION); - $this->log(PHP_EOL . 'Usage: ' . trim($usage)); - - $this->log(PHP_EOL . 'Required Arguments:'); - foreach ($required as $name => $info) { - $value = $this->formatUsage($name, $info); - $this->log("\t$value"); - $this->log("\t\t{$info['info']}"); - } - $this->log(PHP_EOL . 'Optional Arguments:'); - foreach ($optional as $name => $info) { - $value = $this->formatUsage($name, $info); - $this->log("\t$value"); - $this->log("\t\t{$info['info']}"); - } - } + $io->success(sprintf('All files in "%s" has been fixed with success!', $path)); + } elseif (is_file($path)) { + $fixedContent = $fixer->fix(file_get_contents($path)); + file_put_contents($path, $fixedContent); - public function log(string $message) - { - if (!$this->hasOption('quiet')) { - echo $message . PHP_EOL; + $io->success(sprintf('"%s" content has been fixed with success!', $path)); } - } - - private function formatUsage($name, $rule): string - { - $example = $rule['example'] ?? $name; - $value = $rule['required'] ? " $name" : "=\"$example\""; - $value = isset($rule['flag']) && $rule['flag'] ? '' : $value; - - return '--' . $name . $value; - } -} - -$cli = new Cli(); - -try { - $cli->parse(); -} catch (Exception $e) { - $cli->log($e->getMessage()); - exit(1); -} - -if ($cli->hasOption('help')) { - $cli->showUsage(); - exit(0); -} - -if (!$cli->validate()) { - exit(1); -} - -$locale = $cli->getOption('locale'); -$file = $cli->getOption('file'); -$rules = $cli->getOption('rules'); - -if ($rules) { - $rules = explode(',', $rules); -} elseif (array_key_exists($locale, Cli::RECOMMENDED_RULES_BY_LOCALE)) { - $rules = Cli::RECOMMENDED_RULES_BY_LOCALE[$locale]; -} else { - $cli->log(sprintf('There is no recommended rules for "%s" locale. Please specify manually the rules to apply.', $locale)); - exit(1); -} - -if (!file_exists($file)) { - $cli->log(sprintf('The file "%s" does not exist.', $file)); - exit(1); -} - -$fixer = new Fixer($rules); -$fixer->setLocale($locale); - -$fixedContent = $fixer->fix(file_get_contents($file)); -file_put_contents($file, $fixedContent); - -$cli->log(sprintf('"%s" content has been fixed with success!', $file)); - + }) + ->run();