- Added color / formats management

This commit is contained in:
Dave M. 2024-10-31 10:33:00 -04:00
parent d503628356
commit 32d5079559
11 changed files with 297 additions and 2 deletions

@ -3,6 +3,7 @@
namespace Mcnd\CLI;
use Laminas\Diactoros\Response\TextResponse;
use Mcnd\CLI\Lib\AsciiFormatter;
use Notes\CLI\CommandFetcher;
use Psr\Http\Message\ResponseFactoryInterface,
@ -14,6 +15,8 @@ use Psr\Http\Message\ResponseFactoryInterface,
class CliMiddleware implements MiddlewareInterface
{
public bool $allowAsciiFormatting;
public \Closure $defaultResponse;
public ContainerInterface $container;
@ -28,11 +31,12 @@ class CliMiddleware implements MiddlewareInterface
protected array $parsedOptions = [];
public function __construct(ContainerInterface $container, callable $defaultResponseInterface = null, null|CommandFetcher $fetcher = null, null|CommandStack $stack = null) {
public function __construct(ContainerInterface $container, callable $defaultResponseInterface = null, null|CommandFetcher $fetcher = null, null|CommandStack $stack = null, bool $allowAsciiFormatting = true) {
$this->defaultResponse = $defaultResponseInterface;
$this->container = $container;
$this->fetcher = $fetcher;
$this->commands = $stack ?? new CommandStack();
$this->allowAsciiFormatting = $allowAsciiFormatting;
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
@ -59,7 +63,6 @@ class CliMiddleware implements MiddlewareInterface
}
}
return call_user_func($this->defaultResponse);
}
@ -85,6 +88,10 @@ class CliMiddleware implements MiddlewareInterface
" COMMANDS HERE", "",
];
if ($this->allowAsciiFormatting) {
$text = ( new AsciiFormatter() )->parse($text);
}
return new TextResponse( implode(PHP_EOL, $text) );
}
}

