1
0

adding kirby3-janitor

This commit is contained in:
Philip Wagner
2024-10-14 14:22:24 +02:00
parent b0db09492d
commit 94fbb996f0
204 changed files with 27855 additions and 4 deletions

View File

@@ -0,0 +1,416 @@
<?php
namespace League\CLImate\Argument;
use League\CLImate\Exceptions\UnexpectedValueException;
use function is_array;
class Argument
{
/**
* An argument's name.
*
* Use this name when internally referring to the argument.
*
* @var string
*/
protected $name;
/**
* An argument's short representation.
*
* @var string
*/
protected $prefix;
/**
* An argument's long representation.
*
* @var string
*/
protected $longPrefix;
/**
* An argument's description.
*
* @var string
*/
protected $description;
/**
* Whether or not an argument is required.
*
* @var bool
*/
protected $required = false;
/**
* Whether or not an argument only needs to be defined to have a value.
*
* These arguments have the value true when they are defined on the command
* line.
*
* @var bool
*/
protected $noValue = false;
/**
* Which data type to cast an argument's value to.
*
* Valid data types are "string", "int", "float", and "bool".
*
* @var string
*/
protected $castTo = 'string';
/**
* An argument's default value.
*
* @var string
*/
protected $defaultValue = [];
/**
* An argument's value, after type casting.
*
* @var string[]|int[]|float[]|bool[]
*/
protected $values = [];
/**
* Build a new command argument.
*
* @param string $name
*/
public function __construct($name)
{
$this->setName($name);
}
/**
* Build a new command argument from an array.
*
* @param string $name
* @param array $params
*
* @return Argument
*/
public static function createFromArray($name, array $params)
{
$argument = new Argument($name);
$params = self::getSettableArgumentParams($params);
foreach ($params as $key => $value) {
$method = 'set' . ucwords($key);
$argument->{$method}($value);
}
return $argument;
}
/**
* Get argument params based on settable properties
*
* @param array $params
*
* @return array
*/
protected static function getSettableArgumentParams(array $params)
{
$allowed = [
'prefix',
'longPrefix',
'description',
'required',
'noValue',
'castTo',
'defaultValue',
];
return array_intersect_key($params, array_flip($allowed));
}
/**
* Retrieve an argument's name.
*
* Use this name when internally referring to the argument.
*
* @return string
*/
public function name()
{
return $this->name;
}
/**
* Set an argument's name.
*
* Use this name when internally referring to the argument.
*
* @param string $name
*/
protected function setName($name)
{
$this->name = trim($name);
}
/**
* Retrieve an argument's short form.
*
* @return string
*/
public function prefix()
{
return $this->prefix;
}
/**
* Set an argument's short form.
*
* @param string $prefix
*/
protected function setPrefix($prefix)
{
$this->prefix = trim($prefix);
}
/**
* Retrieve an argument's long form.
*
* @return string
*/
public function longPrefix()
{
return $this->longPrefix;
}
/**
* Set an argument's short form.
*
* @param string $longPrefix
*/
protected function setLongPrefix($longPrefix)
{
$this->longPrefix = trim($longPrefix);
}
/**
* Determine if an argument has a prefix.
*
* @return bool
*/
public function hasPrefix()
{
return $this->prefix() || $this->longPrefix();
}
/**
* Retrieve an argument's description.
*
* @return string
*/
public function description()
{
return $this->description;
}
/**
* Set an argument's description.
*
* @param string $description
*/
protected function setDescription($description)
{
$this->description = trim($description);
}
/**
* Determine whether or not an argument is required.
*
* @return bool
*/
public function isRequired()
{
return $this->required;
}
/**
* Set whether an argument is required or not.
*
* @param bool $required
*/
protected function setRequired($required)
{
$this->required = (bool) $required;
}
/**
* Determine whether or not an argument only needs to be defined to have a
* value.
*
* @return bool
*/
public function noValue()
{
return $this->noValue;
}
/**
* Set whether or not an argument only needs to be defined to have a value.
*
* @param bool $noValue
*/
protected function setNoValue($noValue)
{
$this->setCastTo('bool');
$this->noValue = (bool) $noValue;
}
/**
* Retrieve the data type to cast an argument's value to.
*
* @return string
*/
public function castTo()
{
return $this->castTo;
}
/**
* Set the data type to cast an argument's value to.
*
* Valid data types are "string", "int", "float", and "bool".
*
* @param string $castTo
*
* @return void
* @throws UnexpectedValueException if $castTo is not a valid data type.
*/
protected function setCastTo($castTo)
{
if (!in_array($castTo, ['string', 'int', 'float', 'bool'])) {
throw new UnexpectedValueException(
"An argument may only be cast to the data type "
. "'string', 'int', 'float', or 'bool'."
);
}
$this->castTo = $this->noValue() ? 'bool' : $castTo;
}
/**
* Retrieve an argument's default values.
*
* @return string
*/
public function defaultValue()
{
return $this->defaultValue;
}
/**
* Set an argument's default value.
*
* @param string $defaultValue
*/
public function setDefaultValue($defaultValue)
{
if (!is_array($defaultValue)) {
$defaultValue = [$defaultValue];
}
$this->defaultValue = $defaultValue;
}
/**
* Retrieve an argument's value.
*
* Argument values are type cast based on the value of $castTo.
*
* @return string|int|float|bool
*/
public function value()
{
if ($this->values) {
return end($this->values);
}
$cast_method = 'castTo' . ucwords($this->castTo);
return $this->{$cast_method}(current($this->defaultValue()));
}
/**
* Retrieve an argument's values.
*
* Argument values are type cast based on the value of $castTo.
*
* @return string[]|int[]|float[]|bool[]
*/
public function values()
{
if ($this->values) {
return $this->values;
}
$cast_method = 'castTo' . ucwords($this->castTo);
return array_map([$this, $cast_method], $this->defaultValue());
}
/**
* @deprecated use values() instead.
*/
public function valueArray()
{
return $this->values();
}
/**
* Set an argument's value based on its command line entry.
*
* Argument values are type cast based on the value of $castTo.
*
* @param string|bool $value
*/
public function setValue($value)
{
$cast_method = 'castTo' . ucwords($this->castTo);
$this->values[] = $this->{$cast_method}($value);
}
/**
* @param string $value
*
* @return string
*/
protected function castToString($value)
{
return (string) $value;
}
/**
* @param string $value
*
* @return int
*/
protected function castToInt($value)
{
return (int) $value;
}
/**
* @param string $value
*
* @return float
*/
protected function castToFloat($value)
{
return (float) $value;
}
/**
* @param string $value
*
* @return bool
*/
protected function castToBool($value)
{
return (bool) $value;
}
}

