11 Commits

Author SHA1 Message Date
e5b328b822 Spiced up the help 2017-01-27 04:30:50 +01:00
cee82fc740 Added getInput() method for basic async input 2017-01-25 21:49:26 +01:00
c7b1a637c2 Removed descr from command props 2017-01-24 21:13:20 +01:00
809c04abfa Code cleanup, better examples, tasks added 2017-01-24 14:42:43 +01:00
4cd5cc2620 Comments and improvements 2017-01-24 00:49:13 +01:00
5a45ca9c46 Rewrite, cleanup and bugfixes
* More events added, constants cleaned up
* Events now handled using noccylabs/tinyevent
* Fixed magenta/cyan mixup in style
* Fixed LineRead not resetting history pointer on command
2017-01-23 23:28:12 +01:00
b6727bed80 Fixed timer implementation 2017-01-22 23:22:13 +01:00
0e5a25567c Added exception handling to run() 2016-11-21 01:26:06 +01:00
bdec60717f Improved the cursor in LineRead when using history 2016-11-19 15:46:13 +01:00
43a6475192 Added an execute() method to context to catch unhandled commands 2016-11-19 14:18:53 +01:00
7bfd8453e7 Improved the context stack 2016-11-15 03:29:00 +01:00
15 changed files with 637 additions and 125 deletions

25
CHANGELOG.md Normal file
View File

@ -0,0 +1,25 @@
Changelog and Upgrade Instructions
==================================
## 0.2.x to 0.3.x
Major changes:
* The `Shell::EV_*` constants have been renamed to `Shell::EVT_*`.
* The `configure` method has been removed. If you really need to
extend the `Shell` class, use the constructor to configure your
shell (see *examples/timers.php*)
* Events are now dispatched using the *NoccyLabs/TinyEvent* library.
The main difference here is in how the event object received. The
shell instance is now passed in the object, and you can provide any
userdata to be passed on to the handler when you add the listener.
* The event `Shell::EVT_BAD_COMMAND` will be fired if a command can
not be found, assuming `Context->execute()` does not accept the
command. (see *examples/commandevents.php*)
New features:
* Tasks can now be added and removed from the shell. Tasks will receive
a call to their `update()` method once per main loop, until they are
removed or return false from the `isValid()` method. Tasks need to
implement the `TaskInterface` interface. (see *examples/tasks.php*)

View File

@ -0,0 +1,12 @@
NoccyLabs Shell Core
====================
This library helps make elegant command line applications that spawn an isolated shell.
It uses a standalone implementation for buffered input with support for arrow keys to
navigate the history and more.
Note that this library requirements a fully ANSI compatible terminal with UTF-8 support
in order to use colors, control the cursor position etc. As it uses `stty` to configure
input buffering, it will likely not work on Windows.

View File

@ -13,5 +13,8 @@
"psr-4": {
"NoccyLabs\\Shell\\": "lib/"
}
},
"require": {
"noccylabs/tinyevent": "~0.1.1"
}
}

36
examples/basic.php Normal file
View File

@ -0,0 +1,36 @@
<?php
/*
* Basic shell example, demonstrates adding a listener to update the prompt with
* the current time as well as creating an "anonymous" context and adding a
* command to it. It also shows how to change the style of the prompt.
*
*/
require_once __DIR__."/../vendor/autoload.php";
use NoccyLabs\Shell\Shell;
use NoccyLabs\Shell\Style;
use NoccyLabs\Shell\Context;
$myShell = new Shell();
// Set the style of prompt and input
$myShell->setPromptStyle(new Style(Style::BR_GREEN, Style::GREEN));
$myShell->setInputStyle(new Style(Style::BR_CYAN));
// Add a listener to update the prompt
$myShell->addListener(Shell::EVT_UPDATE_PROMPT, function ($e) {
$e->shell->setPrompt(date("H:i:s").">");
});
// Set the initial prompt, not really needed.
$myShell->setPrompt("test>");
// Create an anonymous context and add a command
$ctx = $myShell->createContext("root");
$ctx->addCommand("hello", function () {
echo "Hello World!\n";
}, [ 'help'=>'Say hello' ]);
// Run the shell
$myShell->run();

