Code cleanup, better examples, tasks added

This commit is contained in:
Chris 2017-01-24 14:42:43 +01:00
parent 4cd5cc2620
commit 809c04abfa
7 changed files with 220 additions and 12 deletions

View File

@ -5,6 +5,25 @@ Changelog and Upgrade Instructions
Major changes:
* The `Shell::EV_*` constants have been renamed to `Shell::EVT_*`.
* The `configure` method has been removed.
* Events are now dispatched using the *NoccyLabs/TinyEvent* library.
* 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*)
* When calling `Context->addCommand()` you can now use both `help`
and `descr` as the last argument to provide the description text
for the command. This change is intended to make `addCommand` and
the doccomment `@descr` more similar.
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

@ -30,7 +30,7 @@ $myShell->setPrompt("test>");
$ctx = $myShell->createContext("root");
$ctx->addCommand("hello", function () {
echo "Hello World!\n";
});
}, [ 'descr'=>'Say hello' ]);
// Run the shell
$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();

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

@ -132,6 +132,7 @@ class Context
$info = $this->commandInfo[$command];
$args = array_key_exists("args",$info)?$info['args']:"";
$help = array_key_exists("help",$info)?$info['help']:"";
$help = $help?:(array_key_exists("descr",$info)?$info['descr']:"");
$ret[trim("{$command} {$args}")] = $help;
}
return $ret;

View File

@ -13,12 +13,14 @@ class Shell
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_NO_COMMAND = "shell.command.missing"; // no such command found
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
@ -34,10 +36,10 @@ class Shell
protected $contextStack = [];
/**
* @var object[] Created timers
*/
*/
protected $timers = [];
/**
* @var object[] Running subtasks
/**
* @var TaskInterface[] Created tasks
*/
protected $tasks = [];
/**
@ -186,6 +188,37 @@ class Shell
});
}
/**
* 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
*
@ -268,7 +301,19 @@ class Shell
*/
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;
}
@ -278,11 +323,31 @@ 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;
}
@ -330,12 +395,12 @@ class Shell
}
}
ksort($ghelp);
//printf("Commands in current context:\n");
printf("Commands in current context:\n");
foreach ($help as $command=>$info) {
printf(" %-20s %s\n", $command, $info);
}
if (count($ghelp)) {
//printf("\nImported from parent contexts:\n");
printf("\nImported from parent contexts:\n");
foreach ($ghelp as $command=>$info) {
printf(" %-20s %s\n", $command, $info);
}
@ -411,8 +476,14 @@ class Shell
foreach ($this->timers as $timer) {
$timer->update();
}
foreach ($this->tasks as $task) {
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);
@ -423,11 +494,13 @@ class Shell
if (trim($output)) {
$this->lineReader->erase();
echo rtrim($output)."\n";
$this->lineReader->redraw();
if ($this->running)
$this->lineReader->redraw();
}
if (!$this->context) {
$this->stop();
break;
}
if ($buffer) {
@ -456,6 +529,7 @@ class Shell
$data['context'] = $this->context;
$event = new Event($type, $data);
$this->emitEvent($type, $event);
return $event;
}
/**

10
lib/TaskInterface.php Normal file
View File

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