diff --git a/composer.json b/composer.json index 8df20c4..d280e19 100644 --- a/composer.json +++ b/composer.json @@ -29,7 +29,8 @@ "jkingweb/druuid": "3.*", "guzzlehttp/psr7": "2.*", "laminas/laminas-httphandlerrunner": "2.*", - "ulrichsg/getopt-php": "^4.0" + "ulrichsg/getopt-php": "^4.0", + "symfony/polyfill-mbstring": "*" }, "require-dev": { "bamarni/composer-bin-plugin": "*" diff --git a/composer.lock b/composer.lock index 28358b8..03cec71 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bfac70a85f90c472b42fc3d22c4e9f45", + "content-hash": "04ff61652a7fc921308a8a124cdf4c23", "packages": [ { "name": "guzzlehttp/guzzle", - "version": "7.8.0", + "version": "7.8.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9" + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/1110f66a6530a40fe7aea0378fe608ee2b2248f9", - "reference": "1110f66a6530a40fe7aea0378fe608ee2b2248f9", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", "shasum": "" }, "require": { @@ -32,11 +32,11 @@ "psr/http-client-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -114,7 +114,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.8.0" + "source": "https://github.com/guzzle/guzzle/tree/7.8.1" }, "funding": [ { @@ -130,28 +130,28 @@ "type": "tidelift" } ], - "time": "2023-08-27T10:20:53+00:00" + "time": "2023-12-03T20:35:24+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "111166291a0f8130081195ac4556a5587d7f1b5d" + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/111166291a0f8130081195ac4556a5587d7f1b5d", - "reference": "111166291a0f8130081195ac4556a5587d7f1b5d", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", "shasum": "" }, "require": { "php": "^7.2.5 || ^8.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "type": "library", "extra": { @@ -197,7 +197,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.1" + "source": "https://github.com/guzzle/promises/tree/2.0.2" }, "funding": [ { @@ -213,20 +213,20 @@ "type": "tidelift" } ], - "time": "2023-08-03T15:11:55+00:00" + "time": "2023-12-03T20:19:20+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.6.1", + "version": "2.6.2", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727" + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/be45764272e8873c72dbe3d2edcfdfcc3bc9f727", - "reference": "be45764272e8873c72dbe3d2edcfdfcc3bc9f727", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", "shasum": "" }, "require": { @@ -240,9 +240,9 @@ "psr/http-message-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -313,7 +313,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.6.1" + "source": "https://github.com/guzzle/psr7/tree/2.6.2" }, "funding": [ { @@ -329,7 +329,7 @@ "type": "tidelift" } ], - "time": "2023-08-27T10:13:57+00:00" + "time": "2023-12-03T20:05:35+00:00" }, { "name": "hosteurope/password-generator", @@ -1071,6 +1071,89 @@ ], "time": "2022-01-02T09:53:40+00:00" }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.28.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "42292d99c55abe617799667f454222c54c60e229" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", + "reference": "42292d99c55abe617799667f454222c54c60e229", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.28-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-07-28T09:04:16+00:00" + }, { "name": "ulrichsg/getopt-php", "version": "v4.0.3", diff --git a/dist/man/man1/arsse.1 b/dist/man/man1/arsse.1 index 2b68913..71a51fe 100644 --- a/dist/man/man1/arsse.1 +++ b/dist/man/man1/arsse.1 @@ -9,8 +9,7 @@ . . .Sh SYNOPSIS -.Nm "arsse user" -.Op Nm list +.Nm "arsse user list" .Nm "arsse user add" .Ar username .Op Ar password @@ -87,7 +86,7 @@ Further, seldom-used commands are documented in the subsequent section . .Ss Managing users and metadata .Bl -tag -.It Nm "user" Op Nm list +.It Nm "user list" Displays a simple list of user names with one entry per line .It Nm "user add" Ar username Oo Ar password Oc Oo Fl Fl admin Oc Adds a new user to the database with the specified username and password. diff --git a/lib/CLI.php b/lib/CLI.php index 6c81717..5a6cae0 100644 --- a/lib/CLI.php +++ b/lib/CLI.php @@ -22,16 +22,6 @@ class CLI { return str_replace("arsse.php", $prog, self::USAGE); } - protected function command($args): string { - $out = []; - foreach ($args as $k => $v) { - if (preg_match("/^[a-z]/", $k) && $v === true) { - $out[] = $k; - } - } - return implode(" ", $out); - } - /** @codeCoverageIgnore */ protected function loadConf(): bool { Arsse::bootstrap(); @@ -45,13 +35,12 @@ class CLI { } public function dispatch(array $argv = null): int { - $cli = new GetOpt("", []); + $cli = new GetOpt([], []); $cli->addOptions([ Option::create("h", "help"), Option::create(null, "version"), ]); $cli->addCommands([ - Command::create("user", [$this, "userList"]), Command::create("user list", [$this, "userList"]), Command::create("user add", [$this, "userAdd"]) ->addOperand(Operand::create("username", operand::REQUIRED)) @@ -105,11 +94,11 @@ class CLI { ->addOperand(Operand::create("file", Operand::OPTIONAL)), ]); try { - $cli + $cli->process(array_slice($argv, 1)); // ensure the require extensions are loaded Arsse::checkExtensions(...Arsse::REQUIRED_EXTENSIONS); - // reconstitute multi-token commands (e.g. user add) into a single string - $cmd = $this->command($args); + $cmd = $cli->getCommand(); + $cmd = $cmd ? $cmd->getName() : ""; if ($cmd && !in_array($cmd, ["", "conf save-defaults", "daemon"])) { // only certain commands don't require configuration to be loaded; daemon loads configuration after forking (if applicable) $this->loadConf(); @@ -117,81 +106,79 @@ class CLI { // run the requested command switch ($cmd) { case "": - if ($args['--version']) { + if ($cli->getOption("version")) { echo Arsse::VERSION.\PHP_EOL; - } elseif ($args['--help'] || $args['-h']) { - echo $this->usage($argv0).\PHP_EOL; + } else { + echo $cli->getHelpText(); } return 0; case "daemon": - if ($args['--fork'] !== null) { - return $this->serviceFork($args['--fork']); + if (($fork = $cli->getOption("fork")) !== null) { + return $this->serviceFork($fork); } else { $this->loadConf(); Arsse::$obj->get(Service::class)->watch(true); } return 0; case "feed refresh": - return (int) !Arsse::$db->feedUpdate((int) $args[''], true); + return (int) !Arsse::$db->feedUpdate((int) $cli->getOperand("n"), true); case "feed refresh-all": Arsse::$obj->get(Service::class)->watch(false); return 0; case "conf save-defaults": - $file = $this->resolveFile($args[''], "w"); + $file = $this->resolveFile($cli->getOperand("file"), "w"); return (int) !Arsse::$obj->get(Conf::class)->exportFile($file, true); case "export": - $u = $args['']; - $file = $this->resolveFile($args[''], "w"); - return (int) !Arsse::$obj->get(OPML::class)->exportFile($file, $u, ($args['--flat'] || $args['-f'])); + $u = $cli->getOperand("username"); + $file = $this->resolveFile($cli->getOperand("file"), "w"); + return (int) !Arsse::$obj->get(OPML::class)->exportFile($file, $u, (bool) $cli->getOption("flat")); case "import": - $u = $args['']; - $file = $this->resolveFile($args[''], "r"); - return (int) !Arsse::$obj->get(OPML::class)->importFile($file, $u, ($args['--flat'] || $args['-f']), ($args['--replace'] || $args['-r'])); + $u = $cli->getOperand("username"); + $file = $this->resolveFile($cli->getOperand("file"), "r"); + return (int) !Arsse::$obj->get(OPML::class)->importFile($file, $u, (bool) $cli->getOption("flat"), (bool) $cli->getOption("replace")); case "token list": - case "list token": // command reconstruction yields this order for "token list" command - return $this->tokenList($args['']); + $u = $cli->getOperand("username"); case "token create": - echo Arsse::$obj->get(Miniflux::class)->tokenGenerate($args[''], $args['