20
examples/catchall.php Normal file
View File

@ -0,0 +1,20 @@
<?php
require_once __DIR__."/../vendor/autoload.php";
use NoccyLabs\Shell\Shell;
use NoccyLabs\Shell\Context;
class CatchAllContext extends Context
{
public function execute($cmd, ...$arg)
{
printf("Executing: %s %s\n", $cmd, join(" ",$arg));
return true;
}
}
$myShell = new Shell();
$myShell->setPrompt("test>");
$myShell->pushContext(new CatchAllContext());
$myShell->run();

View File

@ -0,0 +1,44 @@
<?php
/*
* This example demonstrates how to use events to catch commands that
* have not been handled by any context or builtin.
*
*/
require_once __DIR__."/../vendor/autoload.php";
use NoccyLabs\Shell\Shell;
use NoccyLabs\Shell\Style;
use NoccyLabs\Shell\Context;
$myShell = new Shell();
// Add a listeners for various command aspects
$myShell->addListener(Shell::EVT_BAD_COMMAND, function ($e) {
echo "EVT_BAD_COMMAND:\n";
printf("| command: %s\n", $e->command);
printf("|_args: %s\n", join(" ",$e->args));
});
$myShell->addListener(Shell::EVT_BEFORE_COMMAND, function ($e) {
echo "EVT_BEFORE_COMMAND:\n";
printf("| command: %s\n", $e->command);
printf("|_args: %s\n", join(" ",$e->args));
});
$myShell->addListener(Shell::EVT_AFTER_COMMAND, function ($e) {
echo "EVT_AFTER_COMMAND:\n";
printf("| command: %s\n", $e->command);
printf("| args: %s\n", join(" ",$e->args));
printf("|_type: %s\n", $e->type);
});
// Set the initial prompt, not really needed.
$myShell->setPrompt("test>");
// Create an anonymous context and add a command
$ctx = $myShell->createContext("root");
$ctx->addCommand("hello", function () {
echo "Hello World!\n";
});
// Run the shell
$myShell->run();

19
examples/errors.php Normal file
View File

@ -0,0 +1,19 @@
<?php
require_once __DIR__."/../vendor/autoload.php";
use NoccyLabs\Shell\Shell;
use NoccyLabs\Shell\Context;
class CatchAllContext extends Context
{
public function execute($cmd, ...$arg)
{
throw new \Exception("Uh-oh! Error!");
}
}
$myShell = new Shell();
$myShell->setPrompt("test>");
$myShell->pushContext(new CatchAllContext());
$myShell->run();

31
examples/input.php Normal file
View File

@ -0,0 +1,31 @@
<?php
/*
* This example demonstrates how to use events to catch commands that
* have not been handled by any context or builtin.
*
*/
require_once __DIR__."/../vendor/autoload.php";
use NoccyLabs\Shell\Shell;
use NoccyLabs\Shell\Style;
use NoccyLabs\Shell\Context;
$myShell = new Shell();
// Set the initial prompt, not really needed.
$myShell->setPrompt("test>");
// Create an anonymous context and add a command
$ctx = $myShell->createContext("root");
$ctx->addCommand("hello", function () use ($myShell) {
$myShell->getInput("What is your name?", function ($name) use ($myShell) {
echo "Hello, {$name}\n";
$myShell->getInput("Who is your daddy and what does he do?", function ($daddy) {
echo "{$daddy}? Oookay...\n";
});
});
});
// Run the shell
$myShell->run();

60
examples/tasks.php Normal file
View File

