From 06b76e4c968b0c4399d7ef15990cea99b6f5d87a Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Thu, 12 May 2022 20:27:01 +0800 Subject: [PATCH] initial commit --- .gitignore | 7 +- .php-cs-fixer.php | 70 +++++++++ composer.json | 62 ++++++++ phpstan.neon | 8 + src/ZM/Logger/ConsoleColor.php | 199 +++++++++++++++++++++++++ src/ZM/Logger/ConsoleLogger.php | 170 +++++++++++++++++++++ src/ZM/Logger/ConsolePrettyPrinter.php | 53 +++++++ src/ZM/Logger/TextUtil.php | 14 ++ 8 files changed, 580 insertions(+), 3 deletions(-) create mode 100644 .php-cs-fixer.php create mode 100644 composer.json create mode 100644 phpstan.neon create mode 100644 src/ZM/Logger/ConsoleColor.php create mode 100644 src/ZM/Logger/ConsoleLogger.php create mode 100644 src/ZM/Logger/ConsolePrettyPrinter.php create mode 100644 src/ZM/Logger/TextUtil.php diff --git a/.gitignore b/.gitignore index a67d42b..8af1b8b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ +### Composer 相关文件 ### composer.phar /vendor/ +composer.lock -# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control -# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file -# composer.lock +### cghooks.lock ### +cghooks.lock \ No newline at end of file diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php new file mode 100644 index 0000000..b133250 --- /dev/null +++ b/.php-cs-fixer.php @@ -0,0 +1,70 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12' => true, + '@Symfony' => true, + '@PhpCsFixer' => true, + 'array_syntax' => [ + 'syntax' => 'short', + ], + 'list_syntax' => [ + 'syntax' => 'short', + ], + 'concat_space' => [ + 'spacing' => 'one', + ], + 'blank_line_before_statement' => [ + 'statements' => [ + 'declare', + ], + ], + 'ordered_imports' => [ + 'imports_order' => [ + 'class', + 'function', + 'const', + ], + 'sort_algorithm' => 'alpha', + ], + 'single_line_comment_style' => [ + 'comment_types' => [ + ], + ], + 'yoda_style' => [ + 'always_move_variable' => false, + 'equal' => false, + 'identical' => false, + ], + 'multiline_whitespace_before_semicolons' => [ + 'strategy' => 'no_multi_line', + ], + 'constant_case' => [ + 'case' => 'lower', + ], + 'class_attributes_separation' => true, + 'combine_consecutive_unsets' => true, + 'declare_strict_types' => true, + 'linebreak_after_opening_tag' => true, + 'lowercase_static_reference' => true, + 'no_useless_else' => true, + 'no_unused_imports' => true, + 'not_operator_with_successor_space' => false, + 'not_operator_with_space' => false, + 'ordered_class_elements' => true, + 'php_unit_strict' => false, + 'phpdoc_separation' => false, + 'single_quote' => true, + 'standardize_not_equals' => true, + 'multiline_comment_opening_closing' => true, + 'phpdoc_summary' => false, + 'php_unit_test_class_requires_covers' => false, + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__ . '/src') + ) + ->setUsingCache(false); diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..6dad9dd --- /dev/null +++ b/composer.json @@ -0,0 +1,62 @@ +{ + "name": "zhamao/logger", + "description": "Another Console Logger for CLI Applications", + "type": "library", + "license": "Apache-2.0", + "autoload": { + "psr-4": { + "ZM\\Logger\\": "src/ZM/Logger" + } + }, + "autoload-dev": { + "psr-4": { + "Tests\\": "tests/" + } + }, + "authors": [ + { + "name": "jerry", + "email": "admin@zhamao.me" + }, + { + "name": "sunxyw", + "email": "dev@sunxyw.xyz" + } + ], + "minimum-stability": "stable", + "require": { + "php": "^7.2 || ^7.3 || ^7.4 || ^8.0 || ^8.1", + "psr/log": "^1 || ^2 || ^3" + }, + "config": { + "optimize-autoloader": true, + "sort-packages": true + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.2", + "phpstan/phpstan": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.0", + "roave/security-advisories": "dev-latest", + "brainmaestro/composer-git-hooks": "^2.8" + }, + "extra": { + "hooks": { + "post-merge": "composer install", + "pre-commit": [ + "echo committing as $(git config user.name)", + "composer cs-fix -- --diff" + ], + "pre-push": [ + "composer cs-fix -- --dry-run --diff", + "composer analyse" + ] + } + }, + "scripts": { + "post-install-cmd": [ + "[ $COMPOSER_DEV_MODE -eq 0 ] || vendor/bin/cghooks add" + ], + "analyse": "phpstan analyse --memory-limit 300M", + "cs-fix": "php-cs-fixer fix" + } +} diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..c661395 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,8 @@ +parameters: + reportUnmatchedIgnoredErrors: false + level: 4 + paths: + - ./src/ + ignoreErrors: + - '#Used constant OS_TYPE_(LINUX|WINDOWS) not found#' + - '#Unsafe usage of new static#' \ No newline at end of file diff --git a/src/ZM/Logger/ConsoleColor.php b/src/ZM/Logger/ConsoleColor.php new file mode 100644 index 0000000..c7d0da7 --- /dev/null +++ b/src/ZM/Logger/ConsoleColor.php @@ -0,0 +1,199 @@ + null, + 'bold' => '1', // 加粗 + 'dark' => '2', // 昏暗 + 'italic' => '3', // 倾斜 + 'underline' => '4', // 下划线 + 'blink' => '5', // 闪烁 + 'rapid_blink' => '6', // 快速闪烁,兼容性不佳 + 'reverse' => '7', // 反转 + 'concealed' => '8', // 遮盖,兼容性不佳 + 'strike' => '9', // 删除线 + + 'default' => '39', // 默认颜色 + 'black' => '30', // 黑色 + 'red' => '31', // 红色 + 'green' => '32', // 绿色 + 'yellow' => '33', // 黄色,各终端表现不一,建议使用 bright_yellow 替代 + 'blue' => '34', // 蓝色 + 'magenta' => '35', // 紫色 + 'cyan' => '36', // 青色 + // 'white' => '37', // 白色,实际表现为灰色 + 'gray' => '37', + 'white' => '97', // 此处为亮白色 + + 'bright_black' => '90', // 亮黑色(暗灰色) + 'bright_red' => '91', // 亮红色 + 'bright_green' => '92', // 亮绿色 + 'bright_yellow' => '93', // 亮黄色 + 'bright_blue' => '94', // 亮蓝色 + 'bright_magenta' => '95', // 亮紫色 + 'bright_cyan' => '96', // 亮青色 + 'bright_white' => '97', // 亮白色 + + 'bg_default' => '49', + 'bg_black' => '40', + 'bg_red' => '41', + 'bg_green' => '42', + 'bg_yellow' => '43', + 'bg_blue' => '44', + 'bg_magenta' => '45', + 'bg_cyan' => '46', + 'bg_gray' => '47', + 'bg_white' => '107', + + 'bg_bright_black' => '100', + 'bg_bright_red' => '101', + 'bg_bright_green' => '102', + 'bg_bright_yellow' => '103', + 'bg_bright_blue' => '104', + 'bg_bright_magenta' => '105', + 'bg_bright_cyan' => '106', + 'bg_bright_white' => '107', + ]; + + protected $styles = []; + + protected $text = ''; + + public function __call($name, $arguments) + { + $this->addStyle($name); + if (isset($arguments[0])) { + $this->setText($arguments[0]); + } + return $this; + } + + public static function __callStatic($name, $arguments) + { + $instance = new self(); + $instance->addStyle($name); + if (isset($arguments[0])) { + $instance->setText($arguments[0]); + } + return $instance; + } + + public function __toString() + { + $style_code = $this->getStylesCode(); + + return sprintf("\033[%sm%s\033[%dm", $style_code, $this->text, self::RESET); + } + + public static function apply(array $styles, string $text): ConsoleColor + { + $instance = new self(); + $instance->setText($text); + $instance->applyStyles($styles); + return $instance; + } + + public function applyStyles(array $styles): ConsoleColor + { + $this->styles = array_merge($this->styles, $styles); + return $this; + } + + public function setText(string $text): ConsoleColor + { + $this->text = $text; + return $this; + } + + protected function addStyle(string $style): ConsoleColor + { + $style = TextUtil::separatorToCamel($style); + if (array_key_exists($style, self::STYLES)) { + $this->styles[] = $style; + } + return $this; + } + + protected function getStylesCode(): string + { + array_walk($this->styles, static function (&$style) { + // 4bit (classic) + if (array_key_exists($style, self::STYLES)) { + $style = self::STYLES[$style]; + return; + } + // 8bit (256color) + if (str_contains($style, 'color')) { + preg_match('~^(bg_)?color_(\d{1,3})$~', $style, $matches); + $type = $matches[1] === 'bg_' ? '48' : '38'; + $value = $matches[2]; + $style = "{$type};5;{$value}"; + return; + } + // 24bit (rgb) + if (str_contains($style, 'rgb')) { + preg_match('~^(bg_)?rgb_(\d{1,3})_(\d{1,3})_(\d{1,3})$~', $style, $matches); + $type = $matches[1] === 'bg_' ? '48' : '38'; + [, , $r, $g, $b] = $matches; + $style = "{$type};2;{$r};{$g};{$b}"; + } + }); + + return implode(';', $this->styles); + } +} diff --git a/src/ZM/Logger/ConsoleLogger.php b/src/ZM/Logger/ConsoleLogger.php new file mode 100644 index 0000000..88b1a17 --- /dev/null +++ b/src/ZM/Logger/ConsoleLogger.php @@ -0,0 +1,170 @@ + ['blink', 'white', 'bg_bright_red'], + LogLevel::ALERT => ['white', 'bg_bright_red'], + LogLevel::CRITICAL => ['underline', 'red'], + LogLevel::ERROR => ['red'], + LogLevel::WARNING => ['bright_yellow'], + LogLevel::NOTICE => ['cyan'], + LogLevel::INFO => ['green'], + LogLevel::DEBUG => ['gray'], + ]; + + protected static $levels = [ + LogLevel::EMERGENCY, // 0 + LogLevel::ALERT, // 1 + LogLevel::CRITICAL, // 2 + LogLevel::ERROR, // 3 + LogLevel::WARNING, // 4 + LogLevel::NOTICE, // 5 + LogLevel::INFO, // 6 + LogLevel::DEBUG, // 7 + ]; + + protected static $logLevel; + + protected $exceptionHandler; + + public function __construct($logLevel = LogLevel::INFO) + { + self::$logLevel = array_flip(self::$levels)[$logLevel]; + //ExceptionHandler::getInstance(); + } + + /** + * @return string[][] + */ + public static function getStyles(): array + { + return self::$styles; + } + + public function colorize($string, $level): string + { + $string = $this->stringify($string); + $styles = self::$styles[$level] ?? []; + return ConsoleColor::apply($styles, $string)->__toString(); + } + + public function trace(): void + { + $log = "Stack trace:\n"; + $trace = debug_backtrace(); + //array_shift($trace); + foreach ($trace as $i => $t) { + if (!isset($t['file'])) { + $t['file'] = 'unknown'; + } + if (!isset($t['line'])) { + $t['line'] = 0; + } + $log .= "#{$i} {$t['file']}({$t['line']}): "; + if (isset($t['object']) && is_object($t['object'])) { + $log .= get_class($t['object']) . '->'; + } + $log .= "{$t['function']}()\n"; + } + $log = $this->colorize($log, LogLevel::DEBUG); + echo $log; + } + + public function getTrace(): string + { +// if (self::$info_level !== null && self::$info_level == 4) { +// $trace = debug_backtrace()[2] ?? ['file' => '', 'function' => '']; +// $trace = '[' . ($trace['class'] ?? '') . ':' . ($trace['function'] ?? '') . '] '; +// } +// return $trace ?? ''; + return ''; + } + +// $trace = debug_backtrace()[1] ?? ['file' => '', 'function' => '']; +// $trace = '[' . ($trace['class'] ?? '') . ':' . ($trace['function'] ?? '') . '] '; + + public function log($level, $message, array $context = []): void + { + if (!in_array($level, self::$levels, true)) { + throw new InvalidArgumentException(); + } + + if (array_flip(self::$levels)[$level] > self::$logLevel) { + return; + } + + $output = str_replace( + ['%date%', '%level%', '%body%'], + [date(self::$date_format), strtoupper(substr($level, 0, 4)), $message], + self::$format + ); + $output = $this->interpolate($output, $context); + echo $this->colorize($output, $level) . "\n"; + } + + public static function getVersion(): string + { + return self::VERSION; + } + + private function stringify($item) + { + if (is_object($item) && method_exists($item, '__toString')) { + return $item; + } + if (is_string($item) || is_numeric($item)) { + return $item; + } + if (is_callable($item)) { + return '{Closure}'; + } + if (is_bool($item)) { + return $item ? '*True*' : '*False*'; + } + if (is_array($item)) { + return json_encode( + $item, + JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_LINE_TERMINATORS + ); + } + if (is_resource($item)) { + return '{Resource}'; + } + if (is_null($item)) { + return 'NULL'; + } + return '{Not Stringable Object:' . get_class($item) . '}'; + } + + private function interpolate($message, array $context = []) + { + if (is_array($message)) { + return $message; + } + + $replace = []; + foreach ($context as $key => $value) { + $replace['{' . $key . '}'] = $this->stringify($value); + } + + return strtr($message, $replace); + } +} diff --git a/src/ZM/Logger/ConsolePrettyPrinter.php b/src/ZM/Logger/ConsolePrettyPrinter.php new file mode 100644 index 0000000..2d7b7bb --- /dev/null +++ b/src/ZM/Logger/ConsolePrettyPrinter.php @@ -0,0 +1,53 @@ +params = $params; + $this->head = $head === '' ? str_pad('', 65, '=') : $head; + $this->foot = $foot === '' ? str_pad('', 65, '=') : $foot; + } + + public static function createFromArray(array $params): ConsolePrettyPrinter + { + return new static($params); + } + + public function printAll(): void + { + $this->printHead(); + $this->printBody(); + $this->printFoot(); + } + + public function printHead() + { + // TODO: 写头 + } + + public function printBody() + { + // TODO: 写主体 + } + + public function printFoot() + { + // TODO: 写尾 + } +} diff --git a/src/ZM/Logger/TextUtil.php b/src/ZM/Logger/TextUtil.php new file mode 100644 index 0000000..50677a4 --- /dev/null +++ b/src/ZM/Logger/TextUtil.php @@ -0,0 +1,14 @@ +