mirror of https://github.com/FreshRSS/FreshRSS.git
Command Line Parser Concept (#6099)
* Adds logic for validation * Adds validation to do-install * Adds help to do-install * Adds validation & help to reconfigure * Adds validation to check.translation * Adds validation to manipulate.translation * Small fixes to help texts * Refactors language option validation * Adds default options to validation * Fixes validation with regex * Refactors readAs functions * Updates to new regex validation format * Fixes typing around default values * Adds file extension validation * Restandardises validation & parsing typing around array of strings * Adds NotOneOf validation * Adds ArrayOfString read as * Refactors existing validation * Adds validation throughout cli * Removes unused file * Adds new CL parser with goal of wrapping CLI behaviour * Hides parsing and validation * Rewites CL parser to make better use of classes * Rolls out new parser across CL * Fixes error during unknown option check * Fixes misnamed property calls * Seperates validations into more appropriate locations * Adds common boolean forms to validation * Moves CommandLineParser and Option classes into their own files * Fixes error when validating Int type * Rewrites appendTypedValues -> appendTypedValidValues now filters invalid values from output * Renames -> for clarity * Adds some docs clarifying option defaults and value taking behaviour * Refactors getUsageMessage for readability * Minor formatting changes * Adds tests for CommandLineParser * Adds more tests * Adds minor fixs * Reconfigure now correctly updates config * More fixes to reconfigure * Fixes required files for CommandLineParserTest * Use .php extension for PHP file * PHPStan ignore instead of wrong typing * Refactors to support php 7.4 * Moves away from dynamic properties by adding 'Definintions' to all commands * Renames target to definition for clarity * Stops null from being returned as a valid value in a certain edge case * Adds PHPStan ignore instead of incorrect typing * Refactors tests to take account of new typing solution * Marks file as executable * Draft CLI rework * Finish rewrite as object-oriented * Fix PHPStan ignore and make more strongly typed * Rename class Option to CliOption * Light renaming + anonymous classes --------- Co-authored-by: Alexandre Alapetite <alexandre@alapetite.fr>
This commit is contained in:
parent
5de794ee0f
commit
4b29e666b0
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
final class CliOption {
|
||||
public const VALUE_NONE = 'none';
|
||||
public const VALUE_REQUIRED = 'required';
|
||||
public const VALUE_OPTIONAL = 'optional';
|
||||
|
||||
private string $longAlias;
|
||||
private ?string $shortAlias;
|
||||
private string $valueTaken = self::VALUE_REQUIRED;
|
||||
/** @var array{type:string,isArray:bool} $types */
|
||||
private array $types = ['type' => 'string', 'isArray' => false];
|
||||
private string $optionalValueDefault = '';
|
||||
private ?string $deprecatedAlias = null;
|
||||
|
||||
public function __construct(string $longAlias, ?string $shortAlias = null) {
|
||||
$this->longAlias = $longAlias;
|
||||
$this->shortAlias = $shortAlias;
|
||||
}
|
||||
|
||||
/** Sets this option to be treated as a flag. */
|
||||
public function withValueNone(): self {
|
||||
$this->valueTaken = static::VALUE_NONE;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** Sets this option to always require a value when used. */
|
||||
public function withValueRequired(): self {
|
||||
$this->valueTaken = static::VALUE_REQUIRED;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets this option to accept both values and flag behavior.
|
||||
* @param string $optionalValueDefault When this option is used as a flag it receives this value as input.
|
||||
*/
|
||||
public function withValueOptional(string $optionalValueDefault = ''): self {
|
||||
$this->valueTaken = static::VALUE_OPTIONAL;
|
||||
$this->optionalValueDefault = $optionalValueDefault;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function typeOfString(): self {
|
||||
$this->types = ['type' => 'string', 'isArray' => false];
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function typeOfInt(): self {
|
||||
$this->types = ['type' => 'int', 'isArray' => false];
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function typeOfBool(): self {
|
||||
$this->types = ['type' => 'bool', 'isArray' => false];
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function typeOfArrayOfString(): self {
|
||||
$this->types = ['type' => 'string', 'isArray' => true];
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function deprecatedAs(string $deprecated): self {
|
||||
$this->deprecatedAlias = $deprecated;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getValueTaken(): string {
|
||||
return $this->valueTaken;
|
||||
}
|
||||
|
||||
public function getOptionalValueDefault(): string {
|
||||
return $this->optionalValueDefault;
|
||||
}
|
||||
|
||||
public function getDeprecatedAlias(): ?string {
|
||||
return $this->deprecatedAlias;
|
||||
}
|
||||
|
||||
public function getLongAlias(): string {
|
||||
return $this->longAlias;
|
||||
}
|
||||
|
||||
public function getShortAlias(): ?string {
|
||||
return $this->shortAlias;
|
||||
}
|
||||
|
||||
/** @return array{type:string,isArray:bool} */
|
||||
public function getTypes(): array {
|
||||
return $this->types;
|
||||
}
|
||||
|
||||
/** @return string[] */
|
||||
public function getAliases(): array {
|
||||
$aliases = [
|
||||
$this->longAlias,
|
||||
$this->shortAlias,
|
||||
$this->deprecatedAlias,
|
||||
];
|
||||
|
||||
return array_filter($aliases);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
abstract class CliOptionsParser {
|
||||
/** @var array<string,CliOption> */
|
||||
private array $options = [];
|
||||
/** @var array<string,array{defaultInput:?string[],required:?bool,aliasUsed:?string,values:?string[]}> */
|
||||
private array $inputs = [];
|
||||
/** @var array<string,string> $errors */
|
||||
public array $errors = [];
|
||||
public string $usage = '';
|
||||
|
||||
public function __construct() {
|
||||
global $argv;
|
||||
|
||||
$this->usage = $this->getUsageMessage($argv[0]);
|
||||
|
||||
$this->parseInput();
|
||||
$this->appendUnknownAliases($argv);
|
||||
$this->appendInvalidValues();
|
||||
$this->appendTypedValidValues();
|
||||
}
|
||||
|
||||
private function parseInput(): void {
|
||||
$getoptInputs = $this->getGetoptInputs();
|
||||
$this->getoptOutputTransformer(getopt($getoptInputs['short'], $getoptInputs['long']));
|
||||
$this->checkForDeprecatedAliasUse();
|
||||
}
|
||||
|
||||
/** Adds an option that produces an error message if not set. */
|
||||
protected function addRequiredOption(string $name, CliOption $option): void {
|
||||
$this->inputs[$name] = [
|
||||
'defaultInput' => null,
|
||||
'required' => true,
|
||||
'aliasUsed' => null,
|
||||
'values' => null,
|
||||
];
|
||||
$this->options[$name] = $option;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an optional option.
|
||||
* @param string $defaultInput If not null this value is received as input in all cases where no
|
||||
* user input is present. e.g. set this if you want an option to always return a value.
|
||||
*/
|
||||
protected function addOption(string $name, CliOption $option, string $defaultInput = null): void {
|
||||
$this->inputs[$name] = [
|
||||
'defaultInput' => is_string($defaultInput) ? [$defaultInput] : $defaultInput,
|
||||
'required' => null,
|
||||
'aliasUsed' => null,
|
||||
'values' => null,
|
||||
];
|
||||
$this->options[$name] = $option;
|
||||
}
|
||||
|
||||
private function appendInvalidValues(): void {
|
||||
foreach ($this->options as $name => $option) {
|
||||
if ($this->inputs[$name]['required'] && $this->inputs[$name]['values'] === null) {
|
||||
$this->errors[$name] = 'invalid input: ' . $option->getLongAlias() . ' cannot be empty';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->inputs as $name => $input) {
|
||||
foreach ($input['values'] ?? $input['defaultInput'] ?? [] as $value) {
|
||||
switch ($this->options[$name]->getTypes()['type']) {
|
||||
case 'int':
|
||||
if (!ctype_digit($value)) {
|
||||
$this->errors[$name] = 'invalid input: ' . $input['aliasUsed'] . ' must be an integer';
|
||||
}
|
||||
break;
|
||||
case 'bool':
|
||||
if (filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) === null) {
|
||||
$this->errors[$name] = 'invalid input: ' . $input['aliasUsed'] . ' must be a boolean';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function appendTypedValidValues(): void {
|
||||
foreach ($this->inputs as $name => $input) {
|
||||
$values = $input['values'] ?? $input['defaultInput'] ?? null;
|
||||
$types = $this->options[$name]->getTypes();
|
||||
if ($values) {
|
||||
$validValues = [];
|
||||
$typedValues = [];
|
||||
|
||||
switch ($types['type']) {
|
||||
case 'string':
|
||||
$typedValues = $values;
|
||||
break;
|
||||
case 'int':
|
||||
$validValues = array_filter($values, static fn($value) => ctype_digit($value));
|
||||
$typedValues = array_map(static fn($value) => (int) $value, $validValues);
|
||||
break;
|
||||
case 'bool':
|
||||
$validValues = array_filter($values, static fn($value) => filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) !== null);
|
||||
$typedValues = array_map(static fn($value) => (bool) filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE), $validValues);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!empty($typedValues)) {
|
||||
// @phpstan-ignore-next-line (change to `@phpstan-ignore property.dynamicName` when upgrading to PHPStan 1.11+)
|
||||
$this->$name = $types['isArray'] ? $typedValues : array_pop($typedValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @param array<string,string|false>|false $getoptOutput */
|
||||
private function getoptOutputTransformer($getoptOutput): void {
|
||||
$getoptOutput = is_array($getoptOutput) ? $getoptOutput : [];
|
||||
|
||||
foreach ($getoptOutput as $alias => $value) {
|
||||
foreach ($this->options as $name => $data) {
|
||||
if (in_array($alias, $data->getAliases(), true)) {
|
||||
$this->inputs[$name]['aliasUsed'] = $alias;
|
||||
$this->inputs[$name]['values'] = $value === false
|
||||
? [$data->getOptionalValueDefault()]
|
||||
: (is_array($value)
|
||||
? $value
|
||||
: [$value]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $userInputs
|
||||
* @return array<string>
|
||||
*/
|
||||
private function getAliasesUsed(array $userInputs, string $regex): array {
|
||||
$foundAliases = [];
|
||||
|
||||
foreach ($userInputs as $input) {
|
||||
preg_match($regex, $input, $matches);
|
||||
|
||||
if(!empty($matches['short'])) {
|
||||
$foundAliases = array_merge($foundAliases, str_split($matches['short']));
|
||||
}
|
||||
if(!empty($matches['long'])) {
|
||||
$foundAliases[] = $matches['long'];
|
||||
}
|
||||
}
|
||||
|
||||
return $foundAliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $input List of user command-line inputs.
|
||||
*/
|
||||
private function appendUnknownAliases(array $input): void {
|
||||
$valid = [];
|
||||
foreach ($this->options as $option) {
|
||||
$valid = array_merge($valid, $option->getAliases());
|
||||
}
|
||||
|
||||
$sanitizeInput = $this->getAliasesUsed($input, $this->makeInputRegex());
|
||||
$unknownAliases = array_diff($sanitizeInput, $valid);
|
||||
if (empty($unknownAliases)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($unknownAliases as $unknownAlias) {
|
||||
$this->errors[$unknownAlias] = 'unknown option: ' . $unknownAlias;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for presence of deprecated aliases.
|
||||
* @return bool Returns TRUE and generates a deprecation warning if deprecated aliases are present, FALSE otherwise.
|
||||
*/
|
||||
private function checkForDeprecatedAliasUse(): bool {
|
||||
$deprecated = [];
|
||||
$replacements = [];
|
||||
|
||||
foreach ($this->inputs as $name => $data) {
|
||||
if ($data['aliasUsed'] !== null && $data['aliasUsed'] === $this->options[$name]->getDeprecatedAlias()) {
|
||||
$deprecated[] = $this->options[$name]->getDeprecatedAlias();
|
||||
$replacements[] = $this->options[$name]->getLongAlias();
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($deprecated)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fwrite(STDERR, "FreshRSS deprecation warning: the CLI option(s): " . implode(', ', $deprecated) .
|
||||
" are deprecated and will be removed in a future release. Use: " . implode(', ', $replacements) .
|
||||
" instead\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @return array{long:array<string>,short:string}*/
|
||||
private function getGetoptInputs(): array {
|
||||
$getoptNotation = [
|
||||
'none' => '',
|
||||
'required' => ':',
|
||||
'optional' => '::',
|
||||
];
|
||||
|
||||
$long = [];
|
||||
$short = '';
|
||||
|
||||
foreach ($this->options as $option) {
|
||||
$long[] = $option->getLongAlias() . $getoptNotation[$option->getValueTaken()];
|
||||
$long[] = $option->getDeprecatedAlias() ? $option->getDeprecatedAlias() . $getoptNotation[$option->getValueTaken()] : '';
|
||||
$short .= $option->getShortAlias() ? $option->getShortAlias() . $getoptNotation[$option->getValueTaken()] : '';
|
||||
}
|
||||
|
||||
return [
|
||||
'long' => array_filter($long),
|
||||
'short' => $short
|
||||
];
|
||||
}
|
||||
|
||||
private function getUsageMessage(string $command): string {
|
||||
$required = ['Usage: ' . basename($command)];
|
||||
$optional = [];
|
||||
|
||||
foreach ($this->options as $name => $option) {
|
||||
$shortAlias = $option->getShortAlias() ? '-' . $option->getShortAlias() . ' ' : '';
|
||||
$longAlias = '--' . $option->getLongAlias() . ($option->getValueTaken() === 'required' ? '=<' . strtolower($name) . '>' : '');
|
||||
if ($this->inputs[$name]['required']) {
|
||||
$required[] = $shortAlias . $longAlias;
|
||||
} else {
|
||||
$optional[] = '[' . $shortAlias . $longAlias . ']';
|
||||
}
|
||||
}
|
||||
|
||||
return implode(' ', $required) . ' ' . implode(' ', $optional);
|
||||
}
|
||||
|
||||
private function makeInputRegex() : string {
|
||||
$shortWithValues = '';
|
||||
foreach ($this->options as $option) {
|
||||
if (($option->getValueTaken() === 'required' || $option->getValueTaken() === 'optional') && $option->getShortAlias()) {
|
||||
$shortWithValues .= $option->getShortAlias();
|
||||
}
|
||||
}
|
||||
|
||||
return $shortWithValues === ''
|
||||
? "/^--(?'long'[^=]+)|^-(?<short>\w+)/"
|
||||
: "/^--(?'long'[^=]+)|^-(?<short>(?(?=\w*[$shortWithValues])[^$shortWithValues]*[$shortWithValues]|\w+))/";
|
||||
}
|
||||
}
|
|
@ -6,11 +6,12 @@ if (php_sapi_name() !== 'cli') {
|
|||
}
|
||||
|
||||
const EXIT_CODE_ALREADY_EXISTS = 3;
|
||||
const REGEX_INPUT_OPTIONS = '/^-{2}|^-{1}/';
|
||||
|
||||
require(__DIR__ . '/../constants.php');
|
||||
require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader
|
||||
require(LIB_PATH . '/lib_install.php');
|
||||
require_once(__DIR__ . '/CliOption.php');
|
||||
require_once(__DIR__ . '/CliOptionsParser.php');
|
||||
|
||||
Minz_Session::init('FreshRSS', true);
|
||||
FreshRSS_Context::initSystem();
|
||||
|
@ -73,119 +74,3 @@ function performRequirementCheck(string $databaseType): void {
|
|||
fail($message);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses parameters used with FreshRSS' CLI commands.
|
||||
* @param array{'long':array<string,string>,'short':array<string,string>,'deprecated':array<string,string>} $parameters
|
||||
* Matrix of 'long': map of long option names as keys and their respective getopt() notations as values,
|
||||
* 'short': map of short option names as values and their equivalent long options as keys, 'deprecated': map of
|
||||
* replacement option names as keys and their respective deprecated option names as values.
|
||||
* @return array{'valid':array<string,string>,'invalid':array<string>} Matrix of 'valid': map of of all known
|
||||
* option names used and their respective values and 'invalid': list of all unknown options used.
|
||||
*/
|
||||
function parseCliParams(array $parameters): array {
|
||||
global $argv;
|
||||
$longOptions = [];
|
||||
$shortOptions = '';
|
||||
|
||||
foreach ($parameters['long'] as $name => $getopt_note) {
|
||||
$longOptions[] = $name . $getopt_note;
|
||||
}
|
||||
foreach ($parameters['deprecated'] as $name => $deprecatedName) {
|
||||
$longOptions[] = $deprecatedName . $parameters['long'][$name];
|
||||
}
|
||||
foreach ($parameters['short'] as $name => $shortName) {
|
||||
$shortOptions .= $shortName . $parameters['long'][$name];
|
||||
}
|
||||
|
||||
$options = getopt($shortOptions, $longOptions);
|
||||
|
||||
$valid = is_array($options) ? $options : [];
|
||||
|
||||
array_walk($valid, static fn(&$option) => $option = $option === false ? '' : $option);
|
||||
|
||||
/** @var array<string,string> $valid */
|
||||
checkForDeprecatedOptions(array_keys($valid), $parameters['deprecated']);
|
||||
|
||||
$valid = replaceOptions($valid, $parameters['short']);
|
||||
$valid = replaceOptions($valid, $parameters['deprecated']);
|
||||
|
||||
$invalid = findInvalidOptions(
|
||||
$argv,
|
||||
array_merge(array_keys($parameters['long']), array_values($parameters['short']), array_values($parameters['deprecated']))
|
||||
);
|
||||
|
||||
return [
|
||||
'valid' => $valid,
|
||||
'invalid' => $invalid
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $options
|
||||
* @return array<string>
|
||||
*/
|
||||
function getOptions(array $options, string $regex): array {
|
||||
$longOptions = array_filter($options, static function (string $a) use ($regex) {
|
||||
return preg_match($regex, $a) === 1;
|
||||
});
|
||||
return array_map(static function (string $a) use ($regex) {
|
||||
return preg_replace($regex, '', $a) ?? '';
|
||||
}, $longOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for presence of unknown options.
|
||||
* @param array<string> $input List of command line arguments to check for validity.
|
||||
* @param array<string> $params List of valid options to check against.
|
||||
* @return array<string> Returns a list all unknown options found.
|
||||
*/
|
||||
function findInvalidOptions(array $input, array $params): array {
|
||||
$sanitizeInput = getOptions($input, REGEX_INPUT_OPTIONS);
|
||||
$unknownOptions = array_diff($sanitizeInput, $params);
|
||||
|
||||
if (0 === count($unknownOptions)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
fwrite(STDERR, sprintf("FreshRSS error: unknown options: %s\n", implode (', ', $unknownOptions)));
|
||||
return $unknownOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for presence of deprecated options.
|
||||
* @param array<string> $optionNames Command line option names to check for deprecation.
|
||||
* @param array<string,string> $params Map of replacement options as keys and their respective deprecated
|
||||
* options as values.
|
||||
* @return bool Returns TRUE and generates a deprecation warning if deprecated options are present, FALSE otherwise.
|
||||
*/
|
||||
function checkForDeprecatedOptions(array $optionNames, array $params): bool {
|
||||
$deprecatedOptions = array_intersect($optionNames, $params);
|
||||
$replacements = array_map(static fn($option) => array_search($option, $params, true), $deprecatedOptions);
|
||||
|
||||
if (0 === count($deprecatedOptions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fwrite(STDERR, "FreshRSS deprecation warning: the CLI option(s): " . implode(', ', $deprecatedOptions) .
|
||||
" are deprecated and will be removed in a future release. Use: "
|
||||
. implode(', ', $replacements) . " instead\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches items in a list to their provided replacements.
|
||||
* @param array<string,string> $options Map with items to check for replacement as keys.
|
||||
* @param array<string,string> $replacements Map of replacement items as keys and the item they replace as their values.
|
||||
* @return array<string,string> Returns $options with replacements.
|
||||
*/
|
||||
function replaceOptions(array $options, array $replacements): array {
|
||||
$updatedOptions = [];
|
||||
|
||||
foreach ($options as $name => $value) {
|
||||
$replacement = array_search($name, $replacements, true);
|
||||
$updatedOptions[$replacement ? $replacement : $name] = $value;
|
||||
}
|
||||
|
||||
return $updatedOptions;
|
||||
}
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
require(__DIR__ . '/_cli.php');
|
||||
|
||||
performRequirementCheck(FreshRSS_Context::systemConf()->db['type'] ?? '');
|
||||
|
||||
$parameters = [
|
||||
'long' => [
|
||||
'user' => ':',
|
||||
'password' => ':',
|
||||
'api-password' => ':',
|
||||
'language' => ':',
|
||||
'email' => ':',
|
||||
'token' => ':',
|
||||
'purge-after-months' => ':',
|
||||
'feed-min-articles-default' => ':',
|
||||
'feed-ttl-default' => ':',
|
||||
'since-hours-posts-per-rss' => ':',
|
||||
'max-posts-per-rss' => ':',
|
||||
],
|
||||
'short' => [],
|
||||
'deprecated' => [
|
||||
'api-password' => 'api_password',
|
||||
'purge-after-months' => 'purge_after_months',
|
||||
'feed-min-articles-default' => 'feed_min_articles_default',
|
||||
'feed-ttl-default' => 'feed_ttl_default',
|
||||
'since-hours-posts-per-rss' => 'since_hours_posts_per_rss',
|
||||
'max-posts-per-rss' => 'max_posts_per_rss',
|
||||
],
|
||||
];
|
||||
|
||||
if (!isset($isUpdate)) {
|
||||
$isUpdate = false;
|
||||
} elseif (!$isUpdate) {
|
||||
$parameters['long']['no-default-feeds'] = ''; //Only for creating new users
|
||||
$parameters['deprecated']['no-default-feeds'] = 'no_default_feeds';
|
||||
}
|
||||
|
||||
$GLOBALS['options'] = parseCliParams($parameters);
|
||||
|
||||
if (!empty($options['invalid']) || empty($options['valid']['user'])) {
|
||||
fail('Usage: ' . basename($_SERVER['SCRIPT_FILENAME']) .
|
||||
" --user username ( --password 'password' --api-password 'api_password'" .
|
||||
" --language en --email user@example.net --token 'longRandomString'" .
|
||||
($isUpdate ? '' : ' --no-default-feeds') .
|
||||
" --purge-after-months 3 --feed-min-articles-default 50 --feed-ttl-default 3600" .
|
||||
" --since-hours-posts-per-rss 168 --max-posts-per-rss 400 )");
|
||||
}
|
||||
|
||||
function strParam(string $name): ?string {
|
||||
global $options;
|
||||
return isset($options['valid'][$name]) ? strval($options['valid'][$name]) : null;
|
||||
}
|
||||
|
||||
function intParam(string $name): ?int {
|
||||
global $options;
|
||||
return isset($options['valid'][$name]) && ctype_digit($options['valid'][$name]) ? intval($options['valid'][$name]) : null;
|
||||
}
|
||||
|
||||
$values = array(
|
||||
'language' => strParam('language'),
|
||||
'mail_login' => strParam('email'),
|
||||
'token' => strParam('token'),
|
||||
'old_entries' => intParam('purge-after-months'), //TODO: Update with new mechanism
|
||||
'keep_history_default' => intParam('feed-min-articles-default'), //TODO: Update with new mechanism
|
||||
'ttl_default' => intParam('feed-ttl-default'),
|
||||
'since_hours_posts_per_rss' => intParam('since-hours-posts-per-rss'),
|
||||
'max_posts_per_rss' => intParam('max-posts-per-rss'),
|
||||
);
|
||||
|
||||
$values = array_filter($values);
|
|
@ -5,21 +5,23 @@ require(__DIR__ . '/_cli.php');
|
|||
|
||||
performRequirementCheck(FreshRSS_Context::systemConf()->db['type'] ?? '');
|
||||
|
||||
$parameters = [
|
||||
'long' => [
|
||||
'user' => ':'
|
||||
],
|
||||
'short' => [],
|
||||
'deprecated' => [],
|
||||
];
|
||||
$cliOptions = new class extends CliOptionsParser {
|
||||
public string $user;
|
||||
|
||||
$options = parseCliParams($parameters);
|
||||
public function __construct() {
|
||||
$this->addRequiredOption('user', (new CliOption('user')));
|
||||
parent::__construct();
|
||||
}
|
||||
};
|
||||
|
||||
if (!empty($options['invalid']) || empty($options['valid']['user']) || !is_string($options['valid']['user'])) {
|
||||
fail('Usage: ' . basename(__FILE__) . " --user username");
|
||||
if (!empty($cliOptions->errors)) {
|
||||
fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage);
|
||||
}
|
||||
|
||||
$username = cliInitUser($options['valid']['user']);
|
||||
$username = cliInitUser($cliOptions->user);
|
||||
|
||||
Minz_ExtensionManager::callHookVoid('freshrss_user_maintenance');
|
||||
|
||||
fwrite(STDERR, 'FreshRSS actualizing user “' . $username . "”…\n");
|
||||
|
||||
$databaseDAO = FreshRSS_Factory::createDatabaseDAO();
|
||||
|
|
|
@ -8,38 +8,39 @@ require_once __DIR__ . '/i18n/I18nFile.php';
|
|||
require_once __DIR__ . '/i18n/I18nUsageValidator.php';
|
||||
require_once __DIR__ . '/../constants.php';
|
||||
|
||||
$i18nFile = new I18nFile();
|
||||
$i18nData = new I18nData($i18nFile->load());
|
||||
$cliOptions = new class extends CliOptionsParser {
|
||||
/** @var array<int,string> $language */
|
||||
public array $language;
|
||||
public string $displayResult;
|
||||
public string $help;
|
||||
public string $displayReport;
|
||||
|
||||
$parameters = [
|
||||
'long' => [
|
||||
'display-result' => '',
|
||||
'help' => '',
|
||||
'language' => ':',
|
||||
'display-report' => '',
|
||||
],
|
||||
'short' => [
|
||||
'display-result' => 'd',
|
||||
'help' => 'h',
|
||||
'language' => 'l',
|
||||
'display-report' => 'r',
|
||||
],
|
||||
'deprecated' => [],
|
||||
];
|
||||
public function __construct() {
|
||||
$this->addOption('language', (new CliOption('language', 'l'))->typeOfArrayOfString());
|
||||
$this->addOption('displayResult', (new CliOption('display-result', 'd'))->withValueNone());
|
||||
$this->addOption('help', (new CliOption('help', 'h'))->withValueNone());
|
||||
$this->addOption('displayReport', (new CliOption('display-report', 'r'))->withValueNone());
|
||||
parent::__construct();
|
||||
}
|
||||
};
|
||||
|
||||
$options = parseCliParams($parameters);
|
||||
|
||||
if (!empty($options['invalid']) || array_key_exists('help', $options['valid'])) {
|
||||
if (!empty($cliOptions->errors)) {
|
||||
fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage);
|
||||
}
|
||||
if (isset($cliOptions->help)) {
|
||||
checkHelp();
|
||||
}
|
||||
|
||||
if (array_key_exists('language', $options['valid'])) {
|
||||
$languages = [$options['valid']['language']];
|
||||
$i18nFile = new I18nFile();
|
||||
$i18nData = new I18nData($i18nFile->load());
|
||||
|
||||
if (isset($cliOptions->language)) {
|
||||
$languages = $cliOptions->language;
|
||||
} else {
|
||||
$languages = $i18nData->getAvailableLanguages();
|
||||
}
|
||||
$displayResults = array_key_exists('display-result', $options['valid']);
|
||||
$displayReport = array_key_exists('display-report', $options['valid']);
|
||||
$displayResults = isset($cliOptions->displayResult);
|
||||
$displayReport = isset($cliOptions->displayReport);
|
||||
|
||||
$isValidated = true;
|
||||
$result = [];
|
||||
|
@ -122,5 +123,5 @@ DESCRIPTION
|
|||
-r, --display-report display completion report.
|
||||
|
||||
HELP;
|
||||
exit;
|
||||
exit();
|
||||
}
|
||||
|
|
|
@ -1,38 +1,93 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require(__DIR__ . '/_cli.php');
|
||||
|
||||
$isUpdate = false;
|
||||
require(__DIR__ . '/_update-or-create-user.php');
|
||||
$cliOptions = new class extends CliOptionsParser {
|
||||
public string $user;
|
||||
public string $password;
|
||||
public string $apiPassword;
|
||||
public string $language;
|
||||
public string $email;
|
||||
public string $token;
|
||||
public int $purgeAfterMonths;
|
||||
public int $feedMinArticles;
|
||||
public int $feedTtl;
|
||||
public int $sinceHoursPostsPerRss;
|
||||
public int $maxPostsPerRss;
|
||||
public bool $noDefaultFeeds;
|
||||
|
||||
$username = $GLOBALS['options']['valid']['user'];
|
||||
if (!FreshRSS_user_Controller::checkUsername($username)) {
|
||||
fail('FreshRSS error: invalid username “' . $username .
|
||||
'”! Must be matching ' . FreshRSS_user_Controller::USERNAME_PATTERN);
|
||||
public function __construct() {
|
||||
$this->addRequiredOption('user', (new CliOption('user')));
|
||||
$this->addOption('password', (new CliOption('password')));
|
||||
$this->addOption('apiPassword', (new CliOption('api-password'))->deprecatedAs('api_password'));
|
||||
$this->addOption('language', (new CliOption('language')));
|
||||
$this->addOption('email', (new CliOption('email')));
|
||||
$this->addOption('token', (new CliOption('token')));
|
||||
$this->addOption(
|
||||
'purgeAfterMonths',
|
||||
(new CliOption('purge-after-months'))->typeOfInt()->deprecatedAs('purge_after_months')
|
||||
);
|
||||
$this->addOption(
|
||||
'feedMinArticles',
|
||||
(new CliOption('feed-min-articles-default'))->typeOfInt()->deprecatedAs('feed_min_articles_default')
|
||||
);
|
||||
$this->addOption(
|
||||
'feedTtl',
|
||||
(new CliOption('feed-ttl-default'))->typeOfInt()->deprecatedAs('feed_ttl_default')
|
||||
);
|
||||
$this->addOption(
|
||||
'sinceHoursPostsPerRss',
|
||||
(new CliOption('since-hours-posts-per-rss'))->typeOfInt()->deprecatedAs('since_hours_posts_per_rss')
|
||||
);
|
||||
$this->addOption(
|
||||
'maxPostsPerRss',
|
||||
(new CliOption('max-posts-per-rss'))->typeOfInt()->deprecatedAs('max_posts_per_rss')
|
||||
);
|
||||
$this->addOption(
|
||||
'noDefaultFeeds',
|
||||
(new CliOption('no-default-feeds'))->withValueNone()->deprecatedAs('no_default_feeds')
|
||||
);
|
||||
parent::__construct();
|
||||
}
|
||||
};
|
||||
|
||||
if (!empty($cliOptions->errors)) {
|
||||
fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage);
|
||||
}
|
||||
|
||||
$usernames = listUsers();
|
||||
if (preg_grep("/^$username$/i", $usernames)) {
|
||||
fail('FreshRSS warning: username already exists “' . $username . '”', EXIT_CODE_ALREADY_EXISTS);
|
||||
}
|
||||
$username = $cliOptions->user;
|
||||
|
||||
echo 'FreshRSS creating user “', $username, "”…\n";
|
||||
|
||||
$values = [
|
||||
'language' => $cliOptions->language ?? null,
|
||||
'mail_login' => $cliOptions->email ?? null,
|
||||
'token' => $cliOptions->token ?? null,
|
||||
'old_entries' => $cliOptions->purgeAfterMonths ?? null,
|
||||
'keep_history_default' => $cliOptions->feedMinArticles ?? null,
|
||||
'ttl_default' => $cliOptions->feedTtl ?? null,
|
||||
'since_hours_posts_per_rss' => $cliOptions->sinceHoursPostsPerRss ?? null,
|
||||
'max_posts_per_rss' => $cliOptions->maxPostsPerRss ?? null,
|
||||
];
|
||||
|
||||
$values = array_filter($values);
|
||||
|
||||
$ok = FreshRSS_user_Controller::createUser(
|
||||
$username,
|
||||
empty($options['valid']['email']) ? '' : $options['valid']['email'],
|
||||
empty($options['valid']['password']) ? '' : $options['valid']['password'],
|
||||
$GLOBALS['values'],
|
||||
!isset($options['valid']['no-default-feeds'])
|
||||
isset($cliOptions->email) ? $cliOptions->email : null,
|
||||
$cliOptions->password ?? '',
|
||||
$values,
|
||||
!isset($cliOptions->noDefaultFeeds)
|
||||
);
|
||||
|
||||
if (!$ok) {
|
||||
fail('FreshRSS could not create user!');
|
||||
}
|
||||
|
||||
if (!empty($options['valid']['api-password'])) {
|
||||
if (isset($cliOptions->apiPassword)) {
|
||||
$username = cliInitUser($username);
|
||||
$error = FreshRSS_api_Controller::updatePassword($options['valid']['api-password']);
|
||||
$error = FreshRSS_api_Controller::updatePassword($cliOptions->apiPassword);
|
||||
if ($error !== false) {
|
||||
fail($error);
|
||||
}
|
||||
|
|
|
@ -5,21 +5,20 @@ require(__DIR__ . '/_cli.php');
|
|||
|
||||
performRequirementCheck(FreshRSS_Context::systemConf()->db['type'] ?? '');
|
||||
|
||||
$parameters = [
|
||||
'long' => [
|
||||
'user' => ':',
|
||||
],
|
||||
'short' => [],
|
||||
'deprecated' => [],
|
||||
];
|
||||
$cliOptions = new class extends CliOptionsParser {
|
||||
public string $user;
|
||||
|
||||
$options = parseCliParams($parameters);
|
||||
public function __construct() {
|
||||
$this->addRequiredOption('user', (new CliOption('user')));
|
||||
parent::__construct();
|
||||
}
|
||||
};
|
||||
|
||||
if (!empty($options['invalid']) || empty($options['valid']['user']) || !is_string($options['valid']['user'])) {
|
||||
fail('Usage: ' . basename(__FILE__) . " --user username");
|
||||
if (!empty($cliOptions->errors)) {
|
||||
fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage);
|
||||
}
|
||||
|
||||
$username = cliInitUser($options['valid']['user']);
|
||||
$username = cliInitUser($cliOptions->user);
|
||||
|
||||
echo 'FreshRSS optimizing database for user “', $username, "”…\n";
|
||||
|
||||
|
|
|
@ -5,29 +5,27 @@ require(__DIR__ . '/_cli.php');
|
|||
|
||||
performRequirementCheck(FreshRSS_Context::systemConf()->db['type'] ?? '');
|
||||
|
||||
$parameters = [
|
||||
'long' => [
|
||||
'user' => ':',
|
||||
],
|
||||
'short' => [],
|
||||
'deprecated' => [],
|
||||
];
|
||||
$cliOptions = new class extends CliOptionsParser {
|
||||
public string $user;
|
||||
|
||||
$options = parseCliParams($parameters);
|
||||
public function __construct() {
|
||||
$this->addRequiredOption('user', (new CliOption('user')));
|
||||
parent::__construct();
|
||||
}
|
||||
};
|
||||
|
||||
if (!empty($options['invalid']) || empty($options['valid']['user']) || !is_string($options['valid']['user'])) {
|
||||
fail('Usage: ' . basename(__FILE__) . " --user username");
|
||||
if (!empty($cliOptions->errors)) {
|
||||
fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage);
|
||||
}
|
||||
$username = $options['valid']['user'];
|
||||
|
||||
$username = $cliOptions->user;
|
||||
|
||||
if (!FreshRSS_user_Controller::checkUsername($username)) {
|
||||
fail('FreshRSS error: invalid username “' . $username . '”');
|
||||
fail('FreshRSS error: invalid username: ' . $username . "\n");
|
||||
}
|
||||
|
||||
$usernames = listUsers();
|
||||
if (!preg_grep("/^$username$/i", $usernames)) {
|
||||
fail('FreshRSS error: username not found “' . $username . '”');
|
||||
if (!FreshRSS_user_Controller::userExists($username)) {
|
||||
fail('FreshRSS error: user not found: ' . $username . "\n");
|
||||
}
|
||||
|
||||
if (strcasecmp($username, FreshRSS_Context::systemConf()->default_user) === 0) {
|
||||
fail('FreshRSS error: default user must not be deleted: “' . $username . '”');
|
||||
}
|
||||
|
|
|
@ -7,74 +7,91 @@ if (file_exists(DATA_PATH . '/applied_migrations.txt')) {
|
|||
fail('FreshRSS seems to be already installed!' . "\n" . 'Please use `./cli/reconfigure.php` instead.', EXIT_CODE_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
$parameters = [
|
||||
'long' => [
|
||||
'environment' => ':',
|
||||
'base-url' => ':',
|
||||
'language' => ':',
|
||||
'title' => ':',
|
||||
'default-user' => ':',
|
||||
'allow-anonymous' => '',
|
||||
'allow-anonymous-refresh' => '',
|
||||
'auth-type' => ':',
|
||||
'api-enabled' => '',
|
||||
'allow-robots' => '',
|
||||
'disable-update' => '',
|
||||
'db-type' => ':',
|
||||
'db-host' => ':',
|
||||
'db-user' => ':',
|
||||
'db-password' => ':',
|
||||
'db-base' => ':',
|
||||
'db-prefix' => '::',
|
||||
],
|
||||
'short' => [],
|
||||
'deprecated' => [
|
||||
'base-url' => 'base_url',
|
||||
'default-user' => 'default_user',
|
||||
'allow-anonymous' => 'allow_anonymous',
|
||||
'allow-anonymous-refresh' => 'allow_anonymous_refresh',
|
||||
'auth-type' => 'auth_type',
|
||||
'api-enabled' => 'api_enabled',
|
||||
'allow-robots' => 'allow_robots',
|
||||
'disable-update' => 'disable_update',
|
||||
],
|
||||
];
|
||||
$cliOptions = new class extends CliOptionsParser {
|
||||
public string $defaultUser;
|
||||
public string $environment;
|
||||
public string $baseUrl;
|
||||
public string $language;
|
||||
public string $title;
|
||||
public bool $allowAnonymous;
|
||||
public bool $allowAnonymousRefresh;
|
||||
public string $authType;
|
||||
public bool $apiEnabled;
|
||||
public bool $allowRobots;
|
||||
public bool $disableUpdate;
|
||||
public string $dbType;
|
||||
public string $dbHost;
|
||||
public string $dbUser;
|
||||
public string $dbPassword;
|
||||
public string $dbBase;
|
||||
public string $dbPrefix;
|
||||
|
||||
$configParams = [
|
||||
'environment' => 'environment',
|
||||
'base-url' => 'base_url',
|
||||
'language' => 'language',
|
||||
'title' => 'title',
|
||||
'default-user' => 'default_user',
|
||||
'allow-anonymous' => 'allow_anonymous',
|
||||
'allow-anonymous-refresh' => 'allow_anonymous_refresh',
|
||||
'auth-type' => 'auth_type',
|
||||
'api-enabled' => 'api_enabled',
|
||||
'allow-robots' => 'allow_robots',
|
||||
'disable-update' => 'disable_update',
|
||||
];
|
||||
public function __construct() {
|
||||
$this->addRequiredOption('defaultUser', (new CliOption('default-user'))->deprecatedAs('default_user'));
|
||||
$this->addOption('environment', (new CliOption('environment')));
|
||||
$this->addOption('baseUrl', (new CliOption('base-url'))->deprecatedAs('base_url'));
|
||||
$this->addOption('language', (new CliOption('language')));
|
||||
$this->addOption('title', (new CliOption('title')));
|
||||
$this->addOption(
|
||||
'allowAnonymous',
|
||||
(new CliOption('allow-anonymous'))->withValueOptional('true')->deprecatedAs('allow_anonymous')->typeOfBool()
|
||||
);
|
||||
$this->addOption(
|
||||
'allowAnonymousRefresh',
|
||||
(new CliOption('allow-anonymous-refresh'))->withValueOptional('true')->deprecatedAs('allow_anonymous_refresh')->typeOfBool()
|
||||
);
|
||||
$this->addOption('authType', (new CliOption('auth-type'))->deprecatedAs('auth_type'));
|
||||
$this->addOption(
|
||||
'apiEnabled',
|
||||
(new CliOption('api-enabled'))->withValueOptional('true')->deprecatedAs('api_enabled')->typeOfBool()
|
||||
);
|
||||
$this->addOption(
|
||||
'allowRobots',
|
||||
(new CliOption('allow-robots'))->withValueOptional('true')->deprecatedAs('allow_robots')->typeOfBool()
|
||||
);
|
||||
$this->addOption(
|
||||
'disableUpdate',
|
||||
(new CliOption('disable-update'))->withValueOptional('true')->deprecatedAs('disable_update')->typeOfBool()
|
||||
);
|
||||
$this->addOption('dbType', (new CliOption('db-type')));
|
||||
$this->addOption('dbHost', (new CliOption('db-host')));
|
||||
$this->addOption('dbUser', (new CliOption('db-user')));
|
||||
$this->addOption('dbPassword', (new CliOption('db-password')));
|
||||
$this->addOption('dbBase', (new CliOption('db-base')));
|
||||
$this->addOption('dbPrefix', (new CliOption('db-prefix'))->withValueOptional());
|
||||
parent::__construct();
|
||||
}
|
||||
};
|
||||
|
||||
$dBconfigParams = [
|
||||
'db-type' => 'type',
|
||||
'db-host' => 'host',
|
||||
'db-user' => 'user',
|
||||
'db-password' => 'password',
|
||||
'db-base' => 'base',
|
||||
'db-prefix' => 'prefix',
|
||||
];
|
||||
|
||||
$options = parseCliParams($parameters);
|
||||
|
||||
if (!empty($options['invalid']) || empty($options['valid']['default-user']) || !is_string($options['valid']['default-user'])) {
|
||||
fail('Usage: ' . basename(__FILE__) . " --default-user admin ( --auth-type form" .
|
||||
" --environment production --base-url https://rss.example.net --allow-robots" .
|
||||
" --language en --title FreshRSS --allow-anonymous --allow-anonymous-refresh --api-enabled" .
|
||||
" --db-type mysql --db-host localhost:3306 --db-user freshrss --db-password dbPassword123" .
|
||||
" --db-base freshrss --db-prefix freshrss_ --disable-update )");
|
||||
if (!empty($cliOptions->errors)) {
|
||||
fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage);
|
||||
}
|
||||
|
||||
fwrite(STDERR, 'FreshRSS install…' . "\n");
|
||||
|
||||
$values = [
|
||||
'default_user' => $cliOptions->defaultUser ?? null,
|
||||
'environment' => $cliOptions->environment ?? null,
|
||||
'base_url' => $cliOptions->baseUrl ?? null,
|
||||
'language' => $cliOptions->language ?? null,
|
||||
'title' => $cliOptions->title ?? null,
|
||||
'allow_anonymous' => $cliOptions->allowAnonymous ?? null,
|
||||
'allow_anonymous_refresh' => $cliOptions->allowAnonymousRefresh ?? null,
|
||||
'auth_type' => $cliOptions->authType ?? null,
|
||||
'api_enabled' => $cliOptions->apiEnabled ?? null,
|
||||
'allow_robots' => $cliOptions->allowRobots ?? null,
|
||||
'disable_update' => $cliOptions->disableUpdate ?? null,
|
||||
];
|
||||
|
||||
$dbValues = [
|
||||
'type' => $cliOptions->dbType ?? null,
|
||||
'host' => $cliOptions->dbHost ?? null,
|
||||
'user' => $cliOptions->dbUser ?? null,
|
||||
'password' => $cliOptions->dbPassword ?? null,
|
||||
'base' => $cliOptions->dbBase ?? null,
|
||||
'prefix' => $cliOptions->dbPrefix ?? null,
|
||||
];
|
||||
|
||||
$config = array(
|
||||
'salt' => generateSalt(),
|
||||
'db' => FreshRSS_Context::systemConf()->db,
|
||||
|
@ -88,10 +105,26 @@ if (file_exists($customConfigPath)) {
|
|||
}
|
||||
}
|
||||
|
||||
foreach ($configParams as $param => $configParam) {
|
||||
if (isset($options['valid'][$param])) {
|
||||
$isFlag = $parameters['long'][$param] === '';
|
||||
$config[$configParam] = $isFlag ? true : $options['valid'][$param];
|
||||
foreach ($values as $name => $value) {
|
||||
if ($value !== null) {
|
||||
switch ($name) {
|
||||
case 'default_user':
|
||||
if (!FreshRSS_user_Controller::checkUsername($value)) {
|
||||
fail('FreshRSS invalid default username! default_user must be ASCII alphanumeric');
|
||||
}
|
||||
break;
|
||||
case 'environment':
|
||||
if (!in_array($value, ['development', 'production', 'silent'], true)) {
|
||||
fail('FreshRSS invalid environment! environment must be one of { development, production, silent }');
|
||||
}
|
||||
break;
|
||||
case 'auth_type':
|
||||
if (!in_array($value, ['form', 'http_auth', 'none'], true)) {
|
||||
fail('FreshRSS invalid authentication method! auth_type must be one of { form, http_auth, none }');
|
||||
}
|
||||
break;
|
||||
}
|
||||
$config[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -99,23 +132,10 @@ if ((!empty($config['base_url'])) && is_string($config['base_url']) && Minz_Requ
|
|||
$config['pubsubhubbub_enabled'] = true;
|
||||
}
|
||||
|
||||
foreach ($dBconfigParams as $dBparam => $configDbParam) {
|
||||
if (isset($options['valid'][$dBparam])) {
|
||||
$config['db'][$configDbParam] = $options['valid'][$dBparam];
|
||||
}
|
||||
}
|
||||
$config['db'] = array_merge($config['db'], array_filter($dbValues));
|
||||
|
||||
performRequirementCheck($config['db']['type']);
|
||||
|
||||
if (!FreshRSS_user_Controller::checkUsername($options['valid']['default-user'])) {
|
||||
fail('FreshRSS error: invalid default username “' . $options['valid']['default-user']
|
||||
. '”! Must be matching ' . FreshRSS_user_Controller::USERNAME_PATTERN);
|
||||
}
|
||||
|
||||
if (isset($options['valid']['auth-type']) && !in_array($options['valid']['auth-type'], ['form', 'http_auth', 'none'], true)) {
|
||||
fail('FreshRSS invalid authentication method (auth-type must be one of { form, http_auth, none })');
|
||||
}
|
||||
|
||||
if (file_put_contents(join_path(DATA_PATH, 'config.php'),
|
||||
"<?php\n return " . var_export($config, true) . ";\n") === false) {
|
||||
fail('FreshRSS could not write configuration file!: ' . join_path(DATA_PATH, 'config.php'));
|
||||
|
|
|
@ -5,21 +5,20 @@ require(__DIR__ . '/_cli.php');
|
|||
|
||||
performRequirementCheck(FreshRSS_Context::systemConf()->db['type'] ?? '');
|
||||
|
||||
$parameters = [
|
||||
'long' => [
|
||||
'user' => ':',
|
||||
],
|
||||
'short' => [],
|
||||
'deprecated' => [],
|
||||
];
|
||||
$cliOptions = new class extends CliOptionsParser {
|
||||
public string $user;
|
||||
|
||||
$options = parseCliParams($parameters);
|
||||
public function __construct() {
|
||||
$this->addRequiredOption('user', (new CliOption('user')));
|
||||
parent::__construct();
|
||||
}
|
||||
};
|
||||
|
||||
if (!empty($options['invalid']) || empty($options['valid']['user']) || !is_string($options['valid']['user'])) {
|
||||
fail('Usage: ' . basename(__FILE__) . " --user username > /path/to/file.opml.xml");
|
||||
if (!empty($cliOptions->errors)) {
|
||||
fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage);
|
||||
}
|
||||
|
||||
$username = cliInitUser($options['valid']['user']);
|
||||
$username = cliInitUser($cliOptions->user);
|
||||
|
||||
fwrite(STDERR, 'FreshRSS exporting OPML for user “' . $username . "”…\n");
|
||||
|
||||
|
|
|
@ -5,26 +5,23 @@ require(__DIR__ . '/_cli.php');
|
|||
|
||||
performRequirementCheck(FreshRSS_Context::systemConf()->db['type'] ?? '');
|
||||
|
||||
$parameters = [
|
||||
'long' => [
|
||||
'user' => ':',
|
||||
'filename' => ':',
|
||||
],
|
||||
'short' => [],
|
||||
'deprecated' => [],
|
||||
];
|
||||
$cliOptions = new class extends CliOptionsParser {
|
||||
public string $user;
|
||||
public string $filename;
|
||||
|
||||
$options = parseCliParams($parameters);
|
||||
public function __construct() {
|
||||
$this->addRequiredOption('user', (new CliOption('user')));
|
||||
$this->addRequiredOption('filename', (new CliOption('filename')));
|
||||
parent::__construct();
|
||||
}
|
||||
};
|
||||
|
||||
if (!empty($options['invalid'])
|
||||
|| empty($options['valid']['user']) || empty($options['valid']['filename'])
|
||||
|| !is_string($options['valid']['user']) || !is_string($options['valid']['filename'])
|
||||
) {
|
||||
fail('Usage: ' . basename(__FILE__) . ' --user username --filename /path/to/db.sqlite');
|
||||
if (!empty($cliOptions->errors)) {
|
||||
fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage);
|
||||
}
|
||||
|
||||
$username = cliInitUser($options['valid']['user']);
|
||||
$filename = $options['valid']['filename'];
|
||||
$username = cliInitUser($cliOptions->user);
|
||||
$filename = $cliOptions->filename;
|
||||
|
||||
if (pathinfo($filename, PATHINFO_EXTENSION) !== 'sqlite') {
|
||||
fail('Only *.sqlite files are supported!');
|
||||
|
|
|
@ -5,31 +5,31 @@ require(__DIR__ . '/_cli.php');
|
|||
|
||||
performRequirementCheck(FreshRSS_Context::systemConf()->db['type'] ?? '');
|
||||
|
||||
$parameters = [
|
||||
'long' => [
|
||||
'user' => ':',
|
||||
'max-feed-entries' => ':',
|
||||
],
|
||||
'short' => [],
|
||||
'deprecated' => [],
|
||||
];
|
||||
$cliOptions = new class extends CliOptionsParser {
|
||||
public string $user;
|
||||
public int $maxFeedEntries;
|
||||
|
||||
$options = parseCliParams($parameters);
|
||||
public function __construct() {
|
||||
$this->addRequiredOption('user', (new CliOption('user')));
|
||||
$this->addOption('maxFeedEntries', (new CliOption('max-feed-entries'))->typeOfInt(), '100');
|
||||
parent::__construct();
|
||||
}
|
||||
};
|
||||
|
||||
if (!empty($options['invalid']) || empty($options['valid']['user']) || !is_string($options['valid']['user'])) {
|
||||
fail('Usage: ' . basename(__FILE__) . " --user username ( --max-feed-entries 100 ) > /path/to/file.zip");
|
||||
if (!empty($cliOptions->errors)) {
|
||||
fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage);
|
||||
}
|
||||
|
||||
if (!extension_loaded('zip')) {
|
||||
fail('FreshRSS error: Lacking php-zip extension!');
|
||||
}
|
||||
|
||||
$username = cliInitUser($options['valid']['user']);
|
||||
$username = cliInitUser($cliOptions->user);
|
||||
|
||||
fwrite(STDERR, 'FreshRSS exporting ZIP for user “' . $username . "”…\n");
|
||||
|
||||
$export_service = new FreshRSS_Export_Service($username);
|
||||
$number_entries = empty($options['valid']['max-feed-entries']) ? 100 : intval($options['valid']['max-feed-entries']);
|
||||
$number_entries = $cliOptions->maxFeedEntries;
|
||||
$exported_files = [];
|
||||
|
||||
// First, we generate the OPML file
|
||||
|
|
|
@ -5,27 +5,24 @@ require(__DIR__ . '/_cli.php');
|
|||
|
||||
performRequirementCheck(FreshRSS_Context::systemConf()->db['type'] ?? '');
|
||||
|
||||
$parameters = [
|
||||
'long' => [
|
||||
'user' => ':',
|
||||
'filename' => ':',
|
||||
],
|
||||
'short' => [],
|
||||
'deprecated' => [],
|
||||
];
|
||||
$cliOptions = new class extends CliOptionsParser {
|
||||
public string $user;
|
||||
public string $filename;
|
||||
|
||||
$options = parseCliParams($parameters);
|
||||
public function __construct() {
|
||||
$this->addRequiredOption('user', (new CliOption('user')));
|
||||
$this->addRequiredOption('filename', (new CliOption('filename')));
|
||||
parent::__construct();
|
||||
}
|
||||
};
|
||||
|
||||
if (!empty($options['invalid'])
|
||||
|| empty($options['valid']['user']) || empty($options['valid']['filename'])
|
||||
|| !is_string($options['valid']['user']) || !is_string($options['valid']['filename'])
|
||||
) {
|
||||
fail('Usage: ' . basename(__FILE__) . " --user username --filename /path/to/file.ext");
|
||||
if (!empty($cliOptions->errors)) {
|
||||
fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage);
|
||||
}
|
||||
|
||||
$username = cliInitUser($options['valid']['user']);
|
||||
$username = cliInitUser($cliOptions->user);
|
||||
$filename = $cliOptions->filename;
|
||||
|
||||
$filename = $options['valid']['filename'];
|
||||
if (!is_readable($filename)) {
|
||||
fail('FreshRSS error: file is not readable “' . $filename . '”');
|
||||
}
|
||||
|
|
|
@ -5,27 +5,25 @@ require(__DIR__ . '/_cli.php');
|
|||
|
||||
performRequirementCheck(FreshRSS_Context::systemConf()->db['type'] ?? '');
|
||||
|
||||
$parameters = [
|
||||
'long' => [
|
||||
'user' => ':',
|
||||
'filename' => ':',
|
||||
'force-overwrite' => '',
|
||||
],
|
||||
'short' => [],
|
||||
'deprecated' => [],
|
||||
];
|
||||
$cliOptions = new class extends CliOptionsParser {
|
||||
public string $user;
|
||||
public string $filename;
|
||||
public string $forceOverwrite;
|
||||
|
||||
$options = parseCliParams($parameters);
|
||||
public function __construct() {
|
||||
$this->addRequiredOption('user', (new CliOption('user')));
|
||||
$this->addRequiredOption('filename', (new CliOption('filename')));
|
||||
$this->addOption('forceOverwrite', (new CliOption('force-overwrite'))->withValueNone());
|
||||
parent::__construct();
|
||||
}
|
||||
};
|
||||
|
||||
if (!empty($options['invalid'])
|
||||
|| empty($options['valid']['user']) || empty($options['valid']['filename'])
|
||||
|| !is_string($options['valid']['user']) || !is_string($options['valid']['filename'])
|
||||
) {
|
||||
fail('Usage: ' . basename(__FILE__) . ' --user username --force-overwrite --filename /path/to/db.sqlite');
|
||||
if (!empty($cliOptions->errors)) {
|
||||
fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage);
|
||||
}
|
||||
|
||||
$username = cliInitUser($options['valid']['user']);
|
||||
$filename = $options['valid']['filename'];
|
||||
$username = cliInitUser($cliOptions->user);
|
||||
$filename = $cliOptions->filename;
|
||||
|
||||
if (pathinfo($filename, PATHINFO_EXTENSION) !== 'sqlite') {
|
||||
fail('Only *.sqlite files are supported!');
|
||||
|
@ -34,7 +32,7 @@ if (pathinfo($filename, PATHINFO_EXTENSION) !== 'sqlite') {
|
|||
echo 'FreshRSS importing database from SQLite for user “', $username, "”…\n";
|
||||
|
||||
$databaseDAO = FreshRSS_Factory::createDatabaseDAO($username);
|
||||
$clearFirst = array_key_exists('force-overwrite', $options['valid']);
|
||||
$clearFirst = isset($cliOptions->forceOverwrite);
|
||||
$ok = $databaseDAO->dbCopy($filename, FreshRSS_DatabaseDAO::SQLITE_IMPORT, $clearFirst);
|
||||
if (!$ok) {
|
||||
echo 'If you would like to clear the user database first, use the option --force-overwrite', "\n";
|
||||
|
|
|
@ -6,70 +6,65 @@ require_once __DIR__ . '/i18n/I18nData.php';
|
|||
require_once __DIR__ . '/i18n/I18nFile.php';
|
||||
require_once __DIR__ . '/../constants.php';
|
||||
|
||||
$parameters = [
|
||||
'long' => [
|
||||
'action' => ':',
|
||||
'help' => '',
|
||||
'key' => ':',
|
||||
'language' => ':',
|
||||
'origin-language' => ':',
|
||||
'revert' => '',
|
||||
'value' => ':',
|
||||
],
|
||||
'short' => [
|
||||
'action' => 'a',
|
||||
'help' => 'h',
|
||||
'key' => 'k',
|
||||
'language' => 'l',
|
||||
'origin-language' => 'o',
|
||||
'revert' => 'r',
|
||||
'value' => 'v',
|
||||
],
|
||||
'deprecated' => [],
|
||||
];
|
||||
$cliOptions = new class extends CliOptionsParser {
|
||||
public string $action;
|
||||
public string $key;
|
||||
public string $value;
|
||||
public string $language;
|
||||
public string $originLanguage;
|
||||
public string $revert;
|
||||
public string $help;
|
||||
|
||||
$options = parseCliParams($parameters);
|
||||
public function __construct() {
|
||||
$this->addRequiredOption('action', (new CliOption('action', 'a')));
|
||||
$this->addOption('key', (new CliOption('key', 'k')));
|
||||
$this->addOption('value', (new CliOption('value', 'v')));
|
||||
$this->addOption('language', (new CliOption('language', 'l')));
|
||||
$this->addOption('originLanguage', (new CliOption('origin-language', 'o')));
|
||||
$this->addOption('revert', (new CliOption('revert', 'r'))->withValueNone());
|
||||
$this->addOption('help', (new CliOption('help', 'h'))->withValueNone());
|
||||
parent::__construct();
|
||||
}
|
||||
};
|
||||
|
||||
if (!empty($options['invalid']) || array_key_exists('help', $options['valid'])) {
|
||||
manipulateHelp();
|
||||
exit();
|
||||
if (!empty($cliOptions->errors)) {
|
||||
fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage);
|
||||
}
|
||||
|
||||
if (!array_key_exists('action', $options['valid'])) {
|
||||
error('You need to specify the action to perform.');
|
||||
if (isset($cliOptions->help)) {
|
||||
manipulateHelp();
|
||||
}
|
||||
|
||||
$data = new I18nFile();
|
||||
$i18nData = new I18nData($data->load());
|
||||
|
||||
switch ($options['valid']['action']) {
|
||||
switch ($cliOptions->action) {
|
||||
case 'add' :
|
||||
if (array_key_exists('key', $options['valid']) && array_key_exists('value', $options['valid']) && array_key_exists('language', $options['valid'])) {
|
||||
$i18nData->addValue($options['valid']['key'], $options['valid']['value'], $options['valid']['language']);
|
||||
} elseif (array_key_exists('key', $options['valid']) && array_key_exists('value', $options['valid'])) {
|
||||
$i18nData->addKey($options['valid']['key'], $options['valid']['value']);
|
||||
} elseif (array_key_exists('language', $options['valid'])) {
|
||||
if (isset($cliOptions->key) && isset($cliOptions->value) && isset($cliOptions->language)) {
|
||||
$i18nData->addValue($cliOptions->key, $cliOptions->value, $cliOptions->language);
|
||||
} elseif (isset($cliOptions->key) && isset($cliOptions->value)) {
|
||||
$i18nData->addKey($cliOptions->key, $cliOptions->value);
|
||||
} elseif (isset($cliOptions->language)) {
|
||||
$reference = null;
|
||||
if (array_key_exists('origin-language', $options['valid'])) {
|
||||
$reference = $options['valid']['origin-language'];
|
||||
if (isset($cliOptions->originLanguage)) {
|
||||
$reference = $cliOptions->originLanguage;
|
||||
}
|
||||
$i18nData->addLanguage($options['valid']['language'], $reference);
|
||||
$i18nData->addLanguage($cliOptions->language, $reference);
|
||||
} else {
|
||||
error('You need to specify a valid set of options.');
|
||||
exit;
|
||||
}
|
||||
break;
|
||||
case 'delete' :
|
||||
if (array_key_exists('key', $options['valid'])) {
|
||||
$i18nData->removeKey($options['valid']['key']);
|
||||
if (isset($cliOptions->key)) {
|
||||
$i18nData->removeKey($cliOptions->key);
|
||||
} else {
|
||||
error('You need to specify the key to delete.');
|
||||
exit;
|
||||
}
|
||||
break;
|
||||
case 'exist':
|
||||
if (array_key_exists('key', $options['valid'])) {
|
||||
$key = $options['valid']['key'];
|
||||
if (isset($cliOptions->key)) {
|
||||
$key = $cliOptions->key;
|
||||
if ($i18nData->isKnown($key)) {
|
||||
echo "The '{$key}' key is known.\n\n";
|
||||
} else {
|
||||
|
@ -83,16 +78,16 @@ switch ($options['valid']['action']) {
|
|||
case 'format' :
|
||||
break;
|
||||
case 'ignore' :
|
||||
if (array_key_exists('language', $options['valid']) && array_key_exists('key', $options['valid'])) {
|
||||
$i18nData->ignore($options['valid']['key'], $options['valid']['language'], array_key_exists('revert', $options['valid']));
|
||||
if (isset($cliOptions->language) && isset($cliOptions->key)) {
|
||||
$i18nData->ignore($cliOptions->key, $cliOptions->language, isset($cliOptions->revert));
|
||||
} else {
|
||||
error('You need to specify a valid set of options.');
|
||||
exit;
|
||||
}
|
||||
break;
|
||||
case 'ignore_unmodified' :
|
||||
if (array_key_exists('language', $options['valid'])) {
|
||||
$i18nData->ignore_unmodified($options['valid']['language'], array_key_exists('revert', $options['valid']));
|
||||
if (isset($cliOptions->language)) {
|
||||
$i18nData->ignore_unmodified($cliOptions->language, isset($cliOptions->revert));
|
||||
} else {
|
||||
error('You need to specify a valid set of options.');
|
||||
exit;
|
||||
|
@ -122,6 +117,7 @@ ERROR;
|
|||
*/
|
||||
function manipulateHelp(): void {
|
||||
$file = str_replace(__DIR__ . '/', '', __FILE__);
|
||||
|
||||
echo <<<HELP
|
||||
NAME
|
||||
$file
|
||||
|
@ -144,17 +140,17 @@ DESCRIPTION
|
|||
select the origin language (only for add language action)
|
||||
|
||||
EXAMPLES
|
||||
Example 1: add a language. It adds a new language by duplicating the referential.
|
||||
Example 1: add a language. Adds a new language by duplicating the reference language.
|
||||
php $file -a add -l my_lang
|
||||
php $file -a add -l my_lang -o ref_lang
|
||||
|
||||
Example 2: add a new key. It adds the key for all supported languages.
|
||||
Example 2: add a new key. Adds a key to all supported languages.
|
||||
php $file -a add -k my_key -v my_value
|
||||
|
||||
Example 3: add a new value. It adds a new value for the selected key in the selected language.
|
||||
Example 3: add a new value. Sets a new value for the selected key in the selected language.
|
||||
php $file -a add -k my_key -v my_value -l my_lang
|
||||
|
||||
Example 4: delete a key. It deletes the selected key from all supported languages.
|
||||
Example 4: delete a key. Deletes the selected key from all supported languages.
|
||||
php $file -a delete -k my_key
|
||||
|
||||
Example 5: format i18n files.
|
||||
|
@ -170,11 +166,12 @@ Example 8: ignore all unmodified keys. Adds IGNORE comments to all unmodified ke
|
|||
php $file -a ignore_unmodified -l my_lang
|
||||
|
||||
Example 9: revert ignore on all unmodified keys. Removes IGNORE comments from all unmodified keys in the selected language.
|
||||
Warning: will also revert individually added unmodified keys.
|
||||
Warning: will also revert individually added IGNOREs on unmodified keys.
|
||||
php $file -a ignore_unmodified -r -l my_lang
|
||||
|
||||
Example 10: check if a key exist.
|
||||
php $file -a exist -k my_key\n\n
|
||||
php $file -a exist -k my_key
|
||||
|
||||
HELP;
|
||||
exit();
|
||||
}
|
||||
|
|
|
@ -3,133 +3,120 @@
|
|||
declare(strict_types=1);
|
||||
require(__DIR__ . '/_cli.php');
|
||||
|
||||
$parameters = [
|
||||
'long' => [
|
||||
'environment' => ':',
|
||||
'base-url' => ':',
|
||||
'language' => ':',
|
||||
'title' => ':',
|
||||
'default-user' => ':',
|
||||
'allow-anonymous' => '',
|
||||
'allow-anonymous-refresh' => '',
|
||||
'auth-type' => ':',
|
||||
'api-enabled' => '',
|
||||
'allow-robots' => '',
|
||||
'disable-update' => '',
|
||||
'db-type' => ':',
|
||||
'db-host' => ':',
|
||||
'db-user' => ':',
|
||||
'db-password' => ':',
|
||||
'db-base' => ':',
|
||||
'db-prefix' => '::',
|
||||
],
|
||||
'short' => [],
|
||||
'deprecated' => [
|
||||
'base-url' => 'base_url',
|
||||
'default-user' => 'default_user',
|
||||
'allow-anonymous' => 'allow_anonymous',
|
||||
'allow-anonymous-refresh' => 'allow_anonymous_refresh',
|
||||
'auth-type' => 'auth_type',
|
||||
'api-enabled' => 'api_enabled',
|
||||
'allow-robots' => 'allow_robots',
|
||||
'disable-update' => 'disable_update',
|
||||
],
|
||||
];
|
||||
$cliOptions = new class extends CliOptionsParser {
|
||||
public string $defaultUser;
|
||||
public string $environment;
|
||||
public string $baseUrl;
|
||||
public string $language;
|
||||
public string $title;
|
||||
public bool $allowAnonymous;
|
||||
public bool $allowAnonymousRefresh;
|
||||
public string $authType;
|
||||
public bool $apiEnabled;
|
||||
public bool $allowRobots;
|
||||
public bool $disableUpdate;
|
||||
public string $dbType;
|
||||
public string $dbHost;
|
||||
public string $dbUser;
|
||||
public string $dbPassword;
|
||||
public string $dbBase;
|
||||
public string $dbPrefix;
|
||||
|
||||
$configParams = [
|
||||
'environment',
|
||||
'base-url',
|
||||
'language',
|
||||
'title',
|
||||
'default-user',
|
||||
'allow-anonymous',
|
||||
'allow-anonymous-refresh',
|
||||
'auth-type',
|
||||
'api-enabled',
|
||||
'allow-robots',
|
||||
'disable-update',
|
||||
];
|
||||
public function __construct() {
|
||||
$this->addOption('defaultUser', (new CliOption('default-user'))->deprecatedAs('default_user'));
|
||||
$this->addOption('environment', (new CliOption('environment')));
|
||||
$this->addOption('baseUrl', (new CliOption('base-url'))->deprecatedAs('base_url'));
|
||||
$this->addOption('language', (new CliOption('language')));
|
||||
$this->addOption('title', (new CliOption('title')));
|
||||
$this->addOption(
|
||||
'allowAnonymous',
|
||||
(new CliOption('allow-anonymous'))->withValueOptional('true')->deprecatedAs('allow_anonymous')->typeOfBool()
|
||||
);
|
||||
$this->addOption(
|
||||
'allowAnonymousRefresh',
|
||||
(new CliOption('allow-anonymous-refresh'))->withValueOptional('true')->deprecatedAs('allow_anonymous_refresh')->typeOfBool()
|
||||
);
|
||||
$this->addOption('authType', (new CliOption('auth-type'))->deprecatedAs('auth_type'));
|
||||
$this->addOption(
|
||||
'apiEnabled',
|
||||
(new CliOption('api-enabled'))->withValueOptional('true')->deprecatedAs('api_enabled')->typeOfBool()
|
||||
);
|
||||
$this->addOption(
|
||||
'allowRobots',
|
||||
(new CliOption('allow-robots'))->withValueOptional('true')->deprecatedAs('allow_robots')->typeOfBool()
|
||||
);
|
||||
$this->addOption(
|
||||
'disableUpdate',
|
||||
(new CliOption('disable-update'))->withValueOptional('true')->deprecatedAs('disable_update')->typeOfBool()
|
||||
);
|
||||
$this->addOption('dbType', (new CliOption('db-type')));
|
||||
$this->addOption('dbHost', (new CliOption('db-host')));
|
||||
$this->addOption('dbUser', (new CliOption('db-user')));
|
||||
$this->addOption('dbPassword', (new CliOption('db-password')));
|
||||
$this->addOption('dbBase', (new CliOption('db-base')));
|
||||
$this->addOption('dbPrefix', (new CliOption('db-prefix'))->withValueOptional());
|
||||
parent::__construct();
|
||||
}
|
||||
};
|
||||
|
||||
$dBconfigParams = [
|
||||
'db-type' => 'type',
|
||||
'db-host' => 'host',
|
||||
'db-user' => 'user',
|
||||
'db-password' => 'password',
|
||||
'db-base' => 'base',
|
||||
'db-prefix' => 'prefix',
|
||||
];
|
||||
|
||||
$options = parseCliParams($parameters);
|
||||
|
||||
if (!empty($options['invalid'])) {
|
||||
fail('Usage: ' . basename(__FILE__) . " --default-user admin ( --auth-type form" .
|
||||
" --environment production --base-url https://rss.example.net --allow-robots" .
|
||||
" --language en --title FreshRSS --allow-anonymous --allow-anonymous-refresh --api-enabled" .
|
||||
" --db-type mysql --db-host localhost:3306 --db-user freshrss --db-password dbPassword123" .
|
||||
" --db-base freshrss --db-prefix freshrss_ --disable-update )");
|
||||
if (!empty($cliOptions->errors)) {
|
||||
fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage);
|
||||
}
|
||||
|
||||
fwrite(STDERR, 'Reconfiguring FreshRSS…' . "\n");
|
||||
|
||||
foreach ($configParams as $param) {
|
||||
if (isset($options['valid'][$param])) {
|
||||
switch ($param) {
|
||||
case 'allow-anonymous-refresh':
|
||||
FreshRSS_Context::systemConf()->allow_anonymous_refresh = true;
|
||||
break;
|
||||
case 'allow-anonymous':
|
||||
FreshRSS_Context::systemConf()->allow_anonymous = true;
|
||||
break;
|
||||
case 'allow-robots':
|
||||
FreshRSS_Context::systemConf()->allow_robots = true;
|
||||
break;
|
||||
case 'api-enabled':
|
||||
FreshRSS_Context::systemConf()->api_enabled = true;
|
||||
break;
|
||||
case 'auth-type':
|
||||
if (in_array($options['valid'][$param], ['form', 'http_auth', 'none'], true)) {
|
||||
FreshRSS_Context::systemConf()->auth_type = $options['valid'][$param];
|
||||
} else {
|
||||
fail('FreshRSS invalid authentication method! auth_type must be one of { form, http_auth, none }');
|
||||
}
|
||||
break;
|
||||
case 'base-url':
|
||||
FreshRSS_Context::systemConf()->base_url = (string) $options['valid'][$param];
|
||||
break;
|
||||
case 'default-user':
|
||||
if (FreshRSS_user_Controller::checkUsername((string) $options['valid'][$param])) {
|
||||
FreshRSS_Context::systemConf()->default_user = (string) $options['valid'][$param];
|
||||
} else {
|
||||
$values = [
|
||||
'default_user' => $cliOptions->defaultUser ?? null,
|
||||
'environment' => $cliOptions->environment ?? null,
|
||||
'base_url' => $cliOptions->baseUrl ?? null,
|
||||
'language' => $cliOptions->language ?? null,
|
||||
'title' => $cliOptions->title ?? null,
|
||||
'allow_anonymous' => $cliOptions->allowAnonymous ?? null,
|
||||
'allow_anonymous_refresh' => $cliOptions->allowAnonymousRefresh ?? null,
|
||||
'auth_type' => $cliOptions->authType ?? null,
|
||||
'api_enabled' => $cliOptions->apiEnabled ?? null,
|
||||
'allow_robots' => $cliOptions->allowRobots ?? null,
|
||||
'disable_update' => $cliOptions->disableUpdate ?? null,
|
||||
];
|
||||
|
||||
$dbValues = [
|
||||
'type' => $cliOptions->dbType ?? null,
|
||||
'host' => $cliOptions->dbHost ?? null,
|
||||
'user' => $cliOptions->dbUser ?? null,
|
||||
'password' => $cliOptions->dbPassword ?? null,
|
||||
'base' => $cliOptions->dbBase ?? null,
|
||||
'prefix' => $cliOptions->dbPrefix ?? null,
|
||||
];
|
||||
|
||||
$systemConf = FreshRSS_Context::systemConf();
|
||||
foreach ($values as $name => $value) {
|
||||
if ($value !== null) {
|
||||
switch ($name) {
|
||||
case 'default_user':
|
||||
if (!FreshRSS_user_Controller::checkUsername($value)) {
|
||||
fail('FreshRSS invalid default username! default_user must be ASCII alphanumeric');
|
||||
}
|
||||
break;
|
||||
case 'disable-update':
|
||||
FreshRSS_Context::systemConf()->disable_update = true;
|
||||
break;
|
||||
case 'environment':
|
||||
if (in_array($options['valid'][$param], ['development', 'production', 'silent'], true)) {
|
||||
FreshRSS_Context::systemConf()->environment = $options['valid'][$param];
|
||||
} else {
|
||||
if (!in_array($value, ['development', 'production', 'silent'], true)) {
|
||||
fail('FreshRSS invalid environment! environment must be one of { development, production, silent }');
|
||||
}
|
||||
break;
|
||||
case 'language':
|
||||
FreshRSS_Context::systemConf()->language = (string) $options['valid'][$param];
|
||||
break;
|
||||
case 'title':
|
||||
FreshRSS_Context::systemConf()->title = (string) $options['valid'][$param];
|
||||
case 'auth_type':
|
||||
if (!in_array($value, ['form', 'http_auth', 'none'], true)) {
|
||||
fail('FreshRSS invalid authentication method! auth_type must be one of { form, http_auth, none }');
|
||||
}
|
||||
break;
|
||||
}
|
||||
// @phpstan-ignore-next-line (change to `@phpstan-ignore property.dynamicName` when upgrading to PHPStan 1.11+)
|
||||
$systemConf->$name = $value;
|
||||
}
|
||||
}
|
||||
$db = FreshRSS_Context::systemConf()->db;
|
||||
foreach ($dBconfigParams as $dBparam => $configDbParam) {
|
||||
if (isset($options['valid'][$dBparam])) {
|
||||
$db[$configDbParam] = $options['valid'][$dBparam];
|
||||
}
|
||||
}
|
||||
/** @var array{'type':string,'host':string,'user':string,'password':string,'base':string,'prefix':string,
|
||||
* 'connection_uri_params':string,'pdo_options':array<int,int|string|bool>} $db */
|
||||
|
||||
$db = array_merge(FreshRSS_Context::systemConf()->db, array_filter($dbValues));
|
||||
|
||||
performRequirementCheck($db['type']);
|
||||
|
||||
FreshRSS_Context::systemConf()->db = $db;
|
||||
|
||||
FreshRSS_Context::systemConf()->save();
|
||||
|
|
|
@ -1,26 +1,85 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require(__DIR__ . '/_cli.php');
|
||||
|
||||
$isUpdate = true;
|
||||
require(__DIR__ . '/_update-or-create-user.php');
|
||||
$cliOptions = new class extends CliOptionsParser {
|
||||
public string $user;
|
||||
public string $password;
|
||||
public string $apiPassword;
|
||||
public string $language;
|
||||
public string $email;
|
||||
public string $token;
|
||||
public int $purgeAfterMonths;
|
||||
public int $feedMinArticles;
|
||||
public int $feedTtl;
|
||||
public int $sinceHoursPostsPerRss;
|
||||
public int $maxPostsPerRss;
|
||||
|
||||
$username = cliInitUser($GLOBALS['options']['valid']['user']);
|
||||
public function __construct() {
|
||||
$this->addRequiredOption('user', (new CliOption('user')));
|
||||
$this->addOption('password', (new CliOption('password')));
|
||||
$this->addOption('apiPassword', (new CliOption('api-password'))->deprecatedAs('api_password'));
|
||||
$this->addOption('language', (new CliOption('language')));
|
||||
$this->addOption('email', (new CliOption('email')));
|
||||
$this->addOption('token', (new CliOption('token')));
|
||||
$this->addOption(
|
||||
'purgeAfterMonths',
|
||||
(new CliOption('purge-after-months'))->typeOfInt()->deprecatedAs('purge_after_months')
|
||||
);
|
||||
$this->addOption(
|
||||
'feedMinArticles',
|
||||
(new CliOption('feed-min-articles-default'))->typeOfInt()->deprecatedAs('feed_min_articles_default')
|
||||
);
|
||||
$this->addOption(
|
||||
'feedTtl',
|
||||
(new CliOption('feed-ttl-default'))->typeOfInt()->deprecatedAs('feed_ttl_default')
|
||||
);
|
||||
$this->addOption(
|
||||
'sinceHoursPostsPerRss',
|
||||
(new CliOption('since-hours-posts-per-rss'))->typeOfInt()->deprecatedAs('since_hours_posts_per_rss')
|
||||
);
|
||||
$this->addOption(
|
||||
'maxPostsPerRss',
|
||||
(new CliOption('max-posts-per-rss'))->typeOfInt()->deprecatedAs('max_posts_per_rss')
|
||||
);
|
||||
parent::__construct();
|
||||
}
|
||||
};
|
||||
|
||||
if (!empty($cliOptions->errors)) {
|
||||
fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage);
|
||||
}
|
||||
|
||||
$username = cliInitUser($cliOptions->user);
|
||||
|
||||
echo 'FreshRSS updating user “', $username, "”…\n";
|
||||
|
||||
$values = [
|
||||
'language' => $cliOptions->language ?? null,
|
||||
'mail_login' => $cliOptions->email ?? null,
|
||||
'token' => $cliOptions->token ?? null,
|
||||
'old_entries' => $cliOptions->purgeAfterMonths ?? null,
|
||||
'keep_history_default' => $cliOptions->feedMinArticles ?? null,
|
||||
'ttl_default' => $cliOptions->feedTtl ?? null,
|
||||
'since_hours_posts_per_rss' => $cliOptions->sinceHoursPostsPerRss ?? null,
|
||||
'max_posts_per_rss' => $cliOptions->maxPostsPerRss ?? null,
|
||||
];
|
||||
|
||||
$values = array_filter($values);
|
||||
|
||||
$ok = FreshRSS_user_Controller::updateUser(
|
||||
$username,
|
||||
empty($options['valid']['email']) ? null : $options['valid']['email'],
|
||||
empty($options['valid']['password']) ? '' : $options['valid']['password'],
|
||||
$GLOBALS['values']);
|
||||
isset($cliOptions->email) ? $cliOptions->email : null,
|
||||
$cliOptions->password ?? '',
|
||||
$values);
|
||||
|
||||
if (!$ok) {
|
||||
fail('FreshRSS could not update user!');
|
||||
}
|
||||
|
||||
if (!empty($options['valid']['api_password'])) {
|
||||
$error = FreshRSS_api_Controller::updatePassword($options['valid']['api_password']);
|
||||
if (isset($cliOptions->apiPassword)) {
|
||||
$error = FreshRSS_api_Controller::updatePassword($cliOptions->apiPassword);
|
||||
if ($error) {
|
||||
fail($error);
|
||||
}
|
||||
|
|
|
@ -5,45 +5,38 @@ require(__DIR__ . '/_cli.php');
|
|||
|
||||
const DATA_FORMAT = "%-7s | %-20s | %-5s | %-7s | %-25s | %-15s | %-10s | %-10s | %-10s | %-10s | %-10s | %-10s | %-5s | %-10s\n";
|
||||
|
||||
$parameters = [
|
||||
'long' => [
|
||||
'user' => ':',
|
||||
'header' => '',
|
||||
'json' => '',
|
||||
'human-readable' => '',
|
||||
],
|
||||
'short' => [
|
||||
'human-readable' => 'h',
|
||||
],
|
||||
'deprecated' => [],
|
||||
];
|
||||
$cliOptions = new class extends CliOptionsParser {
|
||||
/** @var array<int,string> $user */
|
||||
public array $user;
|
||||
public string $header;
|
||||
public string $json;
|
||||
public string $humanReadable;
|
||||
|
||||
$options = parseCliParams($parameters);
|
||||
public function __construct() {
|
||||
$this->addOption('user', (new CliOption('user'))->typeOfArrayOfString());
|
||||
$this->addOption('header', (new CliOption('header'))->withValueNone());
|
||||
$this->addOption('json', (new CliOption('json'))->withValueNone());
|
||||
$this->addOption('humanReadable', (new CliOption('human-readable', 'h'))->withValueNone());
|
||||
parent::__construct();
|
||||
}
|
||||
};
|
||||
|
||||
if (!empty($options['invalid'])) {
|
||||
fail('Usage: ' . basename(__FILE__) . ' (--human-readable --header --json --user username --user username …)');
|
||||
if (!empty($cliOptions->errors)) {
|
||||
fail('FreshRSS error: ' . array_shift($cliOptions->errors) . "\n" . $cliOptions->usage);
|
||||
}
|
||||
|
||||
if (empty($options['valid']['user'])) {
|
||||
$users = listUsers();
|
||||
} elseif (is_array($options['valid']['user'])) {
|
||||
/** @var array<string> $users */
|
||||
$users = $options['valid']['user'];
|
||||
} else {
|
||||
/** @var array<string> $users */
|
||||
$users = [$options['valid']['user']];
|
||||
}
|
||||
$users = $cliOptions->user ?? listUsers();
|
||||
|
||||
sort($users);
|
||||
|
||||
$formatJson = isset($options['valid']['json']);
|
||||
$formatJson = isset($cliOptions->json);
|
||||
$jsonOutput = [];
|
||||
if ($formatJson) {
|
||||
unset($options['valid']['header']);
|
||||
unset($options['valid']['human-readable']);
|
||||
unset($cliOptions->header);
|
||||
unset($cliOptions->humanReadable);
|
||||
}
|
||||
|
||||
if (array_key_exists('header', $options['valid'])) {
|
||||
if (isset($cliOptions->header)) {
|
||||
printf(
|
||||
DATA_FORMAT,
|
||||
'default',
|
||||
|
@ -92,7 +85,7 @@ foreach ($users as $username) {
|
|||
'lang' => FreshRSS_Context::userConf()->language,
|
||||
'mail_login' => FreshRSS_Context::userConf()->mail_login,
|
||||
);
|
||||
if (isset($options['valid']['human-readable'])) { //Human format
|
||||
if (isset($cliOptions->humanReadable)) { //Human format
|
||||
$data['last_user_activity'] = date('c', $data['last_user_activity']);
|
||||
$data['database_size'] = format_bytes($data['database_size']);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,242 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
require_once __DIR__ . '/../../cli/CliOption.php';
|
||||
require_once __DIR__ . '/../../cli/CliOptionsParser.php';
|
||||
|
||||
final class CliOptionsOptionalTest extends CliOptionsParser {
|
||||
public string $string = '';
|
||||
public int $int = 0;
|
||||
public bool $bool = false;
|
||||
/** @var array<int,string> $arrayOfString */
|
||||
public array $arrayOfString = [];
|
||||
public string $defaultInput = '';
|
||||
public string $optionalValue = '';
|
||||
public bool $optionalValueWithDefault = false;
|
||||
public string $defaultInputAndOptionalValueWithDefault = '';
|
||||
|
||||
public function __construct() {
|
||||
$this->addOption('string', (new CliOption('string', 's'))->deprecatedAs('deprecated-string'));
|
||||
$this->addOption('int', (new CliOption('int', 'i'))->typeOfInt());
|
||||
$this->addOption('bool', (new CliOption('bool', 'b'))->typeOfBool());
|
||||
$this->addOption('arrayOfString', (new CliOption('array-of-string', 'a'))->typeOfArrayOfString());
|
||||
$this->addOption('defaultInput', (new CliOption('default-input', 'i')), 'default');
|
||||
$this->addOption('optionalValue', (new CliOption('optional-value', 'o'))->withValueOptional());
|
||||
$this->addOption('optionalValueWithDefault', (new CliOption('optional-value-with-default', 'd'))->withValueOptional('true')->typeOfBool());
|
||||
$this->addOption('defaultInputAndOptionalValueWithDefault',
|
||||
(new CliOption('default-input-and-optional-value-with-default', 'e'))->withValueOptional('optional'),
|
||||
'default'
|
||||
);
|
||||
$this->addOption('flag', (new CliOption('flag', 'f'))->withValueNone());
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
|
||||
final class CliOptionsOptionalAndRequiredTest extends CliOptionsParser {
|
||||
public string $required = '';
|
||||
public string $string = '';
|
||||
public int $int = 0;
|
||||
public bool $bool = false;
|
||||
public string $flag = '';
|
||||
|
||||
public function __construct() {
|
||||
$this->addRequiredOption('required', new CliOption('required'));
|
||||
$this->addOption('string', new CliOption('string', 's'));
|
||||
$this->addOption('int', (new CliOption('int', 'i'))->typeOfInt());
|
||||
$this->addOption('bool', (new CliOption('bool', 'b'))->typeOfBool());
|
||||
$this->addOption('flag', (new CliOption('flag', 'f'))->withValueNone());
|
||||
parent::__construct();
|
||||
}
|
||||
}
|
||||
|
||||
class CliOptionsParserTest extends TestCase {
|
||||
|
||||
public function testInvalidOptionSetWithValueReturnsError(): void {
|
||||
$result = $this->runOptionalOptions('--invalid=invalid');
|
||||
|
||||
self::assertEquals(['invalid' => 'unknown option: invalid'], $result->errors);
|
||||
}
|
||||
|
||||
public function testInvalidOptionSetWithoutValueReturnsError(): void {
|
||||
$result = $this->runOptionalOptions('--invalid');
|
||||
|
||||
self::assertEquals(['invalid' => 'unknown option: invalid'], $result->errors);
|
||||
}
|
||||
|
||||
public function testValidOptionSetWithValidValueAndInvalidOptionSetWithValueReturnsValueForValidOptionAndErrorForInvalidOption(): void {
|
||||
$result = $this->runOptionalOptions('--string=string --invalid=invalid');
|
||||
|
||||
self::assertEquals('string', $result->string);
|
||||
self::assertEquals(['invalid' => 'unknown option: invalid'], $result->errors);
|
||||
}
|
||||
|
||||
public function testOptionWithValueTypeOfStringSetOnceWithValidValueReturnsValueAsString(): void {
|
||||
$result = $this->runOptionalOptions('--string=string');
|
||||
|
||||
self::assertEquals('string', $result->string);
|
||||
}
|
||||
|
||||
public function testOptionWithRequiredValueTypeOfIntSetOnceWithValidValueReturnsValueAsInt(): void {
|
||||
$result = $this->runOptionalOptions('--int=111');
|
||||
|
||||
self::assertEquals(111, $result->int);
|
||||
}
|
||||
|
||||
public function testOptionWithRequiredValueTypeOfBoolSetOnceWithValidValueReturnsValueAsBool(): void {
|
||||
$result = $this->runOptionalOptions('--bool=on');
|
||||
|
||||
self::assertEquals(true, $result->bool);
|
||||
}
|
||||
|
||||
public function testOptionWithValueTypeOfArrayOfStringSetOnceWithValidValueReturnsValueAsArrayOfString(): void {
|
||||
$result = $this->runOptionalOptions('--array-of-string=string');
|
||||
|
||||
self::assertEquals(['string'], $result->arrayOfString);
|
||||
}
|
||||
|
||||
public function testOptionWithValueTypeOfStringSetMultipleTimesWithValidValueReturnsLastValueSetAsString(): void {
|
||||
$result = $this->runOptionalOptions('--string=first --string=second');
|
||||
|
||||
self::assertEquals('second', $result->string);
|
||||
}
|
||||
|
||||
public function testOptionWithValueTypeOfIntSetMultipleTimesWithValidValueReturnsLastValueSetAsInt(): void {
|
||||
$result = $this->runOptionalOptions('--int=111 --int=222');
|
||||
|
||||
self::assertEquals(222, $result->int);
|
||||
}
|
||||
|
||||
public function testOptionWithValueTypeOfBoolSetMultipleTimesWithValidValueReturnsLastValueSetAsBool(): void {
|
||||
$result = $this->runOptionalOptions('--bool=on --bool=off');
|
||||
|
||||
self::assertEquals(false, $result->bool);
|
||||
}
|
||||
|
||||
public function testOptionWithValueTypeOfArrayOfStringSetMultipleTimesWithValidValueReturnsAllSetValuesAsArrayOfString(): void {
|
||||
$result = $this->runOptionalOptions('--array-of-string=first --array-of-string=second');
|
||||
|
||||
self::assertEquals(['first', 'second'], $result->arrayOfString);
|
||||
}
|
||||
|
||||
public function testOptionWithValueTypeOfIntSetWithInvalidValueReturnsAnError(): void {
|
||||
$result = $this->runOptionalOptions('--int=one');
|
||||
|
||||
self::assertEquals(['int' => 'invalid input: int must be an integer'], $result->errors);
|
||||
}
|
||||
|
||||
public function testOptionWithValueTypeOfBoolSetWithInvalidValuesReturnsAnError(): void {
|
||||
$result = $this->runOptionalOptions('--bool=bad');
|
||||
|
||||
self::assertEquals(['bool' => 'invalid input: bool must be a boolean'], $result->errors);
|
||||
}
|
||||
|
||||
public function testOptionWithValueTypeOfIntSetMultipleTimesWithValidAndInvalidValuesReturnsLastValidValueSetAsIntAndError(): void {
|
||||
$result = $this->runOptionalOptions('--int=111 --int=one --int=222 --int=two');
|
||||
|
||||
self::assertEquals(222, $result->int);
|
||||
self::assertEquals(['int' => 'invalid input: int must be an integer'], $result->errors);
|
||||
}
|
||||
|
||||
public function testOptionWithValueTypeOfBoolSetMultipleTimesWithWithValidAndInvalidValuesReturnsLastValidValueSetAsBoolAndError(): void {
|
||||
$result = $this->runOptionalOptions('--bool=on --bool=good --bool=off --bool=bad');
|
||||
|
||||
self::assertEquals(false, $result->bool);
|
||||
self::assertEquals(['bool' => 'invalid input: bool must be a boolean'], $result->errors);
|
||||
}
|
||||
|
||||
public function testNotSetOptionWithDefaultInputReturnsDefaultInput(): void {
|
||||
$result = $this->runOptionalOptions('');
|
||||
|
||||
self::assertEquals('default', $result->defaultInput);
|
||||
}
|
||||
|
||||
public function testOptionWithDefaultInputSetWithValidValueReturnsCorrectlyTypedValue(): void {
|
||||
$result = $this->runOptionalOptions('--default-input=input');
|
||||
|
||||
self::assertEquals('input', $result->defaultInput);
|
||||
}
|
||||
|
||||
public function testOptionWithOptionalValueSetWithoutValueReturnsEmptyString(): void {
|
||||
$result = $this->runOptionalOptions('--optional-value');
|
||||
|
||||
self::assertEquals('', $result->optionalValue);
|
||||
}
|
||||
|
||||
public function testOptionWithOptionalValueDefaultSetWithoutValueReturnsOptionalValueDefault(): void {
|
||||
$result = $this->runOptionalOptions('--optional-value-with-default');
|
||||
|
||||
self::assertEquals(true, $result->optionalValueWithDefault);
|
||||
}
|
||||
|
||||
public function testNotSetOptionWithOptionalValueDefaultAndDefaultInputReturnsDefaultInput(): void {
|
||||
$result = $this->runOptionalOptions('');
|
||||
|
||||
self::assertEquals('default', $result->defaultInputAndOptionalValueWithDefault);
|
||||
}
|
||||
|
||||
public function testOptionWithOptionalValueDefaultAndDefaultInputSetWithoutValueReturnsOptionalValueDefault(): void {
|
||||
$result = $this->runOptionalOptions('--default-input-and-optional-value-with-default');
|
||||
|
||||
self::assertEquals('optional', $result->defaultInputAndOptionalValueWithDefault);
|
||||
}
|
||||
|
||||
public function testRequiredOptionNotSetReturnsError(): void {
|
||||
$result = $this->runOptionalAndRequiredOptions('');
|
||||
|
||||
self::assertEquals(['required' => 'invalid input: required cannot be empty'], $result->errors);
|
||||
}
|
||||
|
||||
public function testOptionSetWithDeprecatedAliasGeneratesDeprecationWarningAndReturnsValue(): void {
|
||||
$result = $this->runCommandReadingStandardError('--deprecated-string=string');
|
||||
|
||||
self::assertEquals('FreshRSS deprecation warning: the CLI option(s): deprecated-string are deprecated ' .
|
||||
'and will be removed in a future release. Use: string instead',
|
||||
$result
|
||||
);
|
||||
|
||||
$result = $this->runOptionalOptions('--deprecated-string=string');
|
||||
|
||||
self::assertEquals('string', $result->string);
|
||||
}
|
||||
|
||||
public function testAlwaysReturnUsageMessageWithUsageInfoForAllOptions(): void {
|
||||
$result = $this->runOptionalAndRequiredOptions('');
|
||||
|
||||
self::assertEquals('Usage: cli-parser-test.php --required=<required> [-s --string=<string>] [-i --int=<int>] [-b --bool=<bool>] [-f --flag]',
|
||||
$result->usage,
|
||||
);
|
||||
}
|
||||
|
||||
private function runOptionalOptions(string $cliOptions = ''): CliOptionsOptionalTest {
|
||||
$command = __DIR__ . '/cli-parser-test.php';
|
||||
$className = CliOptionsOptionalTest::class;
|
||||
|
||||
$result = shell_exec("CLI_PARSER_TEST_OPTIONS_CLASS='$className' $command $cliOptions 2>/dev/null");
|
||||
$result = is_string($result) ? unserialize($result) : new CliOptionsOptionalTest();
|
||||
|
||||
/** @var CliOptionsOptionalTest $result */
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function runOptionalAndRequiredOptions(string $cliOptions = ''): CliOptionsOptionalAndRequiredTest {
|
||||
$command = __DIR__ . '/cli-parser-test.php';
|
||||
$className = CliOptionsOptionalAndRequiredTest::class;
|
||||
|
||||
$result = shell_exec("CLI_PARSER_TEST_OPTIONS_CLASS='$className' $command $cliOptions 2>/dev/null");
|
||||
$result = is_string($result) ? unserialize($result) : new CliOptionsOptionalAndRequiredTest();
|
||||
|
||||
/** @var CliOptionsOptionalAndRequiredTest $result */
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function runCommandReadingStandardError(string $cliOptions = ''): string {
|
||||
$command = __DIR__ . '/cli-parser-test.php';
|
||||
$className = CliOptionsOptionalTest::class;
|
||||
|
||||
$result = shell_exec("CLI_PARSER_TEST_OPTIONS_CLASS='$className' $command $cliOptions 2>&1");
|
||||
$result = is_string($result) ? explode("\n", $result) : '';
|
||||
|
||||
return is_array($result) ? $result[0] : '';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require(__DIR__ . '/../../vendor/autoload.php');
|
||||
require(__DIR__ . '/CliOptionsParserTest.php');
|
||||
|
||||
$optionsClass = getenv('CLI_PARSER_TEST_OPTIONS_CLASS');
|
||||
if (!is_string($optionsClass) || !class_exists($optionsClass)) {
|
||||
die('Invalid test static method!');
|
||||
}
|
||||
|
||||
switch ($optionsClass) {
|
||||
case CliOptionsOptionalTest::class:
|
||||
$options = new CliOptionsOptionalTest();
|
||||
break;
|
||||
case CliOptionsOptionalAndRequiredTest::class:
|
||||
$options = new CliOptionsOptionalAndRequiredTest();
|
||||
break;
|
||||
default:
|
||||
die('Unknown test static method!');
|
||||
}
|
||||
|
||||
echo serialize($options);
|
Loading…
Reference in New Issue