@ -0,0 +1,60 @@
<?php
/*
* This example demonstrates how to use events to catch commands that
* have not been handled by any context or builtin.
*
*/
require_once __DIR__."/../vendor/autoload.php";
use NoccyLabs\Shell\Shell;
use NoccyLabs\Shell\Style;
use NoccyLabs\Shell\Context;
use NoccyLabs\Shell\TaskInterface;
class MyTask implements TaskInterface
{
protected $start;
protected $last;
public function __construct()
{
$this->start = time();
}
public function update()
{
if ($this->last < time()) {
echo date("H:i:s")."\n";
$this->last = time();
}
}
public function isValid()
{
return (time() - $this->start < 5);
}
}
$myShell = new Shell();
$myShell->addListener(Shell::EVT_TASK_CREATED, function ($e) {
echo "EVT_TASK_CREATED:\n";
printf("|_task: %s\n", get_class($e->task));
});
$myShell->addListener(Shell::EVT_TASK_DESTROYED, function ($e) {
echo "EVT_TASK_DESTROYED:\n";
printf("|_task: %s\n", get_class($e->task));
});
$myShell->addTask(new MyTask());
// Set the initial prompt, not really needed.
$myShell->setPrompt("test>");
// Create an anonymous context and add a command
$ctx = $myShell->createContext("root");
$ctx->addCommand("hello", function () {
echo "Hello World!\n";
});
// Run the shell
$myShell->run();

View File