@ -2,6 +2,7 @@
namespace Mcnd\CLI;
#[\Attribute]
class Command
{
public function __construct(

@ -0,0 +1,95 @@
<?php
namespace Mcnd\CLI\Lib;
class AsciiFormatter
{
public array $stack = [];
protected string $latest;
public function parse(array|string $content) : array|string
{
return is_array($content) ? array_map([ $this, 'parseContent' ], $content) : $this->parseContent($content);
}
protected function parseContent(string $text) : string
{
if (str_contains($text, '#[')) {
# Opening 'tag'
$text = preg_replace_callback("#(\#\[)(.*?)(\])#s", function ($matches) {
if ( empty($stack) ) {
$this->stack[] = $this->translateFormat("c:default, bg:default, f:normal");
}
else {
$this->stack[] = $this->latest;
}
$this->latest = $this->translateFormat($matches[2]);
return $this->latest;
}, $text);
# Closing 'tag'
$text = preg_replace_callback("#(\[\#\])#s", function ($matches) {
$close = $this->stack ? array_pop($this->stack) : "";
return $close;
}, $text);
}
return $text;
}
protected function translateFormat(string $source) : string
{
$valueType =
$color = $background = $format = null;
foreach(array_map(fn($e) => trim(strtolower($e)), explode(',', $source)) as $formatting) {
if ( ! strstr($formatting, ':' )) {
throw new \InvalidArgumentException(
sprintf("Arguments must be called with their variable's name (%s)", $formatting)
);
}
list($scope, $value) = explode(':', $formatting, 2);
switch($scope) {
case 'color':
case 'c':
$type = ValueTypeEnum::autodetect($value);
if ($type === ValueTypeEnum::Ascii16) {
$color = ColorEnum::mapFrom($value);
}
elseif ($type === ValueTypeEnum::Ascii256) {
$color = RgbColor::mapFrom($value);
}
break;
case 'background':
case 'bg':
case 'b':
$type = ValueTypeEnum::autodetect($value);
if ($type === ValueTypeEnum::Ascii16) {
$background = BackgroundColorEnum::mapFrom($value);
}
elseif ($type === ValueTypeEnum::Ascii256) {
$background = RgbBackground::mapFrom($value);
}
break;
case 'format':
case 'f':
$format = FormatEnum::mapFrom($value);
break;
}
}
return ($color ? $color->output() : "") . ($background ? $background->output() : "") . ($format ? $format->output() : "");
}
}

@ -0,0 +1,26 @@
<?php
namespace Mcnd\CLI\Lib;
enum BackgroundColorEnum : string
{
use ColorMappingTrait;
case Default = "49";
case Black = "40";
case Red = "41";
case Green = "42";
case Yellow = "43";
case Blue = "44";
case Magenta = "45";
case Cyan = "46";
case LightGray = "47";
case DarkGray = "100";
case LightRed = "101";
case LightGreen = "102";
case LightYellow = "103";
case LightBlue = "104";
case LightMagenta = "105";
case LightCyan = "106";
case White = "107";
}

26
src/Lib/ColorEnum.php Normal file

@ -0,0 +1,26 @@
<?php
namespace Mcnd\CLI\Lib;
enum ColorEnum : string
{
use ColorMappingTrait;
case Default = "39";
case Black = "30";
case Red = "31";
case Green = "32";
case Yellow = "33";
case Blue = "34";
case Magenta = "35";
case Cyan = "36";
case LightGray = "37";
case DarkGray = "90";
case LightRed = "91";
case LightGreen = "92";
case LightYellow = "93";
case LightBlue = "94";
case LightMagenta = "95";
case LightCyan = "96";
case White = "97";
}

@ -0,0 +1,33 @@
<?php
namespace Mcnd\CLI\Lib;
trait ColorMappingTrait
{
public static function mapFrom(string $name) : ? self
{
return [
'black' => self::Black,
'red' => self::Red,
'green' => self::Green,
'yellow' => self::Yellow,
'blue' => self::Blue,
'magenta' => self::Magenta,
'cyan' => self::Cyan,
'lightgray' => self::LightGray,
'darkgray' => self::DarkGray,
'lightred' => self::LightRed,
'lightgreen' => self::LightGreen,
'lightyellow' => self::LightYellow,
'lightblue' => self::LightBlue,
'lightmagenta' => self::LightMagenta,
'lightcyan' => self::LightCyan,
'white' => self::White,
][$name] ?? null;
}
public function output() : string
{
return "\e[{$this->value}m";
}
}

39
src/Lib/FormatEnum.php Normal file

@ -0,0 +1,39 @@
<?php
namespace Mcnd\CLI\Lib;
enum FormatEnum : string
{
case Normal = "0";
case Bold = "1";
case Dim = "2";
case Italic = "3";
case Underlined = "4";
case Blinking = "5";
case Reverse = "7";
case Invisible = "8";
public static function mapFrom(string $name): ?self
{
return [
'n' => self::Normal,
'normal' => self::Normal,
'b' => self::Bold,
'bold' => self::Bold,
'd' => self::Dim,
'dim' => self::Dim,
'i' => self::Italic,
'italic' => self::Italic,
'u' => self::Underlined,
'underlined' => self::Underlined,
'blinking' => self::Blinking,
'reverse' => self::Reverse,
'invisible' => self::Invisible,
][$name] ?? null;
}
public function output() : string
{
return "\e[{$this->value}m";
}
}

11
src/Lib/RgbBackground.php Normal file

@ -0,0 +1,11 @@
<?php
namespace Mcnd\CLI\Lib;
class RgbBackground extends RgbColor
{
public function output() : string
{
return "\033[48;2;{$this->rgb}m";
}
}

28
src/Lib/RgbColor.php Normal file

@ -0,0 +1,28 @@
<?php
namespace Mcnd\CLI\Lib;
class RgbColor
{
public readonly string $rgb;
public function __construct(int $red, int $green, int $blue)
{
$this->rgb = "$red;$green;$blue";
}
public static function mapFrom(string $value) : ? self
{
$begin = strpos($value, 'rgb(') + 4;
$end = strrpos($value, ')');
$rgb = substr($value, $begin, $end - $begin);
return new static(...explode(';', $rgb));
}
public function output() : string
{
return "\033[38;2;{$this->rgb}m";
}
}

28
src/Lib/ValueTypeEnum.php Normal file

@ -0,0 +1,28 @@
<?php
namespace Mcnd\CLI\Lib;
enum ValueTypeEnum
{
case NoColors;
case Ascii16;
case Ascii256;
public static function autodetect(string $value) : self
{
if ( substr($value, 0, 4) === 'rgb(' ) {
if (! strstr($value, ')')) {
throw new \InvalidArgumentException(
sprintf("An unclosed parenthesis was found for 'rgb' color %s", $value)
);
}
return self::Ascii256;
}
elseif ( ColorEnum::mapFrom($value) !== null || BackgroundColorEnum::mapFrom($value) ) {
return self::Ascii16;
}
return self::NoColors;
}
}

@ -2,6 +2,7 @@
namespace Mcnd\CLI;
#[\Attribute(\Attribute::IS_REPEATABLE | \Attribute::TARGET_CLASS)]
class Option
{
public const ARGUMENT_NONE = 0;