diff --git a/src/output/Console.php b/src/output/Console.php
index 2f1cd36..7667cdf 100644
--- a/src/output/Console.php
+++ b/src/output/Console.php
@@ -21,33 +21,47 @@ class Console implements IMessenger {
"m" => self::LEVEL_MAJOR,
];
- const TYPE_PREFIXES = [
+ protected static function verifix_level($level, bool $debug): int {
+ if ($level === null) $level = $debug? self::LEVEL_DEBUG: self::LEVEL_NORMAL;
+ if (!in_array($level, self::VALID_LEVELS, true)) {
+ $level = cl::get(self::LEVEL_MAP, $level, $level);
+ }
+ if (!in_array($level, self::VALID_LEVELS, true)) {
+ throw new Exception("$level: invalid level");
+ }
+ return $level;
+ }
+
+ const GENERIC_PREFIXES = [
self::LEVEL_MAJOR => [
- "section" => [true, "SECTION!", "===", "= ", " =", "==="],
- "title" => ["TITLE!", null, "T ", "", "---"],
- "desc" => ["DESC!", "> ", ""],
- "error" => ["CRITICAL!", "E! ", ""],
- "warn" => ["ATTENTION!", "W! ", ""],
- "note" => ["IMPORTANT!", "N! ", ""],
- "info" => ["IMPORTANT!", "I! ", ""],
+ "section" => [true, "SECTION!", "===", "=", "=", "==="],
+ "title" => [false, "TITLE!", null, "T", "", "---"],
+ "desc" => ["DESC!", ">", ""],
+ "error" => ["CRITICAL!", "E!", ""],
+ "warn" => ["ATTENTION!", "W!", ""],
+ "note" => ["IMPORTANT!", "N!", ""],
+ "info" => ["IMPORTANT!", "I!", ""],
+ "print" => [null, null, null],
],
self::LEVEL_NORMAL => [
- "section" => [true, "SECTION:", null, ">> ", " <<", "---"],
- "title" => ["TITLE:", null, "T ", "", null],
- "desc" => ["DESC:", "> ", ""],
- "error" => ["ERROR:", "E ", ""],
- "warn" => ["WARN:", "W ", ""],
- "note" => ["NOTE:", "N ", ""],
- "info" => ["INFO:", "I ", ""],
+ "section" => [true, "SECTION:", "---", "-", "-", "---"],
+ "title" => [false, "TITLE:", null, "T", "", null],
+ "desc" => ["DESC:", ">", ""],
+ "error" => ["ERROR:", "E", ""],
+ "warn" => ["WARN:", "W", ""],
+ "note" => ["NOTE:", "N", ""],
+ "info" => ["INFO:", "I", ""],
+ "print" => [null, null, null],
],
self::LEVEL_DEBUG => [
- "section" => [false, "s", null, ">> ", " <<", null],
- "title" => ["t", "t ", ""],
- "desc" => [">", "> ", ""],
- "error" => ["e", "e ", ""],
- "warn" => ["w", "w ", ""],
- "note" => ["i", "i ", ""],
- "info" => ["D", "D ", ""],
+ "section" => [false, "section", null, ">>", "<<", null],
+ "title" => [false, "title", null, "t", "", null],
+ "desc" => [">", ">", ""],
+ "error" => ["e", "e", ""],
+ "warn" => ["w", "w", ""],
+ "note" => ["i", "i", ""],
+ "info" => ["D", "D", ""],
+ "print" => [null, null, null],
],
];
@@ -59,25 +73,24 @@ class Console implements IMessenger {
];
function __construct(?array $params=null) {
+ $color = cl::get($params, "color");
$debug = boolval(cl::get($params, "debug"));
- $minLevel = cl::get($params, "min_level");
- if ($minLevel === null) $minLevel = $debug? self::LEVEL_DEBUG: self::LEVEL_NORMAL;
- if (!in_array($minLevel, self::VALID_LEVELS)) {
- $minLevel = cl::get(self::LEVEL_MAP, $minLevel, $minLevel);
- }
- if (!in_array($minLevel, self::VALID_LEVELS)) {
- throw new Exception("$minLevel: invalid level");
- }
+ $minLevel = self::verifix_level(cl::get($params, "min_level"), $debug);
+ $defaultLevel = self::verifix_level(cl::get($params, "default_level"), false);
- $this->out = new StdOutput(STDOUT);
- $this->err = new StdOutput(STDERR);
+ $params = [
+ "color" => $color,
+ "indent" => static::INDENT,
+ ];
+ $this->out = new StdOutput(STDOUT, $params);
+ $this->err = new StdOutput(STDERR, $params);
$this->minLevel = intval($minLevel);
- $this->indent = static::INDENT;
+ $this->defaultLevel = intval($defaultLevel);
$this->inSection = false;
- $this->titles = null;
- $this->currentTitle = null;
- $this->actions = null;
- $this->currentAction = null;
+ $this->titles = [];
+ $this->title = null;
+ $this->actions = [];
+ $this->action = null;
}
/** @var StdOutput la sortie standard */
@@ -89,25 +102,93 @@ class Console implements IMessenger {
/** @var int level minimum que doivent avoir les messages pour être affichés */
protected $minLevel;
- /** @var string valeur unitaire de l'indentation */
- protected $indent;
+ /** @var int level par défaut dans lequel les messages sont affichés */
+ protected $defaultLevel;
+
+ protected function checkLevel(?int &$level): bool {
+ if ($level === null) $level = $this->defaultLevel;
+ return $level >= $this->minLevel;
+ }
+
+ protected function _printTitle(int $level, string $type, $content, int $indentLevel, StdOutput $out): void {
+ $prefixes = self::GENERIC_PREFIXES[$level][$type];
+ if ($prefixes[0]) $out->print();
+ if ($out->isColor()) {
+ $before = $prefixes[2];
+ $prefix = $prefixes[3];
+ $prefix2 = $prefix !== null? "$prefix ": null;
+ $suffix = $prefixes[4];
+ $suffix2 = $suffix !== null? " $suffix": null;
+ $after = $prefixes[5];
+
+ $lines = $out->getLines(false, $content);
+ $maxlen = 0;
+ foreach ($lines as &$content) {
+ $line = $out->filterColors($content);
+ $len = strlen($line);
+ if ($len > $maxlen) $maxlen = $len;
+ $content = [$content, $len];
+ }; unset($content);
+ if ($before !== null) {
+ $out->iprint($indentLevel, $prefix, substr($before, 1), str_repeat($before[0], $maxlen), $suffix);
+ }
+ foreach ($lines as [$content, $len]) {
+ $padding = $len < $maxlen? str_repeat(" ", $maxlen - $len): null;
+ $out->iprint($indentLevel, $prefix2, $content, $padding, $suffix2);
+ }
+ if ($after !== null) {
+ $out->iprint($indentLevel, $prefix, substr($after, 1), str_repeat($after[0], $maxlen), $suffix);
+ }
+ } else {
+ $prefix = $prefixes[1];
+ if ($prefix !== null) $prefix .= " ";
+ $prefix2 = str_repeat(" ", strlen($prefix));
+ $lines = $out->getLines(false, $content);
+ foreach ($lines as $content) {
+ $out->iprint($indentLevel, $prefix, $content);
+ $prefix = $prefix2;
+ }
+ }
+ }
+
+ protected function _printGeneric(int $level, string $type, $content, int $indentLevel, StdOutput $out): void {
+ $prefixes = self::GENERIC_PREFIXES[$level][$type];
+ if ($out->isColor()) {
+ $prefix = $prefixes[1];
+ $suffix = $prefixes[2];
+ } else {
+ $prefix = $prefixes[0];
+ $suffix = null;
+ }
+ $line = [$prefix];
+ if ($prefix !== null) $line[] = " ";
+ $line[] = $content;
+ $line[] = $suffix;
+ $out->iprint($indentLevel, ...$line);
+ }
/** @var bool est-on dans une section? */
protected $inSection;
- /** @var array|string section qui est en attente d'affichage */
+ /** @var array section qui est en attente d'affichage */
protected $section;
- function section($content, int $level=self::LEVEL_NORMAL): void {
+ function section($content, ?int $level=null): void {
$this->endSection();
$this->inSection = true;
- if ($level < $this->minLevel) return;
- $this->section = $content;
+ if (!$this->checkLevel($level)) return;
+ $this->section = [
+ "level" => $level,
+ "content" => $content,
+ "print_content" => true,
+ ];
}
protected function printSection() {
- if ($this->section !== null) {
- $this->section = null;
+ $section =& $this->section;
+ if ($section["print_content"]) {
+ $this->_printTitle($section["level"], "section", $section["content"], 0, $this->err);
+ $section["print_content"] = false;
}
}
@@ -120,34 +201,57 @@ class Console implements IMessenger {
protected $titles;
/** @var array */
- protected $currentTitle;
+ protected $title;
- function title($content, int $level=self::LEVEL_NORMAL): void {
- if ($level < $this->minLevel) return;
+ function title($content, ?int $level=null): void {
+ if (!$this->checkLevel($level)) return;
$this->titles[] = [
- "title" => $content,
+ "level" => $level,
+ "content" => $content,
+ "print_content" => true,
"descs" => [],
- "print" => true,
+ "print_descs" => false,
];
- $this->currentTitle =& $this->titles[count($this->titles) - 1];
+ $this->title =& $this->titles[count($this->titles) - 1];
}
- function desc($content, int $level=self::LEVEL_NORMAL): void {
- if ($level < $this->minLevel) return;
- $this->currentTitle["descs"][] = $content;
+ function desc($content, ?int $level=null): void {
+ if (!$this->checkLevel($level)) return;
+ $title =& $this->title;
+ $title["descs"][] = [
+ "level" => $level,
+ "content" => $content,
+ ];
+ $title["print_descs"] = true;
}
protected function printTitles(): void {
$this->printSection();
+ $out = $this->err;
+ $indentLevel = 0;
+ foreach ($this->titles as &$title) {
+ if ($title["print_content"]) {
+ $this->_printTitle($title["level"], "title", $title["content"], $indentLevel, $out);
+ $title["print_content"] = false;
+ }
+ if ($title["print_descs"]) {
+ foreach ($title["descs"] as $desc) {
+ $this->_printGeneric($desc["level"], "desc", $desc["content"], $indentLevel, $out);
+ }
+ $title["descs"] = [];
+ $title["print_descs"] = false;
+ }
+ $indentLevel++;
+ }; unset($title);
}
protected function endTitle(): void {
array_pop($this->titles);
if ($this->titles) {
- $this->currentTitle =& $this->titles[count($this->titles) - 1];
+ $this->title =& $this->titles[count($this->titles) - 1];
} else {
- $this->titles = null;
- unset($this->currentTitle);
+ $this->titles = [];
+ unset($this->title);
}
}
@@ -155,9 +259,19 @@ class Console implements IMessenger {
protected $actions;
/** @var array */
- protected $currentAction;
+ protected $action;
- function action($content, int $level=self::LEVEL_NORMAL): void {
+ protected function getIndentLevel(): int {
+ $indentLevel = count($this->titles) - 1;
+ if ($indentLevel < 0) $indentLevel = 0;
+ foreach ($this->actions as $action) {
+ if ($action["level"] < $this->minLevel) continue;
+ $indentLevel++;
+ }
+ return $indentLevel;
+ }
+
+ function action($content, ?int $level=null): void {
$this->actions[] = [
"level" => $level,
"content" => $content,
@@ -166,77 +280,81 @@ class Console implements IMessenger {
"result" => null,
"print_result" => true,
];
- $this->currentAction =& $this->actions[count($this->actions) - 1];
+ $this->action =& $this->actions[count($this->actions) - 1];
}
- function printActions(): void {
+ function printActions(bool $willEnd=false): void {
$this->printTitles();
}
- function step($content): void {
+ function step($content, ?int $level=null): void {
if (!$this->actions) $this->action(null);
$this->printActions();
}
function success($content=null): void {
if (!$this->actions) $this->action(null);
- $this->currentAction["success"] = true;
- $this->currentAction["result"] = $content;
- $this->printActions();
+ $this->action["success"] = true;
+ $this->action["result"] = $content;
+ $this->printActions(true);
$this->endAction();
}
function failure($content=null): void {
if (!$this->actions) $this->action(null);
- $this->currentAction["success"] = false;
- $this->currentAction["result"] = $content;
- $this->printActions();
+ $this->action["success"] = false;
+ $this->action["result"] = $content;
+ $this->printActions(true);
$this->endAction();
}
function neutral($content=null): void {
if (!$this->actions) $this->action(null);
- $this->currentAction["success"] = null;
- $this->currentAction["result"] = $content;
- $this->printActions();
+ $this->action["success"] = null;
+ $this->action["result"] = $content;
+ $this->printActions(true);
$this->endAction();
}
protected function endAction(): void {
array_pop($this->actions);
if ($this->actions) {
- $this->currentAction =& $this->actions[count($this->actions) - 1];
+ $this->action =& $this->actions[count($this->actions) - 1];
} else {
- $this->actions = null;
- unset($this->currentAction);
+ $this->actions = [];
+ unset($this->action);
}
}
- function print($content, int $level=self::LEVEL_NORMAL): void {
- if ($level < $this->minLevel) return;
+ function print($content, ?int $level=null): void {
+ if (!$this->checkLevel($level)) return;
$this->printActions();
- $this->out->print($content);
+ $this->_printGeneric($level, "print", $content, $this->getIndentLevel(), $this->out);
}
- function info($content, int $level=self::LEVEL_NORMAL): void {
- if ($level < $this->minLevel) return;
+ function info($content, ?int $level=null): void {
+ if (!$this->checkLevel($level)) return;
$this->printActions();
+ $this->_printGeneric($level, "info", $content, $this->getIndentLevel(), $this->err);
}
- function note($content, int $level=self::LEVEL_NORMAL): void {
- if ($level < $this->minLevel) return;
+ function note($content, ?int $level=null): void {
+ if (!$this->checkLevel($level)) return;
$this->printActions();
+ $this->_printGeneric($level, "note", $content, $this->getIndentLevel(), $this->err);
}
- function warn($content, int $level=self::LEVEL_NORMAL): void {
- if ($level < $this->minLevel) return;
+ function warn($content, ?int $level=null): void {
+ if (!$this->checkLevel($level)) return;
$this->printActions();
+ $this->_printGeneric($level, "warn", $content, $this->getIndentLevel(), $this->err);
}
- function error($content, int $level=self::LEVEL_NORMAL): void {
- if ($level < $this->minLevel) return;
+ function error($content, ?int $level=null): void {
+ if (!$this->checkLevel($level)) return;
$this->printActions();
+ $this->_printGeneric($level, "error", $content, $this->getIndentLevel(), $this->err);
}
function end(bool $all=false) {
diff --git a/src/output/IContent.php b/src/output/IContent.php
new file mode 100644
index 0000000..fbddc2a
--- /dev/null
+++ b/src/output/IContent.php
@@ -0,0 +1,10 @@
+outf = $outf;
$this->color = boolval($color);
$this->filterTags = boolval($filterTags);
+ $this->indent = $indent;
$this->flush = boolval($flush);
}
@@ -116,6 +118,9 @@ class StdOutput {
/** @var bool faut-il enlever les tags dans la sortie? */
protected $filterTags;
+ /** @var string indentation unitaire */
+ protected $indent;
+
/** @var bool faut-il flush le fichier après l'écriture de chaque ligne */
protected $flush;
@@ -155,24 +160,82 @@ class StdOutput {
}
return $text;
}
- protected function filterColors(string $text): string {
+ function filterColors(string $text): string {
return preg_replace('/\x1B\[.*?m/', "", $text);
}
- protected function fwrite(array $values): void {
+ static function flatten($values, ?array &$dest=null): array {
+ if ($dest === null) $dest = [];
+ if ($values === null) return $dest;
+ if (is_string($values)) {
+ $dest[] = $values;
+ return $dest;
+ } elseif (!is_array($values)) {
+ if ($values instanceof IContent) {
+ $values = $values->getContent();
+ } elseif ($values instanceof IPrintable) {
+ ob_start(null, 0, PHP_OUTPUT_HANDLER_STDFLAGS ^ PHP_OUTPUT_HANDLER_FLUSHABLE);
+ $values->print();
+ $dest[] = ob_get_clean();
+ return $dest;
+ } elseif (!is_iterable($values)) {
+ $dest[] = strval($values);
+ return $dest;
+ }
+ }
+ foreach ($values as $value) {
+ self::flatten($value, $dest);
+ }
+ return $dest;
+ }
+
+ function getIndent(int $indentLevel): string {
+ return str_repeat($this->indent, $indentLevel);
+ }
+
+ function getLines(bool $withNl, ...$values): array {
+ $values = self::flatten($values);
$text = implode("", $values);
$text = $this->filterContent($text);
if (!$this->color) $text = $this->filterColors($text);
- fwrite($this->outf, $text);
- if ($this->flush) fflush($this->outf);
+ $lines = explode("\n", $text);
+ $max = count($lines) - 1;
+ if ($withNl) {
+ for ($i = 0; $i < $max; $i++) {
+ $lines[$i] .= "\n";
+ }
+ }
+ if ($lines[$max] === "") unset($lines[$max]);
+ return $lines;
+ }
+
+ function writeLines($indent, array $lines, bool $addNl=false): void {
+ $outf = $this->outf;
+ foreach ($lines as $line) {
+ if ($indent !== null) fwrite($outf, $indent);
+ fwrite($outf, $line);
+ if ($addNl) fwrite($outf, "\n");
+ }
+ if ($this->flush) fflush($outf);
}
function write(...$values): void {
- $this->fwrite($values);
+ $this->writeLines(null, $this->getLines(true, ...$values));
}
function print(...$values): void {
$values[] = "\n";
- $this->fwrite($values);
+ $this->writeLines(null, $this->getLines(true, ...$values));
+ }
+
+ function iwrite(int $indentLevel, ...$values): void {
+ $indent = $this->getIndent($indentLevel);
+ $this->writeLines($indent, $this->getLines(true, ...$values));
+ }
+
+ function iprint(int $indentLevel, ...$values): void {
+ $values[] = "\n";
+ $indent = $this->getIndent($indentLevel);
+ $this->writeLines($indent, $this->getLines(true, ...$values));
}
}
diff --git a/tbin/test-console.php b/tbin/test-console.php
new file mode 100755
index 0000000..ca68081
--- /dev/null
+++ b/tbin/test-console.php
@@ -0,0 +1,84 @@
+#!/usr/bin/php
+section("section");
+
+$c->title("title");
+$c->desc("desc");
+$c->print("print");
+
+$c->action("action");
+$c->step("step");
+$c->success("action success");
+
+$c->action("action");
+$c->step("step");
+$c->failure("action failure");
+
+$c->action("action");
+$c->success("action success");
+
+$c->action("action");
+$c->failure("action failure");
+
+$c->action("action0");
+$c->action("action1");
+$c->action("action2");
+$c->success("action2 success");
+$c->success("action1 success");
+$c->success("action0 success");
+
+$c->info("info");
+$c->note("note");
+$c->warn("warn");
+$c->error("error");
+
+$c->end();
+
+$c->title("title0");
+$c->title("title1");
+$c->print("print under title1");
+$c->end();
+$c->print("print under title0");
+$c->end();
+
+$c->end(true);
+
+$c->section("multi-line\nsection");
+$c->title("multi-line\ntitle");
+$c->title("another\ntitle");
+$c->print("multi-line\nprint");
+$c->end(true);