Kohana v3.3.0
This commit is contained in:
62
modules/minion/README.md
Normal file
62
modules/minion/README.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Minion
|
||||
|
||||
Minion is a framework for running tasks via the CLI.
|
||||
|
||||
The system is inspired by ruckusing, which had a nice system for defining tasks but lacked the desired flexibility for kohana integration.
|
||||
|
||||
## Getting Started
|
||||
|
||||
First off, download and enable the module in your bootstrap
|
||||
|
||||
Then copy the bash script `minion` alongside your index.php (most likely the webroot).
|
||||
If you'd rather the executable be in a different location to index.php then simply modify the bash script to point to index.php.
|
||||
|
||||
You can then run minion like so:
|
||||
|
||||
./minion {task}
|
||||
|
||||
To view a list of minion tasks, run minion without any parameters, or with the `--help` option
|
||||
|
||||
./minion
|
||||
./minion --help
|
||||
|
||||
To view help for a specific minion task run
|
||||
|
||||
./minion {task} --help
|
||||
|
||||
For security reasons Minion will only run from the cli. Attempting to access it over http will cause
|
||||
a `Kohana_Exception` to be thrown.
|
||||
|
||||
If you're unable to use the binary file for whatever reason then simply replace `./minion {task}` in the above
|
||||
examples with
|
||||
|
||||
php index.php --uri=minion --task={task}
|
||||
|
||||
## Writing your own tasks
|
||||
|
||||
All minion tasks must be located in `classes/task/`. They can be in any module, thus allowing you to
|
||||
ship custom minion tasks with your own module / product.
|
||||
|
||||
Each task must extend the abstract class `Minion_Task` and implement `Minion_Task::_execute()`.
|
||||
|
||||
See `Minion_Task` for more details.
|
||||
|
||||
## Documentation
|
||||
|
||||
Code should be commented well enough not to need documentation, and minion can extract a class' doccomment to use
|
||||
as documentation on the cli.
|
||||
|
||||
## Testing
|
||||
|
||||
This module is unittested using the [unittest module](http://github.com/kohana/unittest).
|
||||
You can use the `minion` group to only run minion tests.
|
||||
|
||||
i.e.
|
||||
|
||||
phpunit --group minion
|
||||
|
||||
Feel free to contribute tests(!), they can be found in the `tests/minion` directory. :)
|
||||
|
||||
## License
|
||||
|
||||
This is licensed under the [same license as Kohana](http://kohanaframework.org/license).
|
315
modules/minion/classes/Kohana/Minion/CLI.php
Normal file
315
modules/minion/classes/Kohana/Minion/CLI.php
Normal file
@@ -0,0 +1,315 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Kohana_Minion_CLI {
|
||||
|
||||
public static $wait_msg = 'Press any key to continue...';
|
||||
|
||||
protected static $foreground_colors = array(
|
||||
'black' => '0;30',
|
||||
'dark_gray' => '1;30',
|
||||
'blue' => '0;34',
|
||||
'light_blue' => '1;34',
|
||||
'green' => '0;32',
|
||||
'light_green' => '1;32',
|
||||
'cyan' => '0;36',
|
||||
'light_cyan' => '1;36',
|
||||
'red' => '0;31',
|
||||
'light_red' => '1;31',
|
||||
'purple' => '0;35',
|
||||
'light_purple' => '1;35',
|
||||
'brown' => '0;33',
|
||||
'yellow' => '1;33',
|
||||
'light_gray' => '0;37',
|
||||
'white' => '1;37',
|
||||
);
|
||||
protected static $background_colors = array(
|
||||
'black' => '40',
|
||||
'red' => '41',
|
||||
'green' => '42',
|
||||
'yellow' => '43',
|
||||
'blue' => '44',
|
||||
'magenta' => '45',
|
||||
'cyan' => '46',
|
||||
'light_gray' => '47',
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns one or more command-line options. Options are specified using
|
||||
* standard CLI syntax:
|
||||
*
|
||||
* php index.php --username=john.smith --password=secret --var="some value with spaces"
|
||||
*
|
||||
* // Get the values of "username" and "password"
|
||||
* $auth = Minion_CLI::options('username', 'password');
|
||||
*
|
||||
* @param string $options,... option name
|
||||
* @return array
|
||||
*/
|
||||
public static function options($options = NULL)
|
||||
{
|
||||
// Get all of the requested options
|
||||
$options = func_get_args();
|
||||
|
||||
// Found option values
|
||||
$values = array();
|
||||
|
||||
// Skip the first option, it is always the file executed
|
||||
for ($i = 1; $i < $_SERVER['argc']; $i++)
|
||||
{
|
||||
if ( ! isset($_SERVER['argv'][$i]))
|
||||
{
|
||||
// No more args left
|
||||
break;
|
||||
}
|
||||
|
||||
// Get the option
|
||||
$opt = $_SERVER['argv'][$i];
|
||||
|
||||
if (substr($opt, 0, 2) !== '--')
|
||||
{
|
||||
// This is a positional argument
|
||||
$values[] = $opt;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove the "--" prefix
|
||||
$opt = substr($opt, 2);
|
||||
|
||||
if (strpos($opt, '='))
|
||||
{
|
||||
// Separate the name and value
|
||||
list ($opt, $value) = explode('=', $opt, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
$value = NULL;
|
||||
}
|
||||
|
||||
$values[$opt] = $value;
|
||||
}
|
||||
|
||||
if ($options)
|
||||
{
|
||||
foreach ($values as $opt => $value)
|
||||
{
|
||||
if ( ! in_array($opt, $options))
|
||||
{
|
||||
// Set the given value
|
||||
unset($values[$opt]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count($options) == 1 ? array_pop($values) : $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads input from the user. This can have either 1 or 2 arguments.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* // Waits for any key press
|
||||
* Minion_CLI::read();
|
||||
*
|
||||
* // Takes any input
|
||||
* $color = Minion_CLI::read('What is your favorite color?');
|
||||
*
|
||||
* // Will only accept the options in the array
|
||||
* $ready = Minion_CLI::read('Are you ready?', array('y','n'));
|
||||
*
|
||||
* @param string $text text to show user before waiting for input
|
||||
* @param array $options array of options the user is shown
|
||||
* @return string the user input
|
||||
*/
|
||||
public static function read($text = '', array $options = NULL)
|
||||
{
|
||||
// If a question has been asked with the read
|
||||
$options_output = '';
|
||||
if ( ! empty($options))
|
||||
{
|
||||
$options_output = ' [ '.implode(', ', $options).' ]';
|
||||
}
|
||||
|
||||
fwrite(STDOUT, $text.$options_output.': ');
|
||||
|
||||
// Read the input from keyboard.
|
||||
$input = trim(fgets(STDIN));
|
||||
|
||||
// If options are provided and the choice is not in the array, tell them to try again
|
||||
if ( ! empty($options) && ! in_array($input, $options))
|
||||
{
|
||||
Minion_CLI::write('This is not a valid option. Please try again.');
|
||||
|
||||
$input = Minion_CLI::read($text, $options);
|
||||
}
|
||||
|
||||
// Read the input
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Experimental feature.
|
||||
*
|
||||
* Reads hidden input from the user
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* $password = Minion_CLI::password('Enter your password');
|
||||
*
|
||||
* @author Mathew Davies.
|
||||
* @return string
|
||||
*/
|
||||
public static function password($text = '')
|
||||
{
|
||||
$text .= ': ';
|
||||
|
||||
if (Kohana::$is_windows)
|
||||
{
|
||||
$vbscript = sys_get_temp_dir().'Minion_CLI_Password.vbs';
|
||||
|
||||
// Create temporary file
|
||||
file_put_contents($vbscript, 'wscript.echo(InputBox("'.addslashes($text).'"))');
|
||||
|
||||
$password = shell_exec('cscript //nologo '.escapeshellarg($command));
|
||||
|
||||
// Remove temporary file.
|
||||
unlink($vbscript);
|
||||
}
|
||||
else
|
||||
{
|
||||
$password = shell_exec('/usr/bin/env bash -c \'read -s -p "'.escapeshellcmd($text).'" var && echo $var\'');
|
||||
}
|
||||
|
||||
Minion_CLI::write();
|
||||
|
||||
return trim($password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a string to the cli. If you send an array it will implode them
|
||||
* with a line break.
|
||||
*
|
||||
* @param string|array $text the text to output, or array of lines
|
||||
*/
|
||||
public static function write($text = '')
|
||||
{
|
||||
if (is_array($text))
|
||||
{
|
||||
foreach ($text as $line)
|
||||
{
|
||||
Minion_CLI::write($line);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fwrite(STDOUT, $text.PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a replacable line to the cli. You can continue replacing the
|
||||
* line until `TRUE` is passed as the second parameter in order to indicate
|
||||
* you are done modifying the line.
|
||||
*
|
||||
* // Sample progress indicator
|
||||
* Minion_CLI::write_replace('0%');
|
||||
* Minion_CLI::write_replace('25%');
|
||||
* Minion_CLI::write_replace('50%');
|
||||
* Minion_CLI::write_replace('75%');
|
||||
* // Done writing this line
|
||||
* Minion_CLI::write_replace('100%', TRUE);
|
||||
*
|
||||
* @param string $text the text to output
|
||||
* @param boolean $end_line whether the line is done being replaced
|
||||
*/
|
||||
public static function write_replace($text = '', $end_line = FALSE)
|
||||
{
|
||||
// Append a newline if $end_line is TRUE
|
||||
$text = $end_line ? $text.PHP_EOL : $text;
|
||||
fwrite(STDOUT, "\r\033[K".$text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits a certain number of seconds, optionally showing a wait message and
|
||||
* waiting for a key press.
|
||||
*
|
||||
* @author Fuel Development Team
|
||||
* @license MIT License
|
||||
* @copyright 2010 - 2011 Fuel Development Team
|
||||
* @link http://fuelphp.com
|
||||
* @param int $seconds number of seconds
|
||||
* @param bool $countdown show a countdown or not
|
||||
*/
|
||||
public static function wait($seconds = 0, $countdown = false)
|
||||
{
|
||||
if ($countdown === true)
|
||||
{
|
||||
$time = $seconds;
|
||||
|
||||
while ($time > 0)
|
||||
{
|
||||
fwrite(STDOUT, $time.'... ');
|
||||
sleep(1);
|
||||
$time--;
|
||||
}
|
||||
|
||||
Minion_CLI::write();
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($seconds > 0)
|
||||
{
|
||||
sleep($seconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
Minion_CLI::write(Minion_CLI::$wait_msg);
|
||||
Minion_CLI::read();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given text with the correct color codes for a foreground and
|
||||
* optionally a background color.
|
||||
*
|
||||
* @author Fuel Development Team
|
||||
* @license MIT License
|
||||
* @copyright 2010 - 2011 Fuel Development Team
|
||||
* @link http://fuelphp.com
|
||||
* @param string $text the text to color
|
||||
* @param atring $foreground the foreground color
|
||||
* @param string $background the background color
|
||||
* @return string the color coded string
|
||||
*/
|
||||
public static function color($text, $foreground, $background = null)
|
||||
{
|
||||
|
||||
if (Kohana::$is_windows)
|
||||
{
|
||||
return $text;
|
||||
}
|
||||
|
||||
if (!array_key_exists($foreground, Minion_CLI::$foreground_colors))
|
||||
{
|
||||
throw new Kohana_Exception('Invalid CLI foreground color: '.$foreground);
|
||||
}
|
||||
|
||||
if ($background !== null and !array_key_exists($background, Minion_CLI::$background_colors))
|
||||
{
|
||||
throw new Kohana_Exception('Invalid CLI background color: '.$background);
|
||||
}
|
||||
|
||||
$string = "\033[".Minion_CLI::$foreground_colors[$foreground]."m";
|
||||
|
||||
if ($background !== null)
|
||||
{
|
||||
$string .= "\033[".Minion_CLI::$background_colors[$background]."m";
|
||||
}
|
||||
|
||||
$string .= $text."\033[0m";
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
}
|
64
modules/minion/classes/Kohana/Minion/Exception.php
Normal file
64
modules/minion/classes/Kohana/Minion/Exception.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Minipn exception
|
||||
*
|
||||
* @package Kohana
|
||||
* @category Minion
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009-2011 Kohana Team
|
||||
* @license http://kohanaframework.org/license
|
||||
*/
|
||||
class Kohana_Minion_Exception extends Kohana_Exception {
|
||||
/**
|
||||
* Inline exception handler, displays the error message, source of the
|
||||
* exception, and the stack trace of the error.
|
||||
*
|
||||
* Should this display a stack trace? It's useful.
|
||||
*
|
||||
* Should this still log? Maybe not as useful since we'll see the error on the screen.
|
||||
*
|
||||
* @uses Kohana_Exception::text
|
||||
* @param Exception $e
|
||||
* @return boolean
|
||||
*/
|
||||
public static function handler(Exception $e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if ($e instanceof Minion_Exception)
|
||||
{
|
||||
echo $e->format_for_cli();
|
||||
}
|
||||
else
|
||||
{
|
||||
echo Kohana_Exception::text($e);
|
||||
}
|
||||
|
||||
$exit_code = $e->getCode();
|
||||
|
||||
// Never exit "0" after an exception.
|
||||
if ($exit_code == 0)
|
||||
{
|
||||
$exit_code = 1;
|
||||
}
|
||||
|
||||
exit($exit_code);
|
||||
}
|
||||
catch (Exception $e)
|
||||
{
|
||||
// Clean the output buffer if one exists
|
||||
ob_get_level() and ob_clean();
|
||||
|
||||
// Display the exception text
|
||||
echo Kohana_Exception::text($e), "\n";
|
||||
|
||||
// Exit with an error status
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public function format_for_cli()
|
||||
{
|
||||
return Kohana_Exception::text($e);
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Invalid Task Exception
|
||||
*
|
||||
* @package Kohana
|
||||
* @category Minion
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009-2011 Kohana Team
|
||||
* @license http://kohanaframework.org/license
|
||||
*/
|
||||
class Kohana_Minion_Exception_InvalidTask extends Minion_Exception {
|
||||
|
||||
public function format_for_cli()
|
||||
{
|
||||
return 'ERROR: '. $this->getMessage().PHP_EOL;
|
||||
}
|
||||
|
||||
}
|
364
modules/minion/classes/Kohana/Minion/Task.php
Normal file
364
modules/minion/classes/Kohana/Minion/Task.php
Normal file
@@ -0,0 +1,364 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Interface that all minion tasks must implement
|
||||
*
|
||||
* @package Kohana
|
||||
* @category Minion
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009-2011 Kohana Team
|
||||
* @license http://kohanaframework.org/license
|
||||
*/
|
||||
abstract class Kohana_Minion_Task {
|
||||
|
||||
/**
|
||||
* The separator used to separate different levels of tasks
|
||||
* @var string
|
||||
*/
|
||||
public static $task_separator = ':';
|
||||
|
||||
/**
|
||||
* Converts a task (e.g. db:migrate to a class name)
|
||||
*
|
||||
* @param string Task name
|
||||
* @return string Class name
|
||||
*/
|
||||
public static function convert_task_to_class_name($task)
|
||||
{
|
||||
$task = trim($task);
|
||||
|
||||
if (empty($task))
|
||||
return '';
|
||||
|
||||
return 'Task_'.implode('_', array_map('ucfirst', explode(Minion_Task::$task_separator, $task)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the task name of a task class / task object
|
||||
*
|
||||
* @param string|Minion_Task The task class / object
|
||||
* @return string The task name
|
||||
*/
|
||||
public static function convert_class_to_task($class)
|
||||
{
|
||||
if (is_object($class))
|
||||
{
|
||||
$class = get_class($class);
|
||||
}
|
||||
|
||||
return strtolower(str_replace('_', Minion_Task::$task_separator, substr($class, 5)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory for loading minion tasks
|
||||
*
|
||||
* @param array An array of command line options. It should contain the 'task' key
|
||||
* @throws Minion_Exception_InvalidTask
|
||||
* @return Minion_Task The Minion task
|
||||
*/
|
||||
public static function factory($options)
|
||||
{
|
||||
if (($task = Arr::get($options, 'task')) !== NULL)
|
||||
{
|
||||
unset($options['task']);
|
||||
}
|
||||
else if (($task = Arr::get($options, 0)) !== NULL)
|
||||
{
|
||||
// The first positional argument (aka 0) may be the task name
|
||||
unset($options[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If we didn't get a valid task, generate the help
|
||||
$task = 'help';
|
||||
}
|
||||
|
||||
$class = Minion_Task::convert_task_to_class_name($task);
|
||||
|
||||
if ( ! class_exists($class))
|
||||
{
|
||||
throw new Minion_Exception_InvalidTask(
|
||||
"Task ':task' is not a valid minion task",
|
||||
array(':task' => $class)
|
||||
);
|
||||
}
|
||||
|
||||
$class = new $class;
|
||||
|
||||
if ( ! $class instanceof Minion_Task)
|
||||
{
|
||||
throw new Minion_Exception_InvalidTask(
|
||||
"Task ':task' is not a valid minion task",
|
||||
array(':task' => $class)
|
||||
);
|
||||
}
|
||||
|
||||
$class->set_options($options);
|
||||
|
||||
// Show the help page for this task if requested
|
||||
if (array_key_exists('help', $options))
|
||||
{
|
||||
$class->_method = '_help';
|
||||
}
|
||||
|
||||
return $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of options this task accepts and their default values.
|
||||
*
|
||||
* protected $_options = array(
|
||||
* 'limit' => 4,
|
||||
* 'table' => NULL,
|
||||
* );
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_options = array();
|
||||
|
||||
/**
|
||||
* Populated with the accepted options for this task.
|
||||
* This array is automatically populated based on $_options.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_accepted_options = array();
|
||||
|
||||
protected $_method = '_execute';
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
// Populate $_accepted_options based on keys from $_options
|
||||
$this->_accepted_options = array_keys($this->_options);
|
||||
}
|
||||
|
||||
/**
|
||||
* The file that get's passes to Validation::errors() when validation fails
|
||||
* @var string|NULL
|
||||
*/
|
||||
protected $_errors_file = 'validation';
|
||||
|
||||
/**
|
||||
* Gets the task name for the task
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
static $task_name = NULL;
|
||||
|
||||
if ($task_name === NULL)
|
||||
{
|
||||
$task_name = Minion_Task::convert_class_to_task($this);
|
||||
}
|
||||
|
||||
return $task_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets options for this task
|
||||
*
|
||||
* $param array the array of options to set
|
||||
* @return this
|
||||
*/
|
||||
public function set_options(array $options)
|
||||
{
|
||||
foreach ($options as $key => $value)
|
||||
{
|
||||
$this->_options[$key] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the options that were passed into this task with their defaults
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_options()
|
||||
{
|
||||
return (array) $this->_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a set of options that this task can accept
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_accepted_options()
|
||||
{
|
||||
return (array) $this->_accepted_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds any validation rules/labels for validating _options
|
||||
*
|
||||
* public function build_validation(Validation $validation)
|
||||
* {
|
||||
* return parent::build_validation($validation)
|
||||
* ->rule('paramname', 'not_empty'); // Require this param
|
||||
* }
|
||||
*
|
||||
* @param Validation the validation object to add rules to
|
||||
*
|
||||
* @return Validation
|
||||
*/
|
||||
public function build_validation(Validation $validation)
|
||||
{
|
||||
// Add a rule to each key making sure it's in the task
|
||||
foreach ($validation->as_array() as $key => $value)
|
||||
{
|
||||
$validation->rule($key, array($this, 'valid_option'), array(':validation', ':field'));
|
||||
}
|
||||
|
||||
return $validation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns $_errors_file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_errors_file()
|
||||
{
|
||||
return $this->_errors_file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the task with the specified set of options
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
$options = $this->get_options();
|
||||
|
||||
// Validate $options
|
||||
$validation = Validation::factory($options);
|
||||
$validation = $this->build_validation($validation);
|
||||
|
||||
if ( $this->_method != '_help' AND ! $validation->check())
|
||||
{
|
||||
echo View::factory('minion/error/validation')
|
||||
->set('task', Minion_Task::convert_class_to_task($this))
|
||||
->set('errors', $validation->errors($this->get_errors_file()));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Finally, run the task
|
||||
$method = $this->_method;
|
||||
echo $this->{$method}($options);
|
||||
}
|
||||
}
|
||||
|
||||
abstract protected function _execute(array $params);
|
||||
|
||||
/**
|
||||
* Outputs help for this task
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function _help(array $params)
|
||||
{
|
||||
$tasks = $this->_compile_task_list(Kohana::list_files('classes/task'));
|
||||
|
||||
$inspector = new ReflectionClass($this);
|
||||
|
||||
list($description, $tags) = $this->_parse_doccomment($inspector->getDocComment());
|
||||
|
||||
$view = View::factory('minion/help/task')
|
||||
->set('description', $description)
|
||||
->set('tags', (array) $tags)
|
||||
->set('task', Minion_Task::convert_class_to_task($this));
|
||||
|
||||
echo $view;
|
||||
}
|
||||
|
||||
|
||||
public function valid_option(Validation $validation, $option)
|
||||
{
|
||||
if ( ! in_array($option, $this->_accepted_options))
|
||||
{
|
||||
$validation->error($option, 'minion_option');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a doccomment, extracting both the comment and any tags associated
|
||||
*
|
||||
* Based on the code in Kodoc::parse()
|
||||
*
|
||||
* @param string The comment to parse
|
||||
* @return array First element is the comment, second is an array of tags
|
||||
*/
|
||||
protected function _parse_doccomment($comment)
|
||||
{
|
||||
// Normalize all new lines to \n
|
||||
$comment = str_replace(array("\r\n", "\n"), "\n", $comment);
|
||||
|
||||
// Remove the phpdoc open/close tags and split
|
||||
$comment = array_slice(explode("\n", $comment), 1, -1);
|
||||
|
||||
// Tag content
|
||||
$tags = array();
|
||||
|
||||
foreach ($comment as $i => $line)
|
||||
{
|
||||
// Remove all leading whitespace
|
||||
$line = preg_replace('/^\s*\* ?/m', '', $line);
|
||||
|
||||
// Search this line for a tag
|
||||
if (preg_match('/^@(\S+)(?:\s*(.+))?$/', $line, $matches))
|
||||
{
|
||||
// This is a tag line
|
||||
unset($comment[$i]);
|
||||
|
||||
$name = $matches[1];
|
||||
$text = isset($matches[2]) ? $matches[2] : '';
|
||||
|
||||
$tags[$name] = $text;
|
||||
}
|
||||
else
|
||||
{
|
||||
$comment[$i] = (string) $line;
|
||||
}
|
||||
}
|
||||
|
||||
$comment = trim(implode("\n", $comment));
|
||||
|
||||
return array($comment, $tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a list of available tasks from a directory structure
|
||||
*
|
||||
* @param array Directory structure of tasks
|
||||
* @param string prefix
|
||||
* @return array Compiled tasks
|
||||
*/
|
||||
protected function _compile_task_list(array $files, $prefix = '')
|
||||
{
|
||||
$output = array();
|
||||
|
||||
foreach ($files as $file => $path)
|
||||
{
|
||||
$file = substr($file, strrpos($file, DIRECTORY_SEPARATOR) + 1);
|
||||
|
||||
if (is_array($path) AND count($path))
|
||||
{
|
||||
$task = $this->_compile_task_list($path, $prefix.$file.Minion_Task::$task_separator);
|
||||
|
||||
if ($task)
|
||||
{
|
||||
$output = array_merge($output, $task);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$output[] = strtolower($prefix.substr($file, 0, -strlen(EXT)));
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
3
modules/minion/classes/Minion/CLI.php
Normal file
3
modules/minion/classes/Minion/CLI.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Minion_CLI extends Kohana_Minion_CLI {}
|
3
modules/minion/classes/Minion/Exception.php
Normal file
3
modules/minion/classes/Minion/Exception.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Minion_Exception extends Kohana_Minion_Exception {}
|
3
modules/minion/classes/Minion/Exception/InvalidTask.php
Normal file
3
modules/minion/classes/Minion/Exception/InvalidTask.php
Normal file
@@ -0,0 +1,3 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Minion_Exception_InvalidTask extends Kohana_Minion_Exception_InvalidTask {}
|
8
modules/minion/classes/Minion/Task.php
Normal file
8
modules/minion/classes/Minion/Task.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
/**
|
||||
* Interface that all minion tasks must implement
|
||||
*/
|
||||
abstract class Minion_Task extends Kohana_Minion_Task {
|
||||
|
||||
}
|
28
modules/minion/classes/Task/Help.php
Normal file
28
modules/minion/classes/Task/Help.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
/**
|
||||
* Help task to display general instructons and list all tasks
|
||||
*
|
||||
* @package Kohana
|
||||
* @category Helpers
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009-2011 Kohana Team
|
||||
* @license http://kohanaframework.org/license
|
||||
*/
|
||||
class Task_Help extends Minion_Task
|
||||
{
|
||||
/**
|
||||
* Generates a help list for all tasks
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function _execute(array $params)
|
||||
{
|
||||
$tasks = $this->_compile_task_list(Kohana::list_files('classes/Task'));
|
||||
|
||||
$view = new View('minion/help/list');
|
||||
|
||||
$view->tasks = $tasks;
|
||||
|
||||
echo $view;
|
||||
}
|
||||
}
|
13
modules/minion/config/userguide.php
Normal file
13
modules/minion/config/userguide.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
return array
|
||||
(
|
||||
'modules' => array(
|
||||
'minion' => array(
|
||||
'enabled' => TRUE,
|
||||
'name' => 'Minion',
|
||||
'description' => 'Minion is a simple command line task runner',
|
||||
'copyright' => '© 2009-2011 Kohana Team',
|
||||
)
|
||||
)
|
||||
);
|
3
modules/minion/guide/minion/index.md
Normal file
3
modules/minion/guide/minion/index.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Minion
|
||||
|
||||
Minion is a simple command line task runner.
|
3
modules/minion/guide/minion/menu.md
Normal file
3
modules/minion/guide/minion/menu.md
Normal file
@@ -0,0 +1,3 @@
|
||||
## [Minion]()
|
||||
- [Setup](setup)
|
||||
- [Writing a Task](tasks)
|
32
modules/minion/guide/minion/setup.md
Normal file
32
modules/minion/guide/minion/setup.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Minion Setup
|
||||
|
||||
To use minion, you'll need to make a small change to your index.php file:
|
||||
|
||||
-/**
|
||||
- * Execute the main request. A source of the URI can be passed, eg: $_SERVER['PATH_INFO'].
|
||||
- * If no source is specified, the URI will be automatically detected.
|
||||
- */
|
||||
-echo Request::factory()
|
||||
- ->execute()
|
||||
- ->send_headers(TRUE)
|
||||
- ->body();
|
||||
+if (PHP_SAPI == 'cli') // Try and load minion
|
||||
+{
|
||||
+ class_exists('Minion_Task') OR die('minion required!');
|
||||
+ set_exception_handler(array('Kohana_Minion_Exception_Handler', 'handler'));
|
||||
+
|
||||
+ Minion_Task::factory(Minion_CLI::options())->execute();
|
||||
+}
|
||||
+else
|
||||
+{
|
||||
+ /**
|
||||
+ * Execute the main request. A source of the URI can be passed, eg: $_SERVER['PATH_INFO'].
|
||||
+ * If no source is specified, the URI will be automatically detected.
|
||||
+ */
|
||||
+ echo Request::factory()
|
||||
+ ->execute()
|
||||
+ ->send_headers(TRUE)
|
||||
+ ->body();
|
||||
+}
|
||||
|
||||
This will short-circuit your index file to intercept any cli calls, and route them to the minion module.
|
71
modules/minion/guide/minion/tasks.md
Normal file
71
modules/minion/guide/minion/tasks.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Writing Tasks
|
||||
|
||||
Writing a task in minion is very easy. Simply create a new class called `Task_<Taskname>` and put it inside `classes/task/<taskname>.php`.
|
||||
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
class Task_Demo extends Minion_Task
|
||||
{
|
||||
protected $_defaults = array(
|
||||
'foo' = 'bar',
|
||||
'bar' => NULL,
|
||||
);
|
||||
|
||||
/**
|
||||
* This is a demo task
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
protected function _execute(array $params)
|
||||
{
|
||||
var_dump($params);
|
||||
echo 'foobar';
|
||||
}
|
||||
}
|
||||
|
||||
You'll notice a few things here:
|
||||
|
||||
- You need a main `_execute()` method. It should take one array parameter.
|
||||
- This parameter contains any command line options passed to the task.
|
||||
- For example, if you call the task above with `./minion --task=demo --foo=foobar` then `$params` will contain: `array('foo' => 'foobar', 'bar' => NULL)`
|
||||
- It needs to have a `protected $_defaults` array. This is a list of parameters you want to accept for this task. Any parameters passed to the task not in this list will be rejected.
|
||||
|
||||
## Namespacing Tasks
|
||||
|
||||
You can "namespace" tasks by placing them all in a subdirectory: `classes/task/database/generate.php`. This task will be named `database:generate` and can be called with this task name.
|
||||
|
||||
# Parameter Validations
|
||||
|
||||
To add validations to your command line options, simply overload the `build_validation()` method in your task:
|
||||
|
||||
public function build_validation(Validation $validation)
|
||||
{
|
||||
return parent::build_validation($validation)
|
||||
->rule('foo', 'not_empty') // Require this param
|
||||
->rule('bar', 'numeric'); // This param should be numeric
|
||||
}
|
||||
|
||||
These validations will run for every task call unless `--help` is passed to the task.
|
||||
|
||||
# Task Help
|
||||
|
||||
Tasks can have built-in help. Minion will read class docblocks that you specify:
|
||||
|
||||
<?php defined('SYSPATH') or die('No direct script access.');
|
||||
|
||||
/**
|
||||
* This is a demo task.
|
||||
*
|
||||
* It can accept the following options:
|
||||
* - foo: this parameter does something. It is required.
|
||||
* - bar: this parameter does something else. It should be numeric.
|
||||
*
|
||||
* @package Kohana
|
||||
* @category Helpers
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009-2011 Kohana Team
|
||||
* @license http://kohanaframework.org/license
|
||||
*/
|
||||
class Minion_Task_Demo extends Minion_Task
|
||||
|
||||
The `@` tags in the class comments will also be displayed in a human readable format. When writing your task comments, you should specify how to use it, and any parameters it accepts.
|
5
modules/minion/messages/validation.php
Normal file
5
modules/minion/messages/validation.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
return array(
|
||||
'minion_option' => ':field is not a valid option for this task!',
|
||||
);
|
4
modules/minion/minion
Normal file
4
modules/minion/minion
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
include __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'index.php';
|
68
modules/minion/miniond
Normal file
68
modules/minion/miniond
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This script is similar to minion but will do a few additional things:
|
||||
# - PHP process is run in the background
|
||||
# - PHP process is monitored and restarted if it exits for any reason
|
||||
# - Added handlers for SUGHUP, SIGINT, and SIGTERM
|
||||
#
|
||||
# This is meant for long running minion tasks (like background workers).
|
||||
# Shutting down the minion tasks is done by sending a SIGINT or SIGTERM signal
|
||||
# to this miniond process. You can also restart the minion task by sending a
|
||||
# SUGHUP signal to this process. It's useful to restart all your workers when
|
||||
# deploying new code so that the workers reload their code as well.
|
||||
# You cannot use this script for tasks that require user input because of the
|
||||
# PHP process running in the background.
|
||||
#
|
||||
# Usage: ./miniond [task:name] [--option1=optval1 --option2=optval2]
|
||||
#
|
||||
# And so on.
|
||||
#
|
||||
|
||||
# Define some functions
|
||||
function start_daemon()
|
||||
{
|
||||
echo "Starting"
|
||||
./minion $ARGS & # This assumes miniond is sitting next to minion
|
||||
pid=$! # Store pid (globally) for later use..
|
||||
}
|
||||
|
||||
function stop_daemon()
|
||||
{
|
||||
kill -TERM $pid
|
||||
wait $pid # Wait for the task to exit and store the exit code
|
||||
ecode=$? # Store exit code (globally) for later use..
|
||||
}
|
||||
|
||||
function handle_SIGHUP()
|
||||
{
|
||||
echo "Restarting"
|
||||
stop_daemon
|
||||
start_daemon
|
||||
}
|
||||
|
||||
function handle_SIGTERM_SIGINT()
|
||||
{
|
||||
echo "Shutting Down"
|
||||
stop_daemon
|
||||
exit $ecode
|
||||
}
|
||||
|
||||
# Register signal handlers
|
||||
trap handle_SIGHUP SIGHUP
|
||||
trap handle_SIGTERM_SIGINT SIGTERM SIGINT
|
||||
|
||||
ARGS=$@
|
||||
|
||||
start_daemon
|
||||
|
||||
while :
|
||||
do
|
||||
# Pauses the script until $pid is dead
|
||||
wait $pid
|
||||
|
||||
# Make sure someone didn't start it back up already (SIGHUP handler does this)
|
||||
if ! kill -0 $pid > /dev/null 2>&1
|
||||
then
|
||||
start_daemon
|
||||
fi
|
||||
done
|
70
modules/minion/tests/minion/task.php
Normal file
70
modules/minion/tests/minion/task.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Test case for Minion_Util
|
||||
*
|
||||
* @package Kohana/Minion
|
||||
* @group kohana
|
||||
* @group kohana.minion
|
||||
* @category Test
|
||||
* @author Kohana Team
|
||||
* @copyright (c) 2009-2012 Kohana Team
|
||||
* @license http://kohanaframework.org/license
|
||||
*/
|
||||
|
||||
class Minion_TaskTest extends Kohana_Unittest_TestCase
|
||||
{
|
||||
/**
|
||||
* Provides test data for test_convert_task_to_class_name()
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function provider_convert_task_to_class_name()
|
||||
{
|
||||
return array(
|
||||
array('Task_Db_Migrate', 'db:migrate'),
|
||||
array('Task_Db_Status', 'db:status'),
|
||||
array('', ''),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a task can be converted to a class name
|
||||
*
|
||||
* @test
|
||||
* @covers Minion_Task::convert_task_to_class_name
|
||||
* @dataProvider provider_convert_task_to_class_name
|
||||
* @param string Expected class name
|
||||
* @param string Input task name
|
||||
*/
|
||||
public function test_convert_task_to_class_name($expected, $task_name)
|
||||
{
|
||||
$this->assertSame($expected, Minion_Task::convert_task_to_class_name($task_name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides test data for test_convert_class_to_task()
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function provider_convert_class_to_task()
|
||||
{
|
||||
return array(
|
||||
array('db:migrate', 'Task_Db_Migrate'),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the task name can be found from a class name / object
|
||||
*
|
||||
* @test
|
||||
* @covers Minion_Task::convert_class_to_task
|
||||
* @dataProvider provider_convert_class_to_task
|
||||
* @param string Expected task name
|
||||
* @param mixed Input class
|
||||
*/
|
||||
public function test_convert_class_to_task($expected, $class)
|
||||
{
|
||||
$this->assertSame($expected, Minion_Task::convert_class_to_task($class));
|
||||
}
|
||||
}
|
10
modules/minion/views/minion/error/validation.php
Normal file
10
modules/minion/views/minion/error/validation.php
Normal file
@@ -0,0 +1,10 @@
|
||||
Parameter Errors:
|
||||
<?php foreach ($errors as $parameter => $error): ?>
|
||||
<?php echo $parameter; ?> - <?php echo $error; ?>
|
||||
<?php endforeach; ?>
|
||||
|
||||
Run
|
||||
|
||||
php index.php --task=<?php echo $task?> --help
|
||||
|
||||
for more help
|
7
modules/minion/views/minion/help/error.php
Normal file
7
modules/minion/views/minion/help/error.php
Normal file
@@ -0,0 +1,7 @@
|
||||
<?php echo $error; ?>
|
||||
|
||||
Run
|
||||
|
||||
index.php --uri=minion
|
||||
|
||||
for more help
|
17
modules/minion/views/minion/help/list.php
Normal file
17
modules/minion/views/minion/help/list.php
Normal file
@@ -0,0 +1,17 @@
|
||||
Minion is a cli tool for performing tasks
|
||||
|
||||
Usage
|
||||
|
||||
<?php echo $_SERVER['argv'][0]; ?> {task} --task=[options]
|
||||
|
||||
Where {task} is one of the following:
|
||||
|
||||
<?php foreach($tasks as $task): ?>
|
||||
* <?php echo $task; ?>
|
||||
|
||||
<?php endforeach; ?>
|
||||
|
||||
For more information on what a task does and usage details execute
|
||||
|
||||
<?php echo $_SERVER['argv'][0]; ?> --task={task} --help
|
||||
|
17
modules/minion/views/minion/help/task.php
Normal file
17
modules/minion/views/minion/help/task.php
Normal file
@@ -0,0 +1,17 @@
|
||||
|
||||
Usage
|
||||
=======
|
||||
php minion.php --task=<?php echo $task; ?> [--option1=value1] [--option2=value2]
|
||||
|
||||
Details
|
||||
=======
|
||||
<?php foreach($tags as $tag_name => $tag_content): ?>
|
||||
<?php echo ucfirst($tag_name) ?>: <?php echo $tag_content ?>
|
||||
|
||||
<?php endforeach; ?>
|
||||
|
||||
Description
|
||||
===========
|
||||
<?php echo $description; ?>
|
||||
|
||||
|
Reference in New Issue
Block a user