Skip to content

Commit 139091e

Browse files
committed
Merge branch 'review-console'
2 parents 78ef1f2 + ebb3ecc commit 139091e

File tree

19 files changed

+514
-311
lines changed

19 files changed

+514
-311
lines changed

CHANGELOG.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,153 @@ The format is based on [Keep a Changelog][], and this project adheres to [Semant
1010
[Keep a Changelog]: https://keepachangelog.com/en/1.1.0/
1111
[Semantic Versioning]: https://semver.org/spec/v2.0.0.html
1212

13+
## [v0.99.78] - 2025-03-28
14+
15+
### Added
16+
17+
#### `Console`
18+
19+
- Add and implement `ConsoleInterface` methods:
20+
- `getTtyTarget()`
21+
- `removeEscapes()`
22+
- `removeTags()`
23+
- Add and implement `StreamTargetInterface` method `getUri()`
24+
- In `ConsoleInterface`:
25+
- Add optional `$debug` parameters to `registerStderrTarget()` and `registerStdioTargets()`
26+
- Add optional `$escapeNewlines` parameter to `escape()`
27+
- Add optional `$count` parameter to `exception()`
28+
29+
#### `Container`
30+
31+
- Allow closures to be bound to the container
32+
- Add `$container` parameter to `HasBindings` methods and allow them to return closures
33+
- Add `$container` parameter to `getContextualBindings()` and allow it to return parameter and unmapped services, closures and instances
34+
- Add and implement `ApplicationInterface` methods:
35+
- `getVersion()`
36+
- `getVersionString()`
37+
- `reportVersion()`
38+
39+
#### `PHPStan`
40+
41+
- Add `ContainerRule` to report when calls to `ContainerInterface::getAs()` have an `$id` that isn't a subtype of `$service`
42+
43+
### Changed
44+
45+
#### `Console`
46+
47+
- **Change default `Console::info()` prefix from `` to `> ` to address inconsistent output width on some platforms**
48+
- In `ConsoleLogger`, pass debug messages to `Console::debug()` so the caller is output
49+
- In `StreamTarget::reopen()`, don't throw an exception if the stream can't be reopened
50+
- Don't close targets when they are deregistered
51+
- Rename `ConsoleInterface` methods:
52+
- `getLogger()` -> `logger()`
53+
- `setTargetPrefix()` -> `setPrefix()`
54+
- `printOut()` -> `printStdio()`
55+
- `getErrorCount()` -> `errors()`
56+
- `getWarningCount()` -> `warnings()`
57+
- Remove `$type` parameters from `print()` methods in `ConsoleInterface`
58+
- Rename `StreamTarget` method `fromPath()` to more accurate `fromFile()`
59+
- Rename:
60+
- `ConsoleFormatInterface` -> `FormatInterface`
61+
- `ConsoleFormatterInterface` -> `FormatterInterface`
62+
- `ConsoleMessageAttributesInterface` -> `MessageAttributesInterface`
63+
- `ConsoleMessageFormatInterface` -> `MessageFormatInterface`
64+
- `ConsoleTagAttributesInterface` -> `TagAttributesInterface`
65+
- `ConsoleTargetInterface` -> `TargetInterface`
66+
- `ConsoleTargetPrefixInterface` -> `HasPrefix`
67+
- `ConsoleTargetStreamInterface` -> `StreamTargetInterface`
68+
- `ConsoleFormat` -> `TtyFormat`
69+
- `ConsoleFormatter` -> `Formatter`
70+
- `ConsoleLoopbackFormat` -> `LoopbackFormat`
71+
- `ConsoleManPageFormat` -> `ManPageFormat`
72+
- `ConsoleMarkdownFormat` -> `MarkdownFormat`
73+
- `ConsoleMessageAttributes` -> `MessageAttributes`
74+
- `ConsoleMessageFormat` -> `MessageFormat`
75+
- `ConsoleMessageFormats` -> `MessageFormats`
76+
- `ConsolePrefixTarget` -> `AbstractTargetWithPrefix`
77+
- `ConsoleStreamTarget` -> `AbstractStreamTarget`
78+
- `ConsoleTag` -> `HasTag` and add `TAG_` prefix to the names of its constants
79+
- `ConsoleTagAttributes` -> `TagAttributes`
80+
- `ConsoleTagFormats` -> `TagFormats`
81+
- `ConsoleTarget` -> `AbstractTarget`
82+
- `ConsoleTargetTypeFlag` -> `HasTargetFlag` and add `TARGET_` prefix to the names of its constants
83+
- Rename methods:
84+
- `withUnescape()` -> `withRemoveEscapes()`
85+
- `getUnescape()` -> `removesEscapes()`
86+
- `withWrapAfterApply()` -> `withWrapAfterFormatting()`
87+
- `getWrapAfterApply()` -> `wrapsAfterFormatting()`
88+
- Replace "dim" with "faint" in method names and documentation
89+
90+
#### `Container`
91+
92+
- **Remove `$args` parameters from `bind()`, `bindIf()`, `singleton()` and `singletonIf()`**
93+
- **Rename `getName()` -> `getClass()`**
94+
- In `getAs()`, throw an exception if `$id` doesn't extend or implement `$service`
95+
- In `providers()`, map unmapped providers to themselves
96+
- Rename event and exception interfaces for consistency
97+
- Rename `ArgumentsNotUsedException` -> `UnusedArgumentsException`
98+
- Rename `ServiceLifetime` -> `HasServiceLifetime` and add `LIFETIME_` prefix to the names of its constants
99+
- Rename `ApplicationInterface` methods:
100+
- `getAppName()` -> `getName()`
101+
- `isProduction()` -> `isRunningInProduction()`
102+
- `registerSyncNamespace()` -> `sync()` and start the entity store implicitly when called
103+
- `getWorkingDirectory()` -> `getInitialWorkingDirectory()`
104+
- `setWorkingDirectory()` -> `setInitialWorkingDirectory()` and remove nullability from parameter
105+
- In `Application`:
106+
- **Always load `.env` files (previously, they were only loaded when running from source)**
107+
- **Write console messages to `STDERR` when running on the command line (previously, warnings and errors were written to `STDERR` and everything else was written to `STDOUT` by default)**
108+
- Retrieve application name from configuration value `"app.name"` before falling back to script basename
109+
- Only stop cache and sync store instances started by the container
110+
111+
### Removed
112+
113+
#### `Cli`
114+
115+
- Remove redundant `CliApplicationInterface` methods `reportVersion()` and `getVersionString()`
116+
117+
#### `Console`
118+
119+
- Remove `ConsoleInterface` methods:
120+
- `getFormatter()` (retrieve via `getTtyTarget()`)
121+
- `getWidth()` (retrieve via `getTtyTarget()`)
122+
- `printStderr()` (unused)
123+
- Remove `FormatterInterface::withSpinnerState()`
124+
- Remove `StreamTargetInterface::getEol()`
125+
- Remove `StreamTarget::fromStream()` in favour of a public constructor
126+
- Remove unused `StreamTarget` method `getPath()`
127+
128+
#### `Container`
129+
130+
- Remove unused `unbind()` method
131+
- Remove implementation-specific `resumeCache()` method from `ApplicationInterface`
132+
- Remove implementation-specific arguments from `startSync()` method in `ApplicationInterface`
133+
134+
### Fixed
135+
136+
#### `Console`
137+
138+
- Fix issue where `$msg2` is not indented when prefix width is 2
139+
- Fix issue where trailing carriage returns may not be preserved in `LoopbackFormat`, `ManPageFormat` and `MarkdownFormat`
140+
141+
#### `Container`
142+
143+
- Fix issue where calling `bind()` with `$class = null` does not replace an existing binding to a different class
144+
- Fix issue where parameter bindings only work for untyped and scalar constructor parameters
145+
- Fix issue where objects cannot be bound to constructor parameters via contextual bindings
146+
- Fix `addContextualBinding()` issue where `$class` cannot be `null`
147+
- Fix `inContextOf()` issue where contextual callbacks are not used
148+
- Fix issue where `Application::logOutput()` can be called repeatedly
149+
150+
#### `Core`
151+
152+
- Fix issue where methods that return `$this` from behind a facade return a facade-bound instance
153+
154+
### Security
155+
156+
#### `Console`
157+
158+
- Fix issue where `StreamTarget::reopen()` may not apply file permissions correctly in some log rotation scenarios
159+
13160
## [v0.99.77] - 2025-03-04
14161

15162
### Added
@@ -4770,6 +4917,7 @@ This is the final release of `lkrms/util`. It is moving to [Salient](https://git
47704917

47714918
- Allow `CliOption` value names to contain arbitrary characters
47724919

4920+
[v0.99.78]: https://github.com/salient-labs/toolkit/compare/v0.99.77...v0.99.78
47734921
[v0.99.77]: https://github.com/salient-labs/toolkit/compare/v0.99.76...v0.99.77
47744922
[v0.99.76]: https://github.com/salient-labs/toolkit/compare/v0.99.75...v0.99.76
47754923
[v0.99.75]: https://github.com/salient-labs/toolkit/compare/v0.99.74...v0.99.75

docs/Console.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ To override these defaults:
4646
| `error()`, `errorOnce()` | `LEVEL_ERROR` = `3` | ` ! ` |
4747
| `warn()`, `warnOnce()` | `LEVEL_WARNING` = `4` | ` ^ ` |
4848
| `group()`,`groupEnd()` | `LEVEL_NOTICE` = `5` | ` » `, ` « ` |
49-
| `info()`, `infoOnce()` | `LEVEL_NOTICE` = `5` | ` ` |
49+
| `info()`, `infoOnce()` | `LEVEL_NOTICE` = `5` | ` > ` |
5050
| `log()`, `logOnce()` | `LEVEL_INFO` = `6` | ` - ` |
5151
| `logProgress()`, `clearProgress()` | `LEVEL_INFO` = `6` | `` (spinner, TTY only) |
5252
| `debug()`, `debugOnce()` | `LEVEL_DEBUG` = `7` | ` : ` |

scripts/check-coverage.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
'Core' => ['Core', true, true, '2025-02-14: code review finalised, replacement of `Legacy` classes still pending'],
3030
'Iterator' => ['Iterators', true, true, '2025-02-26: code review finalised'],
3131
'Cache' => ['Cache', true, true, '2025-02-23: code review finalised'],
32-
'Console' => ['Console', null, null, null],
32+
'Console' => ['Console', true, true, '2025-03-28: code review finalised except `Formatter` rewrite'],
3333
'Container' => ['Container', true, true, '2025-03-11: code review finalised except `Container` rewrite sans Dice'],
3434
'Http' => ['HTTP', null, null, null],
3535
'Db' => ['Db', null, null, null],

src/Toolkit/Cli/CliApplication.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace Salient\Cli;
44

55
use Salient\Cli\Exception\CliInvalidArgumentsException;
6-
use Salient\Console\Format\Formatter;
76
use Salient\Console\Format\ManPageFormat;
87
use Salient\Console\Format\MarkdownFormat;
98
use Salient\Container\Application;
@@ -233,7 +232,7 @@ private function getHelp(string $name, $node, ?CliHelpStyle $style = null): ?str
233232
foreach ($node as $childName => $childNode) {
234233
$command = $this->getNodeCommand(trim("$name $childName"), $childNode);
235234
if ($command) {
236-
$synopses[] = '__' . $childName . '__ - ' . Formatter::escapeTags($command->getDescription());
235+
$synopses[] = '__' . $childName . '__ - ' . Console::escape($command->getDescription());
237236
} elseif (is_array($childNode)) {
238237
$synopses[] = '__' . $childName . '__';
239238
}
@@ -260,7 +259,7 @@ private function getUsage(string $name, $node): ?string
260259

261260
if ($command) {
262261
return $command->getSynopsis($style)
263-
. Formatter::escapeTags("\n\nSee '"
262+
. Console::escape("\n\nSee '"
264263
. ($name === '' ? "$progName --help" : "$progName help $name")
265264
. "' for more information.");
266265
}
@@ -279,15 +278,15 @@ private function getUsage(string $name, $node): ?string
279278
$synopsis = $command->getSynopsis($style);
280279
} elseif (is_array($childNode)) {
281280
$synopsis = "$fullName $childName <command>";
282-
$synopsis = Formatter::escapeTags($synopsis);
281+
$synopsis = Console::escape($synopsis);
283282
} else {
284283
continue;
285284
}
286285
$synopses[] = $synopsis;
287286
}
288287

289288
return implode("\n", $synopses)
290-
. Formatter::escapeTags("\n\nSee '"
289+
. Console::escape("\n\nSee '"
291290
. Arr::implode(' ', ["$progName help", $name, '<command>'])
292291
. "' for more information.");
293292
}

src/Toolkit/Cli/CliCommand.php

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
use Salient\Cli\Exception\CliInvalidArgumentsException;
66
use Salient\Cli\Exception\CliUnknownValueException;
7-
use Salient\Console\Format\Formatter;
87
use Salient\Contract\Cli\CliApplicationInterface;
98
use Salient\Contract\Cli\CliCommandInterface;
109
use Salient\Contract\Cli\CliHelpSectionName;
@@ -314,7 +313,7 @@ final public function getHelp(?CliHelpStyleInterface $style = null): array
314313
) {
315314
foreach ($option->AllowedValues ?: [true, false] as $optionValue) {
316315
$optionValue = $option->normaliseValueForHelp($optionValue);
317-
$allowed[] = $em . Formatter::escapeTags($optionValue) . $em;
316+
$allowed[] = $em . Console::escape($optionValue) . $em;
318317
}
319318
if (!$option->AllowedValues) {
320319
$booleanValue = true;
@@ -382,7 +381,7 @@ final public function getHelp(?CliHelpStyleInterface $style = null): array
382381
$booleanValue
383382
|| mb_strlen($formatter->wrapsAfterFormatting()
384383
? $formatter->format($indent . $_line)
385-
: Formatter::removeTags($indent . $_line)) <= ($width ?: 76)
384+
: Console::removeTags($indent . $_line)) <= ($width ?: 76)
386385
) {
387386
$allowed = null;
388387
} else {
@@ -428,7 +427,7 @@ final public function getHelp(?CliHelpStyleInterface $style = null): array
428427
continue;
429428
}
430429
$value = $option->normaliseValueForHelp($value);
431-
$default[] = $em . Formatter::escapeTags($value) . $em;
430+
$default[] = $em . Console::escape($value) . $em;
432431
}
433432
$default = implode(Str::coalesce($option->Delimiter, ' '), $default);
434433
if ($default !== '') {
@@ -445,8 +444,8 @@ final public function getHelp(?CliHelpStyleInterface $style = null): array
445444
. ($lines ? $descriptionPrefix . ltrim(implode("\n\n", $lines)) : '');
446445
}
447446

