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
This commit is contained in:
		
							
								
								
									
										144
									
								
								lib/Shell.php
									
									
									
									
									
								
							
							
						
						
									
										144
									
								
								lib/Shell.php
									
									
									
									
									
								
							@@ -3,32 +3,50 @@
 | 
			
		||||
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_NO_COMMAND        = "shell.command.missing"; // 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
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @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 = [];
 | 
			
		||||
    
 | 
			
		||||
    protected $timers = [];
 | 
			
		||||
 | 
			
		||||
    protected $tasks = [];
 | 
			
		||||
 | 
			
		||||
    protected $prompt = ">";
 | 
			
		||||
 | 
			
		||||
    protected $prompt_style = null;
 | 
			
		||||
 | 
			
		||||
    protected $input_style = null;
 | 
			
		||||
 | 
			
		||||
    public function __construct()
 | 
			
		||||
    {
 | 
			
		||||
        $this->configure();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function configure()
 | 
			
		||||
    {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -44,7 +62,7 @@ class Shell
 | 
			
		||||
        }
 | 
			
		||||
        $context->setShell($this);
 | 
			
		||||
        $this->context = $context;
 | 
			
		||||
        $this->dispatchEvent("context.update", [ "context"=>$this->context ]);
 | 
			
		||||
        $this->dispatchEvent(self::EVT_CONTEXT_CHANGED);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -60,7 +78,7 @@ class Shell
 | 
			
		||||
        } else {
 | 
			
		||||
            $this->context = null;
 | 
			
		||||
        }
 | 
			
		||||
        $this->dispatchEvent("context.update", [ "context"=>$this->context ]);
 | 
			
		||||
        $this->dispatchEvent(self::EVT_CONTEXT_CHANGED);
 | 
			
		||||
        return $previous;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -81,43 +99,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
 | 
			
		||||
@@ -174,6 +168,24 @@ class Shell
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setPromptStyle(Style $style)
 | 
			
		||||
    {
 | 
			
		||||
        $this->prompt_style = $style;
 | 
			
		||||
 | 
			
		||||
        if ($this->lineReader) {
 | 
			
		||||
            $this->lineReader->setPromptStyle($style);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function setInputStyle(Style $style)
 | 
			
		||||
    {
 | 
			
		||||
        $this->input_style = $style;
 | 
			
		||||
 | 
			
		||||
        if ($this->lineReader) {
 | 
			
		||||
            $this->lineReader->setCommandStyle($style);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Find a command and return a closure.
 | 
			
		||||
     *
 | 
			
		||||
@@ -269,12 +281,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);
 | 
			
		||||
                    }
 | 
			
		||||
@@ -308,18 +320,6 @@ 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);
 | 
			
		||||
        */
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function run()
 | 
			
		||||
@@ -328,12 +328,13 @@ class Shell
 | 
			
		||||
            $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.
 | 
			
		||||
@@ -342,12 +343,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;
 | 
			
		||||
                }
 | 
			
		||||
@@ -357,6 +358,9 @@ class Shell
 | 
			
		||||
                foreach ($this->timers as $timer) {
 | 
			
		||||
                    $timer->update();
 | 
			
		||||
                }
 | 
			
		||||
                foreach ($this->tasks as $task) {
 | 
			
		||||
                    $task->update();
 | 
			
		||||
                }
 | 
			
		||||
                if ($buffer) {
 | 
			
		||||
                    $this->executeBuffer($buffer);
 | 
			
		||||
                }
 | 
			
		||||
@@ -374,7 +378,7 @@ class Shell
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if ($buffer) {
 | 
			
		||||
                    $this->dispatchEvent("prompt", [ "context"=>$this->context ]);
 | 
			
		||||
                    $this->dispatchEvent(self::EVT_UPDATE_PROMPT);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -382,10 +386,20 @@ class Shell
 | 
			
		||||
            fprintf(STDERR, "\e[31;1mFatal: Unhandled exception\e[0m\n\n%s\n", $e);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $this->dispatchEvent(self::EVT_SHELL_STOP);
 | 
			
		||||
 | 
			
		||||
        $this->lineReader = null;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function dispatchEvent($type, array $data=[])
 | 
			
		||||
    {
 | 
			
		||||
        $data['shell'] = $this;
 | 
			
		||||
        $data['context'] = $this->context;
 | 
			
		||||
        $event = new Event($type, $data);
 | 
			
		||||
        $this->emitEvent($type, $event);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function stop()
 | 
			
		||||
    {
 | 
			
		||||
        $this->running = false;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user