$v) { if (preg_match("/^[a-z]/", $k) && $v === true) { $out[] = $k; } } return implode(" ", $out); } /** @codeCoverageIgnore */ protected function loadConf(): bool { Arsse::bootstrap(); return true; } protected function resolveFile($file, string $mode): string { // TODO: checking read/write permissions on the provided path may be useful $stdinOrStdout = in_array($mode, ["r", "r+"]) ? "php://input" : "php://output"; return ($file === "-" ? null : $file) ?? $stdinOrStdout; } public function dispatch(array $argv = null): int { $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)) ->addOperand(Operand::create("password", Operand::OPTIONAL)) ->addOption(Option::create(null, "admin")), Command::create("user remove", [$this, "userRemove"]) ->addOperand(Operand::create("username", Operand::REQUIRED)), Command::create("user show", [$this, "userShow"]) ->addOperand(Operand::create("username", Operand::REQUIRED)), Command::create("user set", [$this, "userSet"]) ->addOperand(Operand::create("username", Operand::REQUIRED)) ->addOperand(Operand::create("property", Operand::REQUIRED)) ->addOperand(Operand::create("value", Operand::REQUIRED)), Command::create("user unset", [$this, "userUnset"]) ->addOperand(Operand::create("username", Operand::REQUIRED)) ->addOperand(Operand::create("property", Operand::REQUIRED)), Command::create("user set-pass", [$this, "userSetPass"]) ->addOperand(Operand::create("username", operand::REQUIRED)) ->addOperand(Operand::create("password", Operand::OPTIONAL)) ->addOption(Option::create(null, "fever")), Command::create("user unset-pass", [$this, "userUnsetPass"]) ->addOperand(Operand::create("username", operand::REQUIRED)) ->addOption(Option::create(null, "fever")), Command::create("user auth", [$this, "userAuth"]) ->addOperand(Operand::create("username", operand::REQUIRED)) ->addOperand(Operand::create("password", Operand::REQUIRED)) ->addOption(Option::create(null, "fever")), Command::create("token list", [$this, "tokenList"]) ->addOperand(Operand::create("username", Operand::REQUIRED)), Command::create("token create", [$this, "tokenCreate"]) ->addOperand(Operand::create("username", Operand::REQUIRED)) ->addOperand(Operand::create("label", Operand::OPTIONAL)), Command::create("token revoke", [$this, "tokenRevoke"]) ->addOperand(Operand::create("username", Operand::REQUIRED)) ->addOperand(Operand::create("token", Operand::OPTIONAL)), Command::create("import", [$this, "import"]) ->addOperand(Operand::create("username", operand::REQUIRED)) ->addOperand(Operand::create("file", Operand::OPTIONAL)) ->addOption(Option::create("f", "flat")) ->addOption(Option::create("r", "replace")), Command::create("export", [$this, "export"]) ->addOperand(Operand::create("username", operand::REQUIRED)) ->addOperand(Operand::create("file", Operand::OPTIONAL)) ->addOption(Option::create("f", "flat")), Command::create("daemon", [$this, "daemon"]) ->addOption(Option::create(null, "fork", GetOpt::REQUIRED_ARGUMENT)->setArgumentName("pidfile")), Command::create("feed refresh-all", [$this, "feedRefreshAll"]), Command::create("feed refresh", [$this, "feedRefresh"]) ->addOperand(Operand::create("n", Operand::REQUIRED)), Command::create("conf save-defaults", [$this, "confSaveDefaults"]) ->addOperand(Operand::create("file", Operand::OPTIONAL)), ]); try { $cli // 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); 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(); } // run the requested command switch ($cmd) { case "": if ($args['--version']) { echo Arsse::VERSION.\PHP_EOL; } elseif ($args['--help'] || $args['-h']) { echo $this->usage($argv0).\PHP_EOL; } return 0; case "daemon": if ($args['--fork'] !== null) { return $this->serviceFork($args['--fork']); } else { $this->loadConf(); Arsse::$obj->get(Service::class)->watch(true); } return 0; case "feed refresh": return (int) !Arsse::$db->feedUpdate((int) $args[''], true); case "feed refresh-all": Arsse::$obj->get(Service::class)->watch(false); return 0; case "conf save-defaults": $file = $this->resolveFile($args[''], "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'])); 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'])); case "token list": case "list token": // command reconstruction yields this order for "token list" command return $this->tokenList($args['']); case "token create": echo Arsse::$obj->get(Miniflux::class)->tokenGenerate($args[''], $args['