448-
$name = Formatter::escapeTags($this->getNameWithProgram());
449-
$summary = Formatter::escapeTags($this->getDescription());
447+
$name = Console::escape($this->getNameWithProgram());
448+
$summary = Console::escape($this->getDescription());
450449
$synopsis = $this->getSynopsis($style);
451450

452451
$description = $this->getLongDescription() ?? '';

src/Toolkit/Cli/CliHelpStyle.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace Salient\Cli;
44

5-
use Salient\Console\Format\Formatter;
65
use Salient\Console\Format\LoopbackFormat;
76
use Salient\Console\Format\ManPageFormat;
87
use Salient\Console\Format\MarkdownFormat;
@@ -262,7 +261,7 @@ public function maybeEscapeTags(string $string): string
262261
if ($this->HasMarkup) {
263262
return $string;
264263
}
265-
return Formatter::escapeTags($string);
264+
return Console::escape($string);
266265
}
267266

268267
public static function getConsoleWidth(): ?int

src/Toolkit/Console/Console.php

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace Salient\Console;
44

55
use Psr\Log\LoggerInterface;
6-
use Salient\Console\Format\Formatter;
76
use Salient\Console\Target\StreamTarget;
87
use Salient\Contract\Console\Target\HasPrefix;
98
use Salient\Contract\Console\Target\StreamTargetInterface;
@@ -224,7 +223,23 @@ public function getTtyTarget(): StreamTargetInterface
224223
*/
225224
public function escape(string $string, bool $escapeNewlines = false): string
226225
{
227-
return Formatter::escapeTags($string, $escapeNewlines);
226+
return ConsoleUtil::escape($string, $escapeNewlines);
227+
}
228+
229+
/**
230+
* @inheritDoc
231+
*/
232+
public function removeEscapes(string $string): string
233+
{
234+
return ConsoleUtil::removeEscapes($string);
235+
}
236+
237+
/**
238+
* @inheritDoc
239+
*/
240+
public function removeTags(string $string): string
241+
{
242+
return ConsoleUtil::removeTags($string);
228243
}
229244