View File

@@ -0,0 +1,183 @@
<?php
namespace League\CLImate\Argument;
class Filter
{
protected $arguments = [];
/**
* Set the available arguments
*
* @param array $arguments
*/
public function setArguments(array $arguments)
{
$this->arguments = $arguments;
}
/**
* Retrieve optional arguments
*
* @return Argument[]
*/
public function optional()
{
return $this->filterArguments(['isOptional']);
}
/**
* Retrieve required arguments
*
* @return Argument[]
*/
public function required()
{
return $this->filterArguments(['isRequired']);
}
/**
* Retrieve arguments with prefix
*
* @return Argument[]
*/
public function withPrefix()
{
return $this->filterArguments(['hasPrefix']);
}
/**
* Retrieve arguments without prefix
*
* @return Argument[]
*/
public function withoutPrefix()
{
return $this->filterArguments(['noPrefix']);
}
/**
* Find all required arguments that don't have values after parsing.
*
* These arguments weren't defined on the command line.
*
* @return Argument[]
*/
public function missing()
{
return $this->filterArguments(['isRequired', 'noValue']);
}
/**
* Filter defined arguments as to whether they are required or not
*
* @param string[] $filters
*
* @return Argument[]
*/
protected function filterArguments($filters = [])
{
$arguments = $this->arguments;
foreach ($filters as $filter) {
$arguments = array_filter($arguments, [$this, $filter]);
}
if (in_array('hasPrefix', $filters)) {
usort($arguments, [$this, 'compareByPrefix']);
}
return array_values($arguments);
}
/**
* Determine whether an argument as a prefix
*
* @param Argument $argument
*
* @return bool
*/
protected function noPrefix($argument)
{
return !$argument->hasPrefix();
}
/**
* Determine whether an argument as a prefix
*
* @param Argument $argument
*
* @return bool
*/
protected function hasPrefix($argument)
{
return $argument->hasPrefix();
}
/**
* Determine whether an argument is required
*
* @param Argument $argument
*
* @return bool
*/
protected function isRequired($argument)
{
return $argument->isRequired();
}
/**
* Determine whether an argument is optional
*
* @param Argument $argument
*
* @return bool
*/
protected function isOptional($argument)
{
return !$argument->isRequired();
}
/**
* Determine whether an argument is optional
*
* @param Argument $argument
*
* @return bool
*/
protected function noValue($argument)
{
return $argument->values() == [];
}
/**
* Compare two arguments by their short and long prefixes.
*
* @see usort()
*
* @param Argument $a
* @param Argument $b
*
* @return int
*/
public function compareByPrefix(Argument $a, Argument $b)
{
if ($this->prefixCompareString($a) < $this->prefixCompareString($b)) {
return -1;
}
return 1;
}
/**
* Prep the prefix string for comparison
*
* @param Argument $argument
*
* @return string
*/
protected function prefixCompareString(Argument $argument)
{
return mb_strtolower($argument->longPrefix() ?: $argument->prefix() ?: '');
}
}

