653 lines
20 KiB
PHP
653 lines
20 KiB
PHP
|
<?php
|
||
|
|
||
|
/**
|
||
|
* Class DokuCLI
|
||
|
*
|
||
|
* All DokuWiki commandline scripts should inherit from this class and implement the abstract methods.
|
||
|
*
|
||
|
* @author Andreas Gohr <andi@splitbrain.org>
|
||
|
*/
|
||
|
abstract class DokuCLI {
|
||
|
/** @var string the executed script itself */
|
||
|
protected $bin;
|
||
|
/** @var DokuCLI_Options the option parser */
|
||
|
protected $options;
|
||
|
/** @var DokuCLI_Colors */
|
||
|
public $colors;
|
||
|
|
||
|
/**
|
||
|
* constructor
|
||
|
*
|
||
|
* Initialize the arguments, set up helper classes and set up the CLI environment
|
||
|
*/
|
||
|
public function __construct() {
|
||
|
set_exception_handler(array($this, 'fatal'));
|
||
|
|
||
|
$this->options = new DokuCLI_Options();
|
||
|
$this->colors = new DokuCLI_Colors();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register options and arguments on the given $options object
|
||
|
*
|
||
|
* @param DokuCLI_Options $options
|
||
|
* @return void
|
||
|
*/
|
||
|
abstract protected function setup(DokuCLI_Options $options);
|
||
|
|
||
|
/**
|
||
|
* Your main program
|
||
|
*
|
||
|
* Arguments and options have been parsed when this is run
|
||
|
*
|
||
|
* @param DokuCLI_Options $options
|
||
|
* @return void
|
||
|
*/
|
||
|
abstract protected function main(DokuCLI_Options $options);
|
||
|
|
||
|
/**
|
||
|
* Execute the CLI program
|
||
|
*
|
||
|
* Executes the setup() routine, adds default options, initiate the options parsing and argument checking
|
||
|
* and finally executes main()
|
||
|
*/
|
||
|
public function run() {
|
||
|
if('cli' != php_sapi_name()) throw new DokuCLI_Exception('This has to be run from the command line');
|
||
|
|
||
|
// setup
|
||
|
$this->setup($this->options);
|
||
|
$this->options->registerOption(
|
||
|
'no-colors',
|
||
|
'Do not use any colors in output. Useful when piping output to other tools or files.'
|
||
|
);
|
||
|
$this->options->registerOption(
|
||
|
'help',
|
||
|
'Display this help screen and exit immeadiately.',
|
||
|
'h'
|
||
|
);
|
||
|
|
||
|
// parse
|
||
|
$this->options->parseOptions();
|
||
|
|
||
|
// handle defaults
|
||
|
if($this->options->getOpt('no-colors')) {
|
||
|
$this->colors->disable();
|
||
|
}
|
||
|
if($this->options->getOpt('help')) {
|
||
|
echo $this->options->help();
|
||
|
exit(0);
|
||
|
}
|
||
|
|
||
|
// check arguments
|
||
|
$this->options->checkArguments();
|
||
|
|
||
|
// execute
|
||
|
$this->main($this->options);
|
||
|
|
||
|
exit(0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Exits the program on a fatal error
|
||
|
*
|
||
|
* @param Exception|string $error either an exception or an error message
|
||
|
*/
|
||
|
public function fatal($error) {
|
||
|
$code = 0;
|
||
|
if(is_object($error) && is_a($error, 'Exception')) {
|
||
|
/** @var Exception $error */
|
||
|
$code = $error->getCode();
|
||
|
$error = $error->getMessage();
|
||
|
}
|
||
|
if(!$code) $code = DokuCLI_Exception::E_ANY;
|
||
|
|
||
|
$this->error($error);
|
||
|
exit($code);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print an error message
|
||
|
*
|
||
|
* @param string $string
|
||
|
*/
|
||
|
public function error($string) {
|
||
|
$this->colors->ptln("E: $string", 'red', STDERR);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print a success message
|
||
|
*
|
||
|
* @param string $string
|
||
|
*/
|
||
|
public function success($string) {
|
||
|
$this->colors->ptln("S: $string", 'green', STDERR);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Print an info message
|
||
|
*
|
||
|
* @param string $string
|
||
|
*/
|
||
|
public function info($string) {
|
||
|
$this->colors->ptln("I: $string", 'cyan', STDERR);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Class DokuCLI_Colors
|
||
|
*
|
||
|
* Handles color output on (Linux) terminals
|
||
|
*
|
||
|
* @author Andreas Gohr <andi@splitbrain.org>
|
||
|
*/
|
||
|
class DokuCLI_Colors {
|
||
|
/** @var array known color names */
|
||
|
protected $colors = array(
|
||
|
'reset' => "\33[0m",
|
||
|
'black' => "\33[0;30m",
|
||
|
'darkgray' => "\33[1;30m",
|
||
|
'blue' => "\33[0;34m",
|
||
|
'lightblue' => "\33[1;34m",
|
||
|
'green' => "\33[0;32m",
|
||
|
'lightgreen' => "\33[1;32m",
|
||
|
'cyan' => "\33[0;36m",
|
||
|
'lightcyan' => "\33[1;36m",
|
||
|
'red' => "\33[0;31m",
|
||
|
'lightred' => "\33[1;31m",
|
||
|
'purple' => "\33[0;35m",
|
||
|
'lightpurple' => "\33[1;35m",
|
||
|
'brown' => "\33[0;33m",
|
||
|
'yellow' => "\33[1;33m",
|
||
|
'lightgray' => "\33[0;37m",
|
||
|
'white' => "\33[1;37m",
|
||
|
);
|
||
|
|
||
|
/** @var bool should colors be used? */
|
||
|
protected $enabled = true;
|
||
|
|
||
|
/**
|
||
|
* Constructor
|
||
|
*
|
||
|
* Tries to disable colors for non-terminals
|
||
|
*/
|
||
|
public function __construct() {
|
||
|
if(function_exists('posix_isatty') && !posix_isatty(STDOUT)) {
|
||
|
$this->enabled = false;
|
||
|
return;
|
||
|
}
|
||
|
if(!getenv('TERM')) {
|
||
|
$this->enabled = false;
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* enable color output
|
||
|
*/
|
||
|
public function enable() {
|
||
|
$this->enabled = true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* disable color output
|
||
|
*/
|
||
|
public function disable() {
|
||
|
$this->enabled = false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Convenience function to print a line in a given color
|
||
|
*
|
||
|
* @param string $line
|
||
|
* @param string $color
|
||
|
* @param resource $channel
|
||
|
*/
|
||
|
public function ptln($line, $color, $channel = STDOUT) {
|
||
|
$this->set($color);
|
||
|
fwrite($channel, rtrim($line)."\n");
|
||
|
$this->reset();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the given color for consecutive output
|
||
|
*
|
||
|
* @param string $color one of the supported color names
|
||
|
* @throws DokuCLI_Exception
|
||
|
*/
|
||
|
public function set($color) {
|
||
|
if(!$this->enabled) return;
|
||
|
if(!isset($this->colors[$color])) throw new DokuCLI_Exception("No such color $color");
|
||
|
echo $this->colors[$color];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* reset the terminal color
|
||
|
*/
|
||
|
public function reset() {
|
||
|
$this->set('reset');
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Class DokuCLI_Options
|
||
|
*
|
||
|
* Parses command line options passed to the CLI script. Allows CLI scripts to easily register all accepted options and
|
||
|
* commands and even generates a help text from this setup.
|
||
|
*
|
||
|
* @author Andreas Gohr <andi@splitbrain.org>
|
||
|
*/
|
||
|
class DokuCLI_Options {
|
||
|
/** @var array keeps the list of options to parse */
|
||
|
protected $setup;
|
||
|
|
||
|
/** @var array store parsed options */
|
||
|
protected $options = array();
|
||
|
|
||
|
/** @var string current parsed command if any */
|
||
|
protected $command = '';
|
||
|
|
||
|
/** @var array passed non-option arguments */
|
||
|
public $args = array();
|
||
|
|
||
|
/** @var string the executed script */
|
||
|
protected $bin;
|
||
|
|
||
|
/**
|
||
|
* Constructor
|
||
|
*/
|
||
|
public function __construct() {
|
||
|
$this->setup = array(
|
||
|
'' => array(
|
||
|
'opts' => array(),
|
||
|
'args' => array(),
|
||
|
'help' => ''
|
||
|
)
|
||
|
); // default command
|
||
|
|
||
|
$this->args = $this->readPHPArgv();
|
||
|
$this->bin = basename(array_shift($this->args));
|
||
|
|
||
|
$this->options = array();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the help text for the tool itself
|
||
|
*
|
||
|
* @param string $help
|
||
|
*/
|
||
|
public function setHelp($help) {
|
||
|
$this->setup['']['help'] = $help;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register the names of arguments for help generation and number checking
|
||
|
*
|
||
|
* This has to be called in the order arguments are expected
|
||
|
*
|
||
|
* @param string $arg argument name (just for help)
|
||
|
* @param string $help help text
|
||
|
* @param bool $required is this a required argument
|
||
|
* @param string $command if theses apply to a sub command only
|
||
|
* @throws DokuCLI_Exception
|
||
|
*/
|
||
|
public function registerArgument($arg, $help, $required = true, $command = '') {
|
||
|
if(!isset($this->setup[$command])) throw new DokuCLI_Exception("Command $command not registered");
|
||
|
|
||
|
$this->setup[$command]['args'][] = array(
|
||
|
'name' => $arg,
|
||
|
'help' => $help,
|
||
|
'required' => $required
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This registers a sub command
|
||
|
*
|
||
|
* Sub commands have their own options and use their own function (not main()).
|
||
|
*
|
||
|
* @param string $command
|
||
|
* @param string $help
|
||
|
* @throws DokuCLI_Exception
|
||
|
*/
|
||
|
public function registerCommand($command, $help) {
|
||
|
if(isset($this->setup[$command])) throw new DokuCLI_Exception("Command $command already registered");
|
||
|
|
||
|
$this->setup[$command] = array(
|
||
|
'opts' => array(),
|
||
|
'args' => array(),
|
||
|
'help' => $help
|
||
|
);
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Register an option for option parsing and help generation
|
||
|
*
|
||
|
* @param string $long multi character option (specified with --)
|
||
|
* @param string $help help text for this option
|
||
|
* @param string|null $short one character option (specified with -)
|
||
|
* @param bool|string $needsarg does this option require an argument? give it a name here
|
||
|
* @param string $command what command does this option apply to
|
||
|
* @throws DokuCLI_Exception
|
||
|
*/
|
||
|
public function registerOption($long, $help, $short = null, $needsarg = false, $command = '') {
|
||
|
if(!isset($this->setup[$command])) throw new DokuCLI_Exception("Command $command not registered");
|
||
|
|
||
|
$this->setup[$command]['opts'][$long] = array(
|
||
|
'needsarg' => $needsarg,
|
||
|
'help' => $help,
|
||
|
'short' => $short
|
||
|
);
|
||
|
|
||
|
if($short) {
|
||
|
if(strlen($short) > 1) throw new DokuCLI_Exception("Short options should be exactly one ASCII character");
|
||
|
|
||
|
$this->setup[$command]['short'][$short] = $long;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Checks the actual number of arguments against the required number
|
||
|
*
|
||
|
* Throws an exception if arguments are missing. Called from parseOptions()
|
||
|
*
|
||
|
* @throws DokuCLI_Exception
|
||
|
*/
|
||
|
public function checkArguments() {
|
||
|
$argc = count($this->args);
|
||
|
|
||
|
$req = 0;
|
||
|
foreach($this->setup[$this->command]['args'] as $arg) {
|
||
|
if(!$arg['required']) break; // last required arguments seen
|
||
|
$req++;
|
||
|
}
|
||
|
|
||
|
if($req > $argc) throw new DokuCLI_Exception("Not enough arguments", DokuCLI_Exception::E_OPT_ARG_REQUIRED);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Parses the given arguments for known options and command
|
||
|
*
|
||
|
* The given $args array should NOT contain the executed file as first item anymore! The $args
|
||
|
* array is stripped from any options and possible command. All found otions can be accessed via the
|
||
|
* getOpt() function
|
||
|
*
|
||
|
* Note that command options will overwrite any global options with the same name
|
||
|
*
|
||
|
* @throws DokuCLI_Exception
|
||
|
*/
|
||
|
public function parseOptions() {
|
||
|
$non_opts = array();
|
||
|
|
||
|
$argc = count($this->args);
|
||
|
for($i = 0; $i < $argc; $i++) {
|
||
|
$arg = $this->args[$i];
|
||
|
|
||
|
// The special element '--' means explicit end of options. Treat the rest of the arguments as non-options
|
||
|
// and end the loop.
|
||
|
if($arg == '--') {
|
||
|
$non_opts = array_merge($non_opts, array_slice($this->args, $i + 1));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// '-' is stdin - a normal argument
|
||
|
if($arg == '-') {
|
||
|
$non_opts = array_merge($non_opts, array_slice($this->args, $i));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// first non-option
|
||
|
if($arg{0} != '-') {
|
||
|
$non_opts = array_merge($non_opts, array_slice($this->args, $i));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
// long option
|
||
|
if(strlen($arg) > 1 && $arg{1} == '-') {
|
||
|
list($opt, $val) = explode('=', substr($arg, 2), 2);
|
||
|
|
||
|
if(!isset($this->setup[$this->command]['opts'][$opt])) {
|
||
|
throw new DokuCLI_Exception("No such option $arg", DokuCLI_Exception::E_UNKNOWN_OPT);
|
||
|
}
|
||
|
|
||
|
// argument required?
|
||
|
if($this->setup[$this->command]['opts'][$opt]['needsarg']) {
|
||
|
if(is_null($val) && $i + 1 < $argc && !preg_match('/^--?[\w]/', $this->args[$i + 1])) {
|
||
|
$val = $this->args[++$i];
|
||
|
}
|
||
|
if(is_null($val)) {
|
||
|
throw new DokuCLI_Exception("Option $arg requires an argument", DokuCLI_Exception::E_OPT_ARG_REQUIRED);
|
||
|
}
|
||
|
$this->options[$opt] = $val;
|
||
|
} else {
|
||
|
$this->options[$opt] = true;
|
||
|
}
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
// short option
|
||
|
$opt = substr($arg, 1);
|
||
|
if(!isset($this->setup[$this->command]['short'][$opt])) {
|
||
|
throw new DokuCLI_Exception("No such option $arg", DokuCLI_Exception::E_UNKNOWN_OPT);
|
||
|
} else {
|
||
|
$opt = $this->setup[$this->command]['short'][$opt]; // store it under long name
|
||
|
}
|
||
|
|
||
|
// argument required?
|
||
|
if($this->setup[$this->command]['opts'][$opt]['needsarg']) {
|
||
|
$val = null;
|
||
|
if($i + 1 < $argc && !preg_match('/^--?[\w]/', $this->args[$i + 1])) {
|
||
|
$val = $this->args[++$i];
|
||
|
}
|
||
|
if(is_null($val)) {
|
||
|
throw new DokuCLI_Exception("Option $arg requires an argument", DokuCLI_Exception::E_OPT_ARG_REQUIRED);
|
||
|
}
|
||
|
$this->options[$opt] = $val;
|
||
|
} else {
|
||
|
$this->options[$opt] = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// parsing is now done, update args array
|
||
|
$this->args = $non_opts;
|
||
|
|
||
|
// if not done yet, check if first argument is a command and reexecute argument parsing if it is
|
||
|
if(!$this->command && $this->args && isset($this->setup[$this->args[0]])) {
|
||
|
// it is a command!
|
||
|
$this->command = array_shift($this->args);
|
||
|
$this->parseOptions(); // second pass
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get the value of the given option
|
||
|
*
|
||
|
* Please note that all options are accessed by their long option names regardless of how they were
|
||
|
* specified on commandline.
|
||
|
*
|
||
|
* Can only be used after parseOptions() has been run
|
||
|
*
|
||
|
* @param string $option
|
||
|
* @param bool|string $default what to return if the option was not set
|
||
|
* @return bool|string
|
||
|
*/
|
||
|
public function getOpt($option, $default = false) {
|
||
|
if(isset($this->options[$option])) return $this->options[$option];
|
||
|
return $default;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the found command if any
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function getCmd() {
|
||
|
return $this->command;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Builds a help screen from the available options. You may want to call it from -h or on error
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function help() {
|
||
|
$text = '';
|
||
|
|
||
|
$hascommands = (count($this->setup) > 1);
|
||
|
foreach($this->setup as $command => $config) {
|
||
|
$hasopts = (bool) $this->setup[$command]['opts'];
|
||
|
$hasargs = (bool) $this->setup[$command]['args'];
|
||
|
|
||
|
if(!$command) {
|
||
|
$text .= 'USAGE: '.$this->bin;
|
||
|
} else {
|
||
|
$text .= "\n$command";
|
||
|
}
|
||
|
|
||
|
if($hasopts) $text .= ' <OPTIONS>';
|
||
|
|
||
|
foreach($this->setup[$command]['args'] as $arg) {
|
||
|
if($arg['required']) {
|
||
|
$text .= ' <'.$arg['name'].'>';
|
||
|
} else {
|
||
|
$text .= ' [<'.$arg['name'].'>]';
|
||
|
}
|
||
|
}
|
||
|
$text .= "\n";
|
||
|
|
||
|
if($this->setup[$command]['help']) {
|
||
|
$text .= "\n";
|
||
|
$text .= $this->tableFormat(
|
||
|
array(2, 72),
|
||
|
array('', $this->setup[$command]['help']."\n")
|
||
|
);
|
||
|
}
|
||
|
|
||
|
if($hasopts) {
|
||
|
$text .= "\n OPTIONS\n\n";
|
||
|
foreach($this->setup[$command]['opts'] as $long => $opt) {
|
||
|
|
||
|
$name = '';
|
||
|
if($opt['short']) {
|
||
|
$name .= '-'.$opt['short'];
|
||
|
if($opt['needsarg']) $name .= ' <'.$opt['needsarg'].'>';
|
||
|
$name .= ', ';
|
||
|
}
|
||
|
$name .= "--$long";
|
||
|
if($opt['needsarg']) $name .= ' <'.$opt['needsarg'].'>';
|
||
|
|
||
|
$text .= $this->tableFormat(
|
||
|
array(2, 20, 52),
|
||
|
array('', $name, $opt['help'])
|
||
|
);
|
||
|
$text .= "\n";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if($hasargs) {
|
||
|
$text .= "\n";
|
||
|
foreach($this->setup[$command]['args'] as $arg) {
|
||
|
$name = '<'.$arg['name'].'>';
|
||
|
|
||
|
$text .= $this->tableFormat(
|
||
|
array(2, 20, 52),
|
||
|
array('', $name, $arg['help'])
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if($command == '' && $hascommands) {
|
||
|
$text .= "\nThis tool accepts a command as first parameter as outlined below:\n";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $text;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Safely read the $argv PHP array across different PHP configurations.
|
||
|
* Will take care on register_globals and register_argc_argv ini directives
|
||
|
*
|
||
|
* @throws DokuCLI_Exception
|
||
|
* @return array the $argv PHP array or PEAR error if not registered
|
||
|
*/
|
||
|
private function readPHPArgv() {
|
||
|
global $argv;
|
||
|
if(!is_array($argv)) {
|
||
|
if(!@is_array($_SERVER['argv'])) {
|
||
|
if(!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
|
||
|
throw new DokuCLI_Exception(
|
||
|
"Could not read cmd args (register_argc_argv=Off?)",
|
||
|
DOKU_CLI_OPTS_ARG_READ
|
||
|
);
|
||
|
}
|
||
|
return $GLOBALS['HTTP_SERVER_VARS']['argv'];
|
||
|
}
|
||
|
return $_SERVER['argv'];
|
||
|
}
|
||
|
return $argv;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Displays text in multiple word wrapped columns
|
||
|
*
|
||
|
* @param int[] $widths list of column widths (in characters)
|
||
|
* @param string[] $texts list of texts for each column
|
||
|
* @return string
|
||
|
*/
|
||
|
private function tableFormat($widths, $texts) {
|
||
|
$wrapped = array();
|
||
|
$maxlen = 0;
|
||
|
|
||
|
foreach($widths as $col => $width) {
|
||
|
$wrapped[$col] = explode("\n", wordwrap($texts[$col], $width - 1, "\n", true)); // -1 char border
|
||
|
$len = count($wrapped[$col]);
|
||
|
if($len > $maxlen) $maxlen = $len;
|
||
|
|
||
|
}
|
||
|
|
||
|
$out = '';
|
||
|
for($i = 0; $i < $maxlen; $i++) {
|
||
|
foreach($widths as $col => $width) {
|
||
|
if(isset($wrapped[$col][$i])) {
|
||
|
$val = $wrapped[$col][$i];
|
||
|
} else {
|
||
|
$val = '';
|
||
|
}
|
||
|
$out .= sprintf('%-'.$width.'s', $val);
|
||
|
}
|
||
|
$out .= "\n";
|
||
|
}
|
||
|
return $out;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Class DokuCLI_Exception
|
||
|
*
|
||
|
* The code is used as exit code for the CLI tool. This should probably be extended. Many cases just fall back to the
|
||
|
* E_ANY code.
|
||
|
*
|
||
|
* @author Andreas Gohr <andi@splitbrain.org>
|
||
|
*/
|
||
|
class DokuCLI_Exception extends Exception {
|
||
|
const E_ANY = -1; // no error code specified
|
||
|
const E_UNKNOWN_OPT = 1; //Unrecognized option
|
||
|
const E_OPT_ARG_REQUIRED = 2; //Option requires argument
|
||
|
const E_OPT_ARG_DENIED = 3; //Option not allowed argument
|
||
|
const E_OPT_ABIGUOUS = 4; //Option abiguous
|
||
|
const E_ARG_READ = 5; //Could not read argv
|
||
|
|
||
|
/**
|
||
|
* @param string $message The Exception message to throw.
|
||
|
* @param int $code The Exception code
|
||
|
* @param Exception $previous The previous exception used for the exception chaining.
|
||
|
*/
|
||
|
public function __construct($message = "", $code = 0, Exception $previous = null) {
|
||
|
if(!$code) $code = DokuCLI_Exception::E_ANY;
|
||
|
parent::__construct($message, $code, $previous);
|
||
|
}
|
||
|
}
|