diff --git a/README.md b/README.md index 499f04a..2d71574 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Below is an example demonstrating how to use the PHP Prompts library to create i 1. **Initialize** ```php -$prompt = new MaplePHP\Prompts\Prompt(); +$prompt = new \MaplePHP\Prompts\Prompt(); ``` 2. **Set the Title and Description for the Prompts** @@ -278,7 +278,7 @@ These prompt types enable a variety of user interactions in a CLI environment, m You also execute some of the prompt above command directly with the Command class. ```php -$command = new MaplePHP\Prompts\Command(); +$command = new \MaplePHP\Prompts\Command(); // Print messages and return input $input = $command->message("Text field", true); $input = $command->mask("Masked input (password)"); @@ -302,7 +302,7 @@ $command->approve("Print red error message"); The `progress` method of the `Command` class allows displaying a progress bar with customizable sleep intervals to indicate ongoing operations. ```php -$command = new MaplePHP\Prompts\Command(); +$command = new \MaplePHP\Prompts\Command(); $command->progress(1, 100, function($i, $length) { return 20; }); diff --git a/composer.json b/composer.json index 84d768b..2766e5d 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ }, "autoload": { "psr-4": { - "MaplePHP\\Prompts\\": "" + "MaplePHP\\Prompts\\": "src" } }, "minimum-stability": "dev" diff --git a/examples/example.php b/examples/example.php index b2f673d..13adcc2 100755 --- a/examples/example.php +++ b/examples/example.php @@ -23,9 +23,8 @@ require_once("{$dir}/vendor/autoload.php"); -use MaplePHP\Prompts\Prompt; use MaplePHP\Prompts\Command; -use MaplePHP\Prompts\SttyWrapper; +use MaplePHP\Prompts\Prompt; $command = new Command(); $inp = new Prompt(); @@ -123,7 +122,7 @@ }); print_r($prompt); -} catch (\MaplePHP\Prompts\PromptException $e) { +} catch (\MaplePHP\Prompts\Exceptions\PromptException $e) { $command->error($e->getMessage()); } diff --git a/Command.php b/src/Command.php similarity index 96% rename from Command.php rename to src/Command.php index 8362346..6bc19ae 100755 --- a/Command.php +++ b/src/Command.php @@ -3,9 +3,10 @@ namespace MaplePHP\Prompts; use Exception; +use InvalidArgumentException; use MaplePHP\Http\Interfaces\StreamInterface; use MaplePHP\Http\Stream; -use InvalidArgumentException; +use MaplePHP\Prompts\Themes\Ansi; /** * Class Command @@ -20,11 +21,24 @@ class Command public function __construct(?StreamInterface $stream = null) { // This is the stream we want to use, 9/10 times - $this->stream = is_null($stream) ? new Stream(Stream::STDOUT) : $stream; + $this->stream = $stream === null ? new Stream(Stream::STDOUT) : $stream; $this->stty = new SttyWrapper(); $this->ansi = new Ansi(); } + /** + * With stream + * + * @param StreamInterface $stream + * @return $this + */ + public function withStream(StreamInterface $stream): self + { + $new = clone $this; + $new->stream = $stream; + return $new; + } + /** * Get stream * @@ -360,7 +374,7 @@ protected function showMenu(int $selIndex, array $items): void protected function write(string $message, bool $break = true): void { $this->stream->write($message); - if($break) { + if ($break) { $this->stream->write("\n"); } } diff --git a/PromptException.php b/src/Exceptions/PromptException.php similarity index 63% rename from PromptException.php rename to src/Exceptions/PromptException.php index 1db6182..a758fe6 100755 --- a/PromptException.php +++ b/src/Exceptions/PromptException.php @@ -1,6 +1,6 @@ prevVal, $this->index); - if($items === false) { + if ($items === false) { return true; } } - if(!is_array($items)) { + if (!is_array($items)) { throw new InvalidArgumentException("The items must return a a valid array"); } $inst = new self(); @@ -199,20 +201,21 @@ public function promptLine(array $row): mixed } /** - * Prompt output and get result as array or false if aborted + * Prompt output and get the result as an array or false if aborted * * @return array|false - * @throws PromptException + * @throws ErrorException + * @throws PromptException|\MaplePHP\Prompts\PromptException */ public function prompt(): array|false { $result = []; $this->index = 0; - if(!$this->disableHeaderInfo) { + if (!$this->disableHeaderInfo) { $this->getHeaderInfo(); } foreach ($this->data as $name => $row) { - if(!is_array($row)) { + if (!is_array($row)) { throw new PromptException("The data array has to return an array!", 1); } $input = $this->promptLine($row); @@ -235,7 +238,7 @@ public function prompt(): array|false */ protected function getHeaderInfo(): void { - if(!is_null($this->helperText)) { + if ($this->helperText !== null) { $this->command->message("\n" . $this->command->getAnsi()->italic($this->helperText . "\n")); } @@ -253,7 +256,7 @@ protected function getHeaderInfo(): void } /** - * Check if input is empty + * Check if the input is empty * * @param mixed $input * @return bool @@ -310,7 +313,7 @@ protected function validateItems(array|callable $validate, mixed $input, ?string */ final protected function validate(string $value, string $method, array $args = []): bool { - $inp = new Inp($value); + $inp = new Validator($value); if (!method_exists($inp, $method)) { throw new InvalidArgumentException("The validation method \"$method\" does not exist", 1); } diff --git a/SttyWrapper.php b/src/SttyWrapper.php similarity index 100% rename from SttyWrapper.php rename to src/SttyWrapper.php diff --git a/Ansi.php b/src/Themes/Ansi.php similarity index 98% rename from Ansi.php rename to src/Themes/Ansi.php index 82ada16..0d3416f 100755 --- a/Ansi.php +++ b/src/Themes/Ansi.php @@ -1,6 +1,6 @@ isSupported()) { + if (!$this->isSupported()) { return "[$message]"; } return $this->ansiStyle($int, $message); @@ -650,10 +650,10 @@ public function checkbox(): string */ final public function isSupported(): bool { - if($this->disableAnsi) { + if ($this->disableAnsi) { $this->hasAnsi = false; } - if (is_null($this->hasAnsi)) { + if ($this->hasAnsi === null) { if (stripos(PHP_OS, 'WIN') === 0) { $this->hasAnsi = false; } else { diff --git a/src/Themes/Blocks.php b/src/Themes/Blocks.php new file mode 100644 index 0000000..3b90a1e --- /dev/null +++ b/src/Themes/Blocks.php @@ -0,0 +1,225 @@ +command = $command; + $this->space = str_repeat(" ", 2); + } + + /** + * Add a formatted headline with bold blue styling + * + * @param string $title The title text to display as a headline + * @return void + */ + public function addHeadline(string $title): void + { + $this->command->message($this->command->getAnsi()->style(['bold', 'blue'],"{$title}")); + } + + /** + * Add a new section with a title and description + * + * @param string $title The title of the section + * @param string|callable $description The description text or a callback function that returns an instance of this class + * @return void + */ + public function addSection(string $title, string|callable $description): void + { + $this->command->message(""); + $this->command->message($this->command->getAnsi()->bold("{$title}:")); + if(is_callable($description)) { + $inst = $description($this); + if($inst instanceof self) { + $inst->writeOptionLines(); + $inst->writeListLines(); + $inst->writeExampleLines(); + } + } else { + $this->command->message("{$this->space}{$description}"); + } + } + + /** + * Adds a code block + * + * @param string $code The description text or a callback function that returns an instance of this class + * @return void + */ + public function addCode(string $code): void + { + $this->command->message(""); + $this->command->message($this->command->getAnsi()->bold("Copy code below:"));; + $this->command->message(""); + $this->command->message($this->addCodeStyle($code, $this->command->getAnsi())); + $this->command->message(""); + } + + /** + * Apply syntax highlighting styling to PHP code using ANSI color codes + * + * @param string $code The PHP code to style + * @param Ansi $ansi The Ansi instance for color formatting + * @return string The styled code with ANSI color codes + */ + function addCodeStyle(string $code, Ansi $ansi): string { + + // Keywords like "use", "function", "new" + $code = preg_replace_callback('/\b(use|function|new)\b/', function ($m) use ($ansi) { + return $ansi->blue($m[1]); + }, $code); + + // Variables like $unit, $case, $valid + $code = preg_replace_callback('/(\$[a-zA-Z_]\w*)/', function ($m) use ($ansi) { + return $ansi->brightMagenta($m[1]); + }, $code); + + // Strings like "Lorem ipsum" + $code = preg_replace_callback('/(["\'])(.*?)(\1)/', function ($m) use ($ansi) { + return $ansi->cyan($m[1] . $m[2] . $m[3]); + }, $code); + + // Data types + $code = preg_replace_callback('/\b(callable|Closure|null|string|bool|float|int)\b/', function ($m) use ($ansi) { + return $ansi->yellow($m[1]); + }, $code); + + $code = preg_replace_callback('/\b(Unit|TestCase|TestConfig|Expect)\b/', function ($m) use ($ansi) { + return $ansi->yellow($m[1]); + }, $code); + + // Method names like ->validate(, ->isString( etc. + $code = preg_replace_callback('/->(\w+)\s*\(/', function ($m) use ($ansi) { + return '->' . $ansi->brightBlue($m[1]) . '('; + }, $code); + + return $code; + } + + /** + * Write formatted option lines with cyan styling and proper spacing + * Used internally to output the stored options with aligned formatting + * + * @return void + */ + private function writeOptionLines(): void + { + foreach($this->options as $key => $value) { + $space2 = str_repeat(" ", ($this->optionsLength - strlen($key) + 5)); + $this->command->message( + $this->command->getAnsi()->cyan("{$this->space}--{$key}{$space2}{$value}") + ); + } + } + + /** + * Write formatted option lines with cyan styling and proper spacing + * Used internally to output the stored options with aligned formatting + * + * @return void + */ + private function writeListLines(): void + { + foreach($this->list as $key => $value) { + $space2 = str_repeat(" ", ($this->listLength - strlen($key) + 5)); + $this->command->message( + $this->command->getAnsi()->yellow("{$key}{$space2}{$value}") + ); + } + } + + + /** + * Write formatted example lines with yellow styling for keys and grey italic for values + * Used internally to output the stored examples with proper formatting and indentation + * + * @return void + */ + private function writeExampleLines(): void + { + foreach($this->examples as $key => $value) { + $this->command->message( + $this->command->getAnsi()->style(['yellow'], "{$this->space}{$key}") + ); + if($value) { + $this->command->message( + $this->command->getAnsi()->style(['grey', 'italic'], "{$this->space}{$this->space}{$value}") + ); + } + } + } + + /** + * Add an option with its description to the command help output + * Returns a new instance of this class with the added option + * + * @param string $option The option name/flag to add + * @param string $description The description text for this option + * @return self New instance with the option added + */ + public function addOption(string $option, string $description): self + { + $inst = clone $this; + $length = strlen($option); + $inst->options[$option] = $description; + if($length > $inst->optionsLength) { + $inst->optionsLength = $length; + } + return $inst; + } + + + /** + * Add an option with its description to the command help output + * Returns a new instance of this class with the added option + * + * @param string $option The option name/flag to add + * @param string $description The description text for this option + * @return self New instance with the option added + */ + public function addList(string $option, string $description): self + { + $inst = clone $this; + $length = strlen($option); + $inst->list[$option] = $description; + if($length > $inst->listLength) { + $inst->listLength = $length; + } + return $inst; + } + + /** + * Add an example with optional description to the command help output + * Returns a new instance of this class with the added example + * + * @param string $example The example text/command to add + * @param string|null $description Optional description text for this example + * @return self New instance with the example added + */ + public function addExamples(string $example, ?string $description = null): self + { + $inst = clone $this; + $inst->examples[$example] = $description; + return $inst; + } +} \ No newline at end of file