View File

@@ -0,0 +1,261 @@
<?php
namespace League\CLImate\Argument;
use League\CLImate\CLImate;
use League\CLImate\Exceptions\InvalidArgumentException;
class Manager
{
/**
* An array of arguments passed to the program.
*
* @var Argument[] $arguments
*/
protected $arguments = [];
/**
* A program's description.
*
* @var string $description
*/
protected $description;
/**
* Filter class to find various types of arguments
*
* @var \League\CLImate\Argument\Filter $filter
*/
protected $filter;
/**
* Summary builder class
*
* @var \League\CLImate\Argument\Summary $summary
*/
protected $summary;
/**
* Argument parser class
*
* @var \League\CLImate\Argument\Parser $parser
*/
protected $parser;
public function __construct()
{
$this->filter = new Filter();
$this->summary = new Summary();
$this->parser = new Parser();
}
/**
* Add an argument.
*
* @param Argument|string|array $argument
* @param $options
*
* @return void
* @throws InvalidArgumentException if $argument isn't an array or Argument object.
*/
public function add($argument, array $options = [])
{
if (is_array($argument)) {
$this->addMany($argument);
return;
}
if (is_string($argument)) {
$argument = Argument::createFromArray($argument, $options);
}
if (!$argument instanceof Argument) {
throw new InvalidArgumentException('Please provide an argument name or object.');
}
$this->arguments[$argument->name()] = $argument;
}
/**
* Add multiple arguments to a CLImate script.
*
* @param array $arguments
*/
protected function addMany(array $arguments = [])
{
foreach ($arguments as $name => $options) {
$this->add($name, $options);
}
}
/**
* Determine if an argument exists.
*
* @param string $name
* @return bool
*/
public function exists($name)
{
return isset($this->arguments[$name]);
}
/**
* Retrieve an argument's value.
*
* @param string $name
* @return string|int|float|bool|null
*/
public function get($name)
{
return isset($this->arguments[$name]) ? $this->arguments[$name]->value() : null;
}
/**
* Retrieve an argument's all values as an array.
*
* @param string $name
* @return string[]|int[]|float[]|bool[]
*/
public function getArray($name)
{
return isset($this->arguments[$name]) ? $this->arguments[$name]->values() : [];
}
/**
* Retrieve all arguments.
*
* @return Argument[]
*/
public function all()
{
return $this->arguments;
}
/**
* Determine if an argument has been defined on the command line.
*
* This can be useful for making sure an argument is present on the command
* line before parse()'ing them into argument objects.
*
* @param string $name
* @param array $argv
*
* @return bool
*/
public function defined($name, array $argv = null)
{
// The argument isn't defined if it's not defined by the calling code.
if (!$this->exists($name)) {
return false;
}
$argument = $this->arguments[$name];
$command_arguments = $this->parser->arguments($argv);
foreach ($command_arguments as $command_argument) {
if ($this->isArgument($argument, $command_argument)) {
return true;
}
}
return false;
}
/**
* Check if the defined argument matches the command argument.
*
* @param Argument $argument
* @param string $command_argument
*
* @return bool
*/
protected function isArgument($argument, $command_argument)
{
$possibilities = [
$argument->prefix() => "-{$argument->prefix()}",
$argument->longPrefix() => "--{$argument->longPrefix()}",
];
foreach ($possibilities as $key => $search) {
if ($key && strpos($command_argument, $search) === 0) {
return true;
}
}
return false;
}
/**
* Retrieve all arguments as key/value pairs.
*
* @return array
*/
public function toArray()
{
$return = [];
foreach ($this->all() as $name => $argument) {
$return[$name] = $argument->value();
}
return $return;
}
/**
* Set a program's description.
*
* @param string $description
*/
public function description($description)
{
$this->description = trim($description);
}
/**
* Output a script's usage statement.
*
* @param CLImate $climate
* @param array $argv
*/
public function usage(CLImate $climate, array $argv = null)
{
$this->summary
->setClimate($climate)
->setDescription($this->description)
->setCommand($this->parser->command($argv))
->setFilter($this->filter, $this->all())
->output();
}
/**
* Parse command line arguments into CLImate arguments.
*
* @param array $argv
*/
public function parse(array $argv = null)
{
$this->parser->setFilter($this->filter, $this->all());
$this->parser->parse($argv);
}
/**
* Get the trailing arguments
*
* @return string|null
*/
public function trailing()
{
return $this->parser->trailing();
}
/**
* Get the trailing arguments as an array
*
* @return array|null
*/
public function trailingArray()
{
return $this->parser->trailingArray();
}
}

View File

@@ -0,0 +1,309 @@
<?php
namespace League\CLImate\Argument;
use League\CLImate\Exceptions\InvalidArgumentException;
class Parser
{
/**
* Filter class to find various types of arguments
*
* @var \League\CLImate\Argument\Filter $filter
*/
protected $filter;
/**
* Summary builder class
*
* @var \League\CLImate\Argument\Summary $summary
*/
protected $summary;
protected $trailing;
protected $trailingArray;
public function __construct()
{
$this->summary = new Summary();
}
/**
* @param Filter $filter
* @param Argument[] $arguments
*
* @return \League\CLImate\Argument\Parser
*/
public function setFilter($filter, $arguments)
{
$this->filter = $filter;
$this->filter->setArguments($arguments);
return $this;
}
/**
* Parse command line arguments into CLImate arguments.
*
* @param array $argv
*
* @return void
* @throws InvalidArgumentException if required arguments aren't defined.
*/
public function parse(array $argv = null)
{
$cliArguments = $this->arguments($argv);
if (in_array('--', $cliArguments)) {
$cliArguments = $this->removeTrailingArguments($cliArguments);
}
$unParsedArguments = $this->prefixedArguments($cliArguments);
$this->nonPrefixedArguments($unParsedArguments);
// After parsing find out which arguments were required but not
// defined on the command line.
$missingArguments = $this->filter->missing();
if (count($missingArguments) > 0) {
throw new InvalidArgumentException(
'The following arguments are required: '
. $this->summary->short($missingArguments) . '.'
);
}
}
/**
* Get the command name.
*
* @param array $argv
*
* @return string
*/
public function command(array $argv = null)
{
return $this->getCommandAndArguments($argv)['command'];
}
/**
* Get the passed arguments.
*
* @param array $argv
*
* @return array
*/
public function arguments(array $argv = null)
{
return $this->getCommandAndArguments($argv)['arguments'];
}
/**
* Get the trailing arguments
*
* @return string|null
*/
public function trailing()
{
return $this->trailing;
}
/**
* Get the trailing arguments as an array
*
* @return array|null
*/
public function trailingArray()
{
return $this->trailingArray;
}
/**
* Remove the trailing arguments from the parser and set them aside
*
* @param array $arguments
*
* @return array
*/
protected function removeTrailingArguments(array $arguments)
{
$trailing = array_splice($arguments, array_search('--', $arguments));
array_shift($trailing);
$this->trailingArray = $trailing;
$this->trailing = implode(' ', $trailing);
return $arguments;
}
/**
* Parse command line options into prefixed CLImate arguments.
*
* Prefixed arguments are arguments with a prefix (-) or a long prefix (--)
* on the command line.
*
* Return the arguments passed on the command line that didn't match up with
* prefixed arguments so they can be assigned to non-prefixed arguments.
*
* @param array $argv
* @return array
*/
protected function prefixedArguments(array $argv = [])
{
foreach ($argv as $key => $passed_argument) {
$argv = $this->trySettingArgumentValue($argv, $key, $passed_argument);
}
// Send un-parsed arguments back upstream.
return array_values($argv);
}
/**
* Parse unset command line options into non-prefixed CLImate arguments.
*
* Non-prefixed arguments are parsed after the prefixed arguments on the
* command line, in the order that they're defined in the script.
*
* @param array $unParsedArguments
*/
protected function nonPrefixedArguments(array $unParsedArguments = [])
{
foreach ($this->filter->withoutPrefix() as $key => $argument) {
if (isset($unParsedArguments[$key])) {
$argument->setValue($unParsedArguments[$key]);
}
}
}
/**
* Parse the name and value of the argument passed in
*
* @param string $cliArgument
* @return string[] [$name, $value]
*/
protected function getNameAndValue($cliArgument)
{
// Look for arguments defined in the "key=value" format.
if (strpos($cliArgument, '=') !== false) {
return explode('=', $cliArgument, 2);
}
// If the argument isn't in "key=value" format then assume it's in
// "key value" format and define the value after we've found the
// matching CLImate argument.
return [$cliArgument, null];
}
/**
* Attempt to set the an argument's value and remove applicable
* arguments from array
*
* @param array $argv
* @param int $key
* @param string $passed_argument
*
* @return array The new $argv
*/
protected function trySettingArgumentValue($argv, $key, $passed_argument)
{
list($name, $value) = $this->getNameAndValue($passed_argument);
// Look for the argument in our defined $arguments
// and assign their value.
if (!($argument = $this->findPrefixedArgument($name))) {
return $argv;
}
// We found an argument key, so take it out of the array.
unset($argv[$key]);
return $this->setArgumentValue($argv, $argument, $key, $value);
}
/**
* Set the argument's value
*
* @param array $argv
* @param Argument $argument
* @param int $key
* @param string|null $value
*
* @return array The new $argv
*/
protected function setArgumentValue($argv, $argument, $key, $value)
{
// Arguments are given the value true if they only need to
// be defined on the command line to be set.
if ($argument->noValue()) {
$argument->setValue(true);
return $argv;
}
if (is_null($value)) {
if (count($argv) === 0) {
return $argv;
}
// If the value wasn't previously defined in "key=value"
// format then define it from the next command argument.
$nextArgvValue = $argv[$key + 1];
if ($this->isValidArgumentValue($nextArgvValue)) {
$argument->setValue($nextArgvValue);
unset($argv[$key + 1]);
return $argv;
}
}
$argument->setValue($value);
return $argv;
}
/**
* Check if the value is considered a valid input value.
*
* @param $argumentValue
* @return bool
*/
protected function isValidArgumentValue($argumentValue)
{
return empty($this->findPrefixedArgument($argumentValue));
}
/**
* Search for argument in defined prefix arguments
*
* @param string $name
*
* @return Argument|false
*/
protected function findPrefixedArgument($name)
{
foreach ($this->filter->withPrefix() as $argument) {
if (in_array($name, ["-{$argument->prefix()}", "--{$argument->longPrefix()}"])) {
return $argument;
}
}
return false;
}
/**
* Pull a command name and arguments from $argv.
*
* @param array $argv
* @return array
*/
protected function getCommandAndArguments(array $argv = null)
{
// If no $argv is provided then use the global PHP defined $argv.
if (is_null($argv)) {
global $argv;
}
$arguments = $argv;
$command = array_shift($arguments);
return compact('arguments', 'command');
}
}