@ -24,15 +24,21 @@ class MyShell extends Shell
protected $seq = 0;
protected function configure()
public function __construct()
{
$context = new MyContext();
$this->pushContext($context);
$this->updatePrompt();
$this->addTimer(5000, function () {
$t1 = $this->addTimer(5000, function () {
echo "5 seconds\n";
});
$app = $this;
$t2 = $this->addTimer(15000, function () use ($t1, $app) {
echo "Removing timers...\n";
$app->removeTimer($t1);
});
}
protected function updatePrompt()

View File

@ -23,6 +23,11 @@ class Context
$this->configure();
}
public function getContextInfo()
{
return null;
}
public function setShell(Shell $shell)
{
$this->shell = $shell;
@ -141,6 +146,23 @@ class Context
return array_key_exists('global', $info);
}
/**
* Catch-all handler for commands not defined in context, globally or builtin.
* Override this function and return true if the command is handled ok.
*
* @param string $command The command to execute
* @param string[] $args The arguments to the command
* @return bool True if the command was handled
*/
public function execute($command, ...$args)
{
return false;
}
/**
* Get the name of the context
*
*/
public function getName()
{
return $this->name;

View File

@ -2,6 +2,10 @@
namespace NoccyLabs\Shell;
/**
* This is a readline-like implementation in pure PHP.
*
*/
class LineRead
{
@ -127,6 +131,7 @@ class LineRead
array_unshift($this->history, $this->buffer);
$this->buffer = null;
$this->posCursor = 0;
$this->posHistory = 0;
printf("\n\r");
$this->state = self::STATE_IDLE;
}
@ -161,22 +166,33 @@ class LineRead
if ($this->posHistory == 0) {
$this->stashedBuffer = $this->buffer;
}
if ($this->posCursor == strlen($this->buffer)) {
$this->posCursor = -1;
}
if ($this->posHistory < count($this->history)) {
$this->posHistory++;
$this->buffer = $this->history[$this->posHistory-1];
$this->redraw();
}
if ($this->posCursor == -1) {
$this->posCursor = strlen($this->buffer);
}
$this->redraw();
break;
case "\e[B": // down
if ($this->posCursor == strlen($this->buffer)) {
$this->posCursor = -1;
}
if ($this->posHistory > 1) {
$this->posHistory--;
$this->buffer = $this->history[$this->posHistory-1];
$this->redraw();
} elseif ($this->posHistory > 0) {
$this->posHistory--;
$this->buffer = $this->stashedBuffer;
$this->redraw();
}
if ($this->posCursor == -1) {
$this->posCursor = strlen($this->buffer);
}
$this->redraw();
break;
default:
fprintf(STDERR, "\n%s\n", substr($code,1));

View File

@ -3,32 +3,91 @@
namespace NoccyLabs\Shell;
use NoccyLabs\Shell\LineRead;
use NoccyLabs\TinyEvent\EventEmitterTrait;
use NoccyLabs\TinyEvent\Event;
class Shell
{
const EV_PROMPT = "prompt";
const EV_COMMAND = "command";
use EventEmitterTrait;
const EVT_UPDATE_PROMPT = "shell.prompt"; // called to update the prompt
const EVT_BEFORE_COMMAND = "shell.command.before"; // before a command is executed
const EVT_AFTER_COMMAND = "shell.command.after"; // after a command is executed
const EVT_BAD_COMMAND = "shell.command.bad"; // no such command found
const EVT_CONTEXT_CHANGED = "shell.context"; // a new context is activated
const EVT_SHELL_START = "shell.start"; // the shell is about to start
const EVT_SHELL_STOP = "shell.stop"; // the shell is about to exit
const EVT_SHELL_ABORT = "shell.abort"; // the shell was aborted (ctrl-c)
const EVT_SHELL_ESCAPE = "shell.escape"; // escape key pressed
const EVT_TASK_CREATED = "task.created"; // a task was created
const EVT_TASK_DESTROYED = "task.destryed"; // a task was removed or invalidated
/**
* @var LineRead The lineread instance
*/
protected $lineReader = null;
/**
* @var Context The current context
*/
protected $context = null;
/**
* @var Context[] The stack of parent contexts
*/
protected $contextStack = [];
protected $listeners = [];
/**
* @var object[] Created timers
*/
protected $timers = [];
/**
* @var TaskInterface[] Created tasks
*/
protected $tasks = [];
/**
* @var string The prompt string
*/
protected $prompt = ">";
/**
* @var Style The style applied to the prompt
*/
protected $prompt_style = null;
/**
* @var Style The style applied to the input text
*/
protected $input_style = null;
/**
* @var callable The callback to pass the input on to
*/
protected $input_callback = null;
/**
* @var string Question prompt for getInput()
*/
protected $input_prompt = null;
/**
* @var string The prompt before changing to $input_prompt
*/
protected $input_last_prompt = null;
/**
* Constructor
*
*/
public function __construct()
{
$this->configure();
$t = $this;
register_shutdown_function(function () use (&$t) {
if ($t) unset($t);
});
}
protected function configure()
/**
* Destructor
*
*/
public function __destruct()
{
if ($this->lineReader) {
$this->lineReader = null;
}
}
/**
@ -44,7 +103,7 @@ class Shell
}
$context->setShell($this);
$this->context = $context;
$this->dispatchEvent("context.update", [ "context"=>$this->context ]);
$this->dispatchEvent(self::EVT_CONTEXT_CHANGED);
}
/**
@ -60,7 +119,7 @@ class Shell
} else {
$this->context = null;
}
$this->dispatchEvent("context.update", [ "context"=>$this->context ]);
$this->dispatchEvent(self::EVT_CONTEXT_CHANGED);
return $previous;
}
@ -81,43 +140,19 @@ class Shell
return join($separator,$stack);
}
public function createContext()
/**
* Create a new empty context and push it on the stack.
*
* @param string $name The name of the context to create
* @return Context The created context
*/
public function createContext($name)
{
$context = new Context();
$context = new Context($name);
$this->pushContext($context);
return $context;
}
/**
* Add a handler to be called when an event fires.
*
* @param string $event
* @param callable $handler
*/
public function addListener($event, callable $handler)
{
if (!array_key_exists($event,$this->listeners)) {
$this->listeners[$event] = [];
}
$this->listeners[$event][] = $handler;
}
/**
* Invoke event handlers for event.
*
* @param string $event
* @param callable $handler
*/
protected function dispatchEvent($event, array $data=[])
{
if (!array_key_exists($event,$this->listeners)) {
return;
}
foreach ($this->listeners[$event] as $handler) {
call_user_func($handler, (object)$data, $this);
}
}
/**
* Add a callback to be called at a regular interval during the update phase.
* Adding timers with a low interval (less than 200 i.e. 0.2s) may have an
@ -137,6 +172,7 @@ class Shell
private $userdata;
public function __construct($interval, callable $handler, array $userdata) {
$this->interval = $interval / 1000;
$this->next = microtime(true) + $this->interval;
$this->handler = $handler;
$this->userdata = $userdata;
}
@ -157,11 +193,49 @@ class Shell
*
* @param Timer $timer
*/
public function removeTimer(Timer $timer)
public function removeTimer($timer)
{
$this->timers = array_filter($this->timers, function ($v) use ($timer) {
return ($v !== $timer);
});
}
/**
* Add a task to be update():d in the main loop.
*
* @param TaskInterface $task The task to add
*/
public function addTask(TaskInterface $task)
{
if ($this->dispatchEvent(self::EVT_TASK_CREATED, [
'task' => $task
])->isPropagationStopped()) {
return;
}
$this->tasks[] = $task;
}
/**
* Remove a previously added task. This can also be done by the task returning
* false from its isValid() method.
*
* @param TaskInterface $task The task to remove
*/
public function removeTask(TaskInterface $task)
{
$this->tasks = array_filter($this->tasks, function ($item) use ($task) {
return ($item != $task);
});
$this->dispatchEvent(self::EVT_TASK_DESTROYED, [
'task' => $task
]);
}
/**
* Set the prompt text
*
* @param string $text The text
*/
public function setPrompt($text)
{
$this->prompt = $text;
@ -171,6 +245,34 @@ class Shell
}
}
/**
* Set the prompt style
*
* @param Style $style The style to apply to the prompt
*/
public function setPromptStyle(Style $style)
{
$this->prompt_style = $style;
if ($this->lineReader) {
$this->lineReader->setPromptStyle($style);
}
}
/**
* Set the input style
*
* @param Style $style The style to apply to the text
*/
public function setInputStyle(Style $style)
{
$this->input_style = $style;
if ($this->lineReader) {
$this->lineReader->setCommandStyle($style);
}
}
/**
* Find a command and return a closure.
*
@ -205,13 +307,25 @@ class Shell
* Execute a command with arguments.
*
* @param string $command The command name to execute
* @param string $args Arguments
* @param string ...$args Arguments
* @return mixed
* @throws Exception\BadCommandExcception
*/
public function executeCommand($command, ...$args)
{
if ($this->dispatchEvent(self::EVT_BEFORE_COMMAND, [
'command' => $command,
'args' => $args
])->isPropagationStopped()) {
return true;
}
if ($this->executeBuiltin($command, ...$args)) {
$this->dispatchEvent(self::EVT_AFTER_COMMAND, [
'command' => $command,
'args' => $args,
'type' => 'builtin'
]);
return;
}
@ -221,22 +335,55 @@ class Shell
if ($ret instanceof Context) {
$this->pushContext($ret);
}
$this->dispatchEvent(self::EVT_AFTER_COMMAND, [
'command' => $command,
'args' => $args,
'type' => 'command'
]);
return;
}
// Call 'execute' on the current context
if ($this->context->execute($command, ...$args)) {
$this->dispatchEvent(self::EVT_AFTER_COMMAND, [
'command' => $command,
'args' => $args,
'type' => 'execute'
]);
return;
}
// Fire the EVT_BAD_COMMAND event and return if the event propagation
// has been stopped.
$evt = $this->dispatchEvent(self::EVT_BAD_COMMAND, [
'command'=>$command,
'args'=>$args
]);
if ($evt->isPropagationStopped()) {
return;
}
// Throw error if the command could not be found
throw new Exception\BadCommandException("Command {$command} not found");
}
/**
* Execute a built-in command
*
* @param string $command Command name
* @param mixed ...$args Arguments
* @return bool True if the command was handled OK
*/
public function executeBuiltin($command, ...$args)
{
switch ($command) {
case '.':
$type = basename(strtr(get_class($this->context), "\\", "/"));
printf("%s<%s>: %s\n", $type, $this->context->getName(), json_encode($this->context->getData()));
printf("%s<%s>: %s\n", $type, $this->context->getName(), $this->context->getContextInfo());
$level = 0;
foreach ($this->contextStack as $context) {
$type = basename(strtr(get_class($context), "\\", "/"));
printf(" %s- %s<%s>\n", str_repeat(" ",$level++), $type, $context->getName());
printf(" %s└─%s<%s>: %s\n", str_repeat(" ",$level++), $type, $context->getName(), $context->getContextInfo());
}
break;
case '..':
@ -260,19 +407,25 @@ class Shell
}
}
ksort($ghelp);
printf("Commands in current context:\n");
printf("\e[1mCommands:\e[0m\n");
foreach ($help as $command=>$info) {
printf(" %-20s %s\n", $command, $info);
if (strpos($command," ")!==false) {
list($command,$args) = explode(" ",$command,2);
} else $args=null;
$command = sprintf("\e[97m%s \e[90m%s\e[0m", $command,$args);
printf(" %s - \e[3;36m%s\e[0m\n", $command, $info);
}
if (count($ghelp)) {
printf("\nImported from parent contexts:\n");
foreach ($ghelp as $command=>$info) {
printf(" %-20s %s\n", $command, $info);
}
printf("\n\e[0mCommands from parent contexts:\e[0m\n");
if (strpos($command," ")!==false) {
list($command,$args) = explode(" ",$command,2);
} else $args=null;
$command = sprintf("\e[97m%s \e[90m%s\e[0m", $command,$args);
printf(" %s - \e[3;36m%s\e[0m\n", $command, $info);
}
printf("\nGlobal commands:\n");
printf(" %-20s %s\n", "exit", "Leave the shell");
printf(" %-20s %s\n", "..", "Discard the current context and go to parent");
printf(" \e[97m%s\e[0;36;3m - %s\e[0m\n", "exit", "Leave the shell");
printf(" \e[97m%s\e[0;36;3m - %s\e[0m\n", "..", "Discard the current context and go to parent");
break;
case 'exit':
$this->stop();
@ -299,31 +452,25 @@ class Shell
} catch (Exception\ShellException $e) {
echo "\e[31;91;1m{$e->getMessage()}\e[0m\n";
}
/*
if (array_key_exists($commandName, $this->commands)) {
$command = $this->commands[$commandName];
if ($command instanceof Command) {
$command->run($buffer);
} else {
call_user_func_array($command,$buffer);
}
return;
}
$this->writeln("Bad command: ".$commandName);
*/
}
/**
* Start the shell
*
*/
public function run()
{
try {
$this->lineReader = new LineRead();
$this->lineReader->setPromptText($this->prompt);
$this->lineReader->setPromptStyle(new Style(Style::BR_GREEN));
$this->lineReader->setCommandStyle(new Style(Style::GREEN));
$this->lineReader->setPromptStyle($this->prompt_style?:new Style(Style::BR_GREEN));
$this->lineReader->setCommandStyle($this->input_style?:new Style(Style::GREEN));
$this->running = true;
$this->dispatchEvent("prompt", [ "context"=>$this->context ]);
$this->dispatchEvent(self::EVT_UPDATE_PROMPT);
$this->dispatchEvent(self::EVT_SHELL_START);
while ($this->running) {
// Update the input stuff, sleep if nothing to do.
@ -332,12 +479,12 @@ class Shell
}
// Escape is handy too...
if ($buffer == "\e") {
$this->dispatchEvent("shell.abort");
$this->dispatchEvent(self::EVT_SHELL_ESCAPE);
continue;
}
// we get a ^C on ^C, so deal with the ^C.
if ($buffer == "\x03") {
$this->dispatchEvent("shell.stop");
$this->dispatchEvent(self::EVT_SHELL_ABORT);
$this->stop();
continue;
}
@ -347,6 +494,15 @@ class Shell
foreach ($this->timers as $timer) {
$timer->update();
}
foreach ($this->tasks as $taskidx=>$task) {
$task->update();
if (!$task->isValid()) {
$this->dispatchEvent(self::EVT_TASK_DESTROYED, [
'task' => $task
]);
unset($this->tasks[$taskidx]);
}
}
if ($buffer) {
$this->executeBuffer($buffer);
}
@ -356,22 +512,73 @@ class Shell
if (trim($output)) {
$this->lineReader->erase();
echo rtrim($output)."\n";
if ($this->running)
$this->lineReader->redraw();
}
if (!$this->context) {
$this->stop();
break;
}
if ($buffer) {
$this->dispatchEvent("prompt", [ "context"=>$this->context ]);
$this->dispatchEvent(self::EVT_UPDATE_PROMPT);
}
}
} catch (\Exception $e) {
fprintf(STDERR, "\e[31;1mFatal: Unhandled exception\e[0m\n\n%s\n", $e);
}
$this->dispatchEvent(self::EVT_SHELL_STOP);
$this->lineReader = null;
}
/**
* Helper function to emit an event with shell instance and context included.
*
* @param string $type The event type
* @param array[] $data The userdata of the event
*/
private function dispatchEvent($type, array $data=[])
{
$data['shell'] = $this;
$data['context'] = $this->context;
$event = new Event($type, $data);
$this->emitEvent($type, $event);
return $event;
}
public function getInput($prompt, callable $callback)
{
$this->addListener(Shell::EVT_UPDATE_PROMPT, [ $this, "onInputPrompt" ], $prompt);
$this->addListener(Shell::EVT_BEFORE_COMMAND, [ $this, "onInputHandler" ], $this->prompt, $callback);
}
public function onInputPrompt(Event $e, $prompt)
{
$this->setPrompt($prompt);
$e->stopPropagation();
$this->removeListener(Shell::EVT_UPDATE_PROMPT, [ $this, "onInputPrompt" ], $prompt);
}
public function onInputHandler(Event $e, $last_prompt, $callback)
{
// Restore the prompt
$this->setPrompt($last_prompt);
// Remove the listeners and compose the result string
$this->removeListener(Shell::EVT_BEFORE_COMMAND, [ $this, "onInputHandler" ], $last_prompt, $callback);
$input = trim($e->command." ".join(" ",$e->args));
$e->stopPropagation();
// Call the callback
call_user_func($callback, $input);
}
/**
* Stop the shell; calling this method will cause the main run() to return.
*
*/
public function stop()
{
$this->running = false;

View File

@ -11,16 +11,17 @@ class Style
const GREEN = 2;
const YELLOW = 3;
const BLUE = 4;
const CYAN = 5;
const MAGENTA = 6;
const MAGENTA = 5;
const CYAN = 6;
const WHITE = 7;
const GRAY = 8;
const BR_RED = 9;
const BR_GREEN = 10;
const BR_YELLOW = 11;
const BR_BLUE = 12;
const BR_CYAN = 13;
const BR_MAGENTA = 14;
const BR_MAGENTA = 13;
const BR_CYAN = 14;
const BR_WHITE = 15;
const A_INTENSE = 1;

10
lib/TaskInterface.php Normal file
View File

@ -0,0 +1,10 @@
<?php
namespace NoccyLabs\Shell;
interface TaskInterface
{
public function update();
public function isValid();
}