230245
/**

src/Toolkit/Console/ConsoleLogger.php

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use Throwable;
1111

1212
/**
13-
* A PSR-3 logger backed by a console service
13+
* @api
1414
*/
1515
class ConsoleLogger implements LoggerInterface
1616
{
@@ -27,6 +27,9 @@ class ConsoleLogger implements LoggerInterface
2727

2828
private Console $Console;
2929

30+
/**
31+
* @api
32+
*/
3033
public function __construct(Console $console)
3134
{
3235
$this->Console = $console;
@@ -93,7 +96,8 @@ public function info($message, array $context = [])
9396
*/
9497
public function debug($message, array $context = [])
9598
{
96-
$this->log(LogLevel::DEBUG, $message, $context);
99+
$msg1 = $this->applyContext((string) $message, $context, $ex);
100+
$this->Console->debug($msg1, null, $ex, 1);
97101
}
98102

99103
/**
@@ -105,6 +109,16 @@ public function log($level, $message, array $context = [])
105109
throw new InvalidArgumentException('Invalid log level');
106110
}
107111

112+
$msg1 = $this->applyContext((string) $message, $context, $ex);
113+
$level = self::LOG_LEVEL_MAP[$level];
114+
$this->Console->message($msg1, null, $level, Console::TYPE_STANDARD, $ex);
115+
}
116+
117+
/**
118+
* @param mixed[] $context
119+
*/
120+
private function applyContext(string $message, array $context, ?Throwable &$ex): string
121+
{
108122
if ($context) {
109123
foreach ($context as $key => $value) {
110124
$replace['{' . $key . '}'] = Format::value($value);
@@ -115,16 +129,10 @@ public function log($level, $message, array $context = [])
115129
isset($context['exception'])
116130
&& $context['exception'] instanceof Throwable
117131
) {
118-
$exception = $context['exception'];
132+
$ex = $context['exception'];
119133
}
120134
}
121135

122-
$this->Console->message(
123-
(string) $message,
124-
null,
125-
self::LOG_LEVEL_MAP[$level],
126-
Console::TYPE_STANDARD,
127-
$exception ?? null,
128-
);
136+
return $message;
129137
}
130138
}

0 commit comments

Comments
 (0)