View File

@@ -0,0 +1,215 @@
<?php
namespace League\CLImate\Argument;
use League\CLImate\CLImate;
class Summary
{
/**
* @var \League\CLImate\CLImate $climate
*/
protected $climate;
/**
* @var string $description
*/
protected $description;
/**
* @var string $command
*/
protected $command;
/**
* @var Filter $filter
*/
protected $filter;
/**
* @param \League\CLImate\CLImate $climate
*
* @return \League\CLImate\Argument\Summary
*/
public function setClimate(CLImate $climate)
{
$this->climate = $climate;
return $this;
}
/**
* @param string $description
*
* @return \League\CLImate\Argument\Summary
*/
public function setDescription($description)
{
$this->description = $description;
return $this;
}
/**
* @param string $command
*
* @return \League\CLImate\Argument\Summary
*/
public function setCommand($command)
{
$this->command = $command;
return $this;
}
/**
* @param Filter $filter
* @param Argument[] $arguments
*
* @return \League\CLImate\Argument\Summary
*/
public function setFilter($filter, $arguments)
{
$this->filter = $filter;
$this->filter->setArguments($arguments);
return $this;
}
/**
* Output the full summary for the program
*/
public function output()
{
// Print the description if it's defined.
if ($this->description) {
$this->climate->out($this->description)->br();
}
// Print the usage statement with the arguments without a prefix at the end.
$this->climate->out("Usage: {$this->command} "
. $this->short($this->getOrderedArguments()));
// Print argument details.
foreach (['required', 'optional'] as $type) {
$this->outputArguments($this->filter->{$type}(), $type);
}
}
/**
* Build a short summary of a list of arguments.
*
* @param Argument[] $arguments
*
* @return string
*/
public function short($arguments)
{
return implode(' ', array_map([$this, 'argumentBracketed'], $arguments));
}
/**
* Build an argument's summary for use in a usage statement.
*
* For example, "-u username, --user username", "--force", or
* "-c count (default: 7)".
*
* @param Argument $argument
*
* @return string
*/
public function argument(Argument $argument)
{
$summary = $this->prefixedArguments($argument);
$printedName = mb_strstr($summary, ' ' . $argument->name());
// Print the argument name if it's not printed yet.
if (!$printedName && !$argument->noValue()) {
$summary .= $argument->name();
}
if ($defaults = $argument->defaultValue()) {
if (count($defaults) == 1) {
$summary .= " (default: {$defaults[0]})";
} else {
$summary .= ' (defaults: ' . implode(', ', $defaults) . ')';
}
}
return $summary;
}
/**
* Build argument summary surrounded by brackets
*
* @param Argument $argument
*
* @return string
*/
protected function argumentBracketed(Argument $argument)
{
return '[' . $this->argument($argument) . ']';
}
/**
* Get the arguments ordered by whether or not they have a prefix
*
* @return Argument[]
*/
protected function getOrderedArguments()
{
return array_merge($this->filter->withPrefix(), $this->filter->withoutPrefix());
}
/**
* Print out the argument list
*
* @param array $arguments
* @param string $type
*/
protected function outputArguments($arguments, $type)
{
if (count($arguments) == 0) {
return;
}
$this->climate->br()->out(mb_convert_case($type, MB_CASE_TITLE) . ' Arguments:');
foreach ($arguments as $argument) {
$this->climate->tab()->out($this->argument($argument));
if ($argument->description()) {
$this->climate->tab(2)->out($argument->description());
}
}
}
/**
* Builds the summary for any prefixed arguments
*
* @param Argument $argument
*
* @return string
*/
protected function prefixedArguments(Argument $argument)
{
$prefixes = [$argument->prefix(), $argument->longPrefix()];
$summary = [];
foreach ($prefixes as $key => $prefix) {
if (!$prefix) {
continue;
}
$sub = str_repeat('-', $key + 1) . $prefix;
if (!$argument->noValue()) {
$sub .= " {$argument->name()}";
}
$summary[] = $sub;
}
return implode(', ', $summary);
}
}