diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6382de2..2f3eecb 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -4,96 +4,11 @@ on: [push, pull_request]
jobs:
blackbox:
- runs-on: ${{ matrix.os }}
- strategy:
- matrix:
- os: [ubuntu-latest, macOS-latest]
- php-version: ['8.2', '8.3']
- dependencies: ['lowest', 'highest']
- name: 'BlackBox'
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- - name: Graphviz
- uses: tlylt/install-graphviz@main
- - name: Setup PHP
- uses: shivammathur/setup-php@v2
- with:
- php-version: ${{ matrix.php-version }}
- extensions: mbstring, intl
- coverage: none
- - name: Composer
- uses: "ramsey/composer-install@v2"
- with:
- dependency-versions: ${{ matrix.dependencies }}
- - name: BlackBox
- run: php blackbox.php
+ uses: innmind/github-workflows/.github/workflows/black-box-matrix.yml@main
coverage:
- runs-on: ${{ matrix.os }}
- strategy:
- matrix:
- os: [ubuntu-latest, macOS-latest]
- php-version: ['8.2', '8.3']
- dependencies: ['lowest', 'highest']
- name: 'Coverage'
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- - name: Graphviz
- uses: tlylt/install-graphviz@main
- - name: Setup PHP
- uses: shivammathur/setup-php@v2
- with:
- php-version: ${{ matrix.php-version }}
- extensions: mbstring, intl
- coverage: xdebug
- - name: Composer
- uses: "ramsey/composer-install@v2"
- with:
- dependency-versions: ${{ matrix.dependencies }}
- - name: BlackBox
- run: php blackbox.php
- env:
- ENABLE_COVERAGE: 'true'
- - uses: codecov/codecov-action@v3
- with:
- token: ${{ secrets.CODECOV_TOKEN }}
+ uses: innmind/github-workflows/.github/workflows/coverage-matrix.yml@main
+ secrets: inherit
psalm:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- php-version: ['8.2', '8.3']
- dependencies: ['lowest', 'highest']
- name: 'Psalm'
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- - name: Setup PHP
- uses: shivammathur/setup-php@v2
- with:
- php-version: ${{ matrix.php-version }}
- extensions: mbstring, intl
- - name: Composer
- uses: "ramsey/composer-install@v2"
- with:
- dependency-versions: ${{ matrix.dependencies }}
- - name: Psalm
- run: vendor/bin/psalm --shepherd
+ uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@main
cs:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- php-version: ['8.2']
- name: 'CS'
- steps:
- - name: Checkout
- uses: actions/checkout@v4
- - name: Setup PHP
- uses: shivammathur/setup-php@v2
- with:
- php-version: ${{ matrix.php-version }}
- extensions: mbstring, intl
- - name: Composer
- uses: "ramsey/composer-install@v2"
- - name: CS
- run: vendor/bin/php-cs-fixer fix --diff --dry-run
+ uses: innmind/github-workflows/.github/workflows/cs.yml@main
diff --git a/.gitignore b/.gitignore
index ff72e2d..4f88347 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
/composer.lock
/vendor
+/var/*
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4b94ef3..20796b4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog
+## [Unreleased]
+
+### Changed
+
+- Requires PHP `8.4`
+- Requires `innmind/profiler:~5.0`
+- Requires `innmind/framework:~4.0`
+
+### Removed
+
+- App graph is no longer recorded
+- The processes output and exit code are not recorded
+
## 4.0.0 - 2023-11-26
### Changed
diff --git a/composer.json b/composer.json
index 7542a14..6dea80c 100644
--- a/composer.json
+++ b/composer.json
@@ -15,12 +15,12 @@
"issues": "http://github.com/Innmind/Debug/issues"
},
"require": {
- "php": "~8.2",
- "innmind/profiler": "~4.0",
- "innmind/framework": "~2.0",
- "innmind/http": "~7.0",
- "innmind/stack-trace": "~4.0",
- "innmind/object-graph": "~3.1"
+ "php": "~8.4",
+ "innmind/profiler": "~5.0",
+ "innmind/framework": "~4.1",
+ "innmind/foundation": "~2.1",
+ "innmind/stack-trace": "~5.0",
+ "innmind/object-graph": "~3.4"
},
"autoload": {
"psr-4": {
@@ -33,11 +33,11 @@
}
},
"require-dev": {
- "vimeo/psalm": "~5.6",
- "innmind/black-box": "~5.5",
+ "innmind/static-analysis": "~1.3",
+ "innmind/black-box": "~6.5",
"innmind/coding-standard": "~2.0"
},
"provide": {
- "innmind/framework-middlewares": "1.0"
+ "innmind/framework-middlewares": "4.0"
}
}
diff --git a/psalm.xml b/psalm.xml
index 5d768ff..a270b9b 100644
--- a/psalm.xml
+++ b/psalm.xml
@@ -14,4 +14,7 @@
+
+
+
diff --git a/src/Cli/Record.php b/src/Cli/Record.php
new file mode 100644
index 0000000..b656aaf
--- /dev/null
+++ b/src/Cli/Record.php
@@ -0,0 +1,124 @@
+environment()->arguments()->toList());
+
+ return $this
+ ->profiler
+ ->start($name)
+ ->map(fn($profile) => _Record\Profile::of(
+ $this->profiler,
+ $profile,
+ ))
+ ->flatMap(
+ static fn($record) => $record(
+ static fn($mutation) => $mutation
+ ->sections()
+ ->environment()
+ ->record($console->variables()),
+ )->map(static fn() => $record),
+ )
+ ->flatMap(function($record) use ($console) {
+ $this->recorder->push($record);
+
+ return ($this->inner)($console)->eitherWay(
+ fn($console) => $this->result($console, $record),
+ fn($e) => $this->error($e, $record),
+ );
+ })
+ ->map(function($console) {
+ $this->recorder->push(new _Record\Nothing);
+
+ return $console;
+ })
+ ->mapError(function($e) {
+ $this->recorder->push(new _Record\Nothing);
+
+ return $e;
+ });
+ }
+
+ /**
+ * @internal
+ */
+ public static function of(
+ Command $inner,
+ Profiler $profiler,
+ Recorder $recorder,
+ Recorder\Exception $exception,
+ ): self {
+ return new self($inner, $profiler, $recorder, $exception);
+ }
+
+ /**
+ * @psalm-mutation-free
+ */
+ #[\Override]
+ public function usage(): Usage
+ {
+ return $this->inner->usage();
+ }
+
+ /**
+ * @return Attempt
+ */
+ private function result(
+ Console $console,
+ _Record $record,
+ ): Attempt {
+ return $record(
+ static fn($mutation) => $console->environment()->exitCode()->match(
+ static fn($exit) => match ($exit->successful()) {
+ true => $mutation->succeed('0'),
+ false => $mutation->fail((string) $exit->toInt()),
+ },
+ static fn() => $mutation->succeed('0'),
+ ),
+ )->map(static fn() => $console);
+ }
+
+ /**
+ * @return Attempt
+ */
+ private function error(
+ \Throwable $e,
+ _Record $record,
+ ): Attempt {
+ /** @var Attempt */
+ return ($this->exception)($e)
+ ->flatMap(static fn() => $record(
+ static fn($mutation) => $mutation->fail('crashed'),
+ ))
+ ->flatMap(static fn() => Attempt::error($e));
+ }
+}
diff --git a/src/Cli/RecordAppGraph.php b/src/Cli/RecordAppGraph.php
deleted file mode 100644
index 4a35cb7..0000000
--- a/src/Cli/RecordAppGraph.php
+++ /dev/null
@@ -1,42 +0,0 @@
-inner = $inner;
- $this->record = $record;
- }
-
- public function __invoke(Console $console): Console
- {
- try {
- return ($this->inner)($console);
- } finally {
- ($this->record)($this->inner);
- }
- }
-
- /**
- * @psalm-mutation-free
- */
- public function usage(): string
- {
- return $this->inner->usage();
- }
-}
diff --git a/src/Cli/RecordEnvironment.php b/src/Cli/RecordEnvironment.php
deleted file mode 100644
index 28f14ad..0000000
--- a/src/Cli/RecordEnvironment.php
+++ /dev/null
@@ -1,60 +0,0 @@
-record = new Record\Nothing;
- $this->inner = $inner;
- $this->env = $env;
- }
-
- public function __invoke(Console $console): Console
- {
- try {
- return ($this->inner)($console);
- } finally {
- ($this->record)(
- fn($mutation) => $mutation
- ->sections()
- ->environment()
- ->record($this->env->all()),
- );
- }
- }
-
- /**
- * @psalm-mutation-free
- */
- public function usage(): string
- {
- return $this->inner->usage();
- }
-
- public function push(Record $record): void
- {
- $this->record = $record;
- }
-}
diff --git a/src/Cli/RecordException.php b/src/Cli/RecordException.php
deleted file mode 100644
index 5cd1ec8..0000000
--- a/src/Cli/RecordException.php
+++ /dev/null
@@ -1,44 +0,0 @@
-inner = $inner;
- $this->record = $record;
- }
-
- public function __invoke(Console $console): Console
- {
- try {
- return ($this->inner)($console);
- } catch (\Throwable $e) {
- ($this->record)($e);
-
- throw $e;
- }
- }
-
- /**
- * @psalm-mutation-free
- */
- public function usage(): string
- {
- return $this->inner->usage();
- }
-}
diff --git a/src/Cli/StartProfile.php b/src/Cli/StartProfile.php
deleted file mode 100644
index d2b4e94..0000000
--- a/src/Cli/StartProfile.php
+++ /dev/null
@@ -1,73 +0,0 @@
-profiler = $profiler;
- $this->recorder = $recorder;
- $this->inner = $inner;
- }
-
- public function __invoke(Console $console): Console
- {
- $profile = $this->profiler->start(\implode(' ', $console->environment()->arguments()->toList()));
- $this->recorder->push(Record\Profile::of($this->profiler, $profile));
-
- try {
- $console = ($this->inner)($console);
- $this->profiler->mutate(
- $profile,
- static fn($mutation) => $console->environment()->exitCode()->match(
- static fn($exit) => match ($exit->successful()) {
- true => $mutation->succeed('0'),
- false => $mutation->fail((string) $exit->toInt()),
- },
- static fn() => $mutation->succeed('0'),
- ),
- );
-
- return $console;
- } catch (\Throwable $e) {
- $this->profiler->mutate(
- $profile,
- static fn($mutation) => $mutation->fail('crashed'),
- );
-
- throw $e;
- } finally {
- $this->recorder->push(new Record\Nothing);
- }
- }
-
- /**
- * @psalm-mutation-free
- */
- public function usage(): string
- {
- return $this->inner->usage();
- }
-}
diff --git a/src/Http/EndProfile.php b/src/Http/EndProfile.php
new file mode 100644
index 0000000..f59ed11
--- /dev/null
+++ b/src/Http/EndProfile.php
@@ -0,0 +1,64 @@
+
+ */
+final class EndProfile implements Recorder, Component\Provider
+{
+ private function __construct(
+ private Record $record = new Record\Nothing,
+ ) {
+ }
+
+ /**
+ * @internal
+ */
+ public static function new(): self
+ {
+ return new self;
+ }
+
+ #[\Override]
+ public function push(Record $record): void
+ {
+ /** @psalm-suppress InaccessibleProperty */
+ $this->record = $record;
+ }
+
+ #[\Override]
+ public function toComponent(): Component
+ {
+ return Component::of(
+ fn(ServerRequest $request, Response $response) => ($this->record)(
+ static fn($mutation) => $mutation
+ ->sections()
+ ->http()
+ ->respondedWith(Response\Stringable::new()($response)),
+ )
+ ->flatMap(
+ fn() => ($this->record)(
+ static fn($mutation) => match ($response->statusCode()->successful()) {
+ true => $mutation->succeed($response->statusCode()->toString()),
+ false => $mutation->fail($response->statusCode()->toString()),
+ },
+ ),
+ )
+ ->map(static fn() => $response),
+ );
+ }
+}
diff --git a/src/Http/FreeProfile.php b/src/Http/FreeProfile.php
new file mode 100644
index 0000000..fd09f1d
--- /dev/null
+++ b/src/Http/FreeProfile.php
@@ -0,0 +1,41 @@
+
+ */
+ public static function of(Recorder $recorder): Component
+ {
+ return Component::of(static function(
+ ServerRequest $request,
+ Response $input,
+ ) use (
+ $recorder,
+ ) {
+ $recorder->push(new Record\Nothing);
+
+ return Attempt::result($input);
+ });
+ }
+}
diff --git a/src/Http/RecordAppGraph.php b/src/Http/RecordAppGraph.php
deleted file mode 100644
index 86e1659..0000000
--- a/src/Http/RecordAppGraph.php
+++ /dev/null
@@ -1,41 +0,0 @@
-inner = $inner;
- $this->record = $record;
- $this->container = $container;
- }
-
- public function __invoke(ServerRequest $request): Response
- {
- try {
- return ($this->inner)($request);
- } finally {
- ($this->record)($this->container);
- }
- }
-}
diff --git a/src/Http/RecordCall.php b/src/Http/RecordCall.php
deleted file mode 100644
index 5b14755..0000000
--- a/src/Http/RecordCall.php
+++ /dev/null
@@ -1,53 +0,0 @@
-record = new Record\Nothing;
- $this->inner = $inner;
- }
-
- public function __invoke(ServerRequest $request): Response
- {
- ($this->record)(
- static fn($mutation) => $mutation
- ->sections()
- ->http()
- ->received(ServerRequest\Stringable::new()($request)),
- );
- $response = ($this->inner)($request);
- ($this->record)(
- static fn($mutation) => $mutation
- ->sections()
- ->http()
- ->respondedWith(Response\Stringable::new()($response)),
- );
-
- return $response;
- }
-
- public function push(Record $record): void
- {
- $this->record = $record;
- }
-}
diff --git a/src/Http/RecordEnvironment.php b/src/Http/RecordEnvironment.php
index 621b074..13cc544 100644
--- a/src/Http/RecordEnvironment.php
+++ b/src/Http/RecordEnvironment.php
@@ -7,49 +7,49 @@
Recorder,
Record,
};
-use Innmind\Framework\{
- Http\RequestHandler,
- Environment,
-};
-use Innmind\Http\{
- ServerRequest,
- Response,
-};
+use Innmind\Framework\Environment;
+use Innmind\Router\Component;
+use Innmind\Http\ServerRequest;
+use Innmind\Immutable\SideEffect;
/**
* @internal
+ * @psalm-immutable
+ * @implements Component\Provider
*/
-final class RecordEnvironment implements RequestHandler, Recorder
+final class RecordEnvironment implements Recorder, Component\Provider
{
- private Record $record;
- private RequestHandler $inner;
- private Environment $env;
-
- public function __construct(
- RequestHandler $inner,
- Environment $env,
+ private function __construct(
+ private Environment $env,
+ private Record $record = new Record\Nothing,
) {
- $this->record = new Record\Nothing;
- $this->inner = $inner;
- $this->env = $env;
}
- public function __invoke(ServerRequest $request): Response
+ /**
+ * @internal
+ */
+ public static function of(Environment $env): self
{
- try {
- return ($this->inner)($request);
- } finally {
- ($this->record)(
- fn($mutation) => $mutation
- ->sections()
- ->environment()
- ->record($this->env->all()),
- );
- }
+ return new self($env);
}
+ #[\Override]
public function push(Record $record): void
{
+ /** @psalm-suppress InaccessibleProperty */
$this->record = $record;
}
+
+ #[\Override]
+ public function toComponent(): Component
+ {
+ return Component::of(
+ fn(ServerRequest $request, SideEffect $input) => ($this->record)(
+ fn($mutation) => $mutation
+ ->sections()
+ ->environment()
+ ->record($this->env->all()),
+ )->map(static fn() => $input),
+ );
+ }
}
diff --git a/src/Http/RecordException.php b/src/Http/RecordException.php
index c540354..43209a5 100644
--- a/src/Http/RecordException.php
+++ b/src/Http/RecordException.php
@@ -3,35 +3,74 @@
namespace Innmind\Debug\Http;
-use Innmind\Debug\Recorder\Exception;
-use Innmind\Framework\Http\RequestHandler;
+use Innmind\Debug\{
+ Recorder,
+ Recorder\Exception,
+ Record,
+};
+use Innmind\Router\{
+ Component,
+ Exception\NotFound,
+};
use Innmind\Http\{
ServerRequest,
Response,
};
+use Innmind\Immutable\{
+ Attempt,
+ SideEffect,
+};
/**
* @internal
*/
-final class RecordException implements RequestHandler
+final class RecordException implements Recorder
{
- private RequestHandler $inner;
- private Exception $record;
+ private function __construct(
+ private Exception $exception,
+ private Record $record = new Record\Nothing,
+ ) {
+ }
- public function __construct(RequestHandler $inner, Exception $record)
+ /**
+ * @return Component
+ */
+ public function __invoke(\Throwable $e): Component
{
- $this->inner = $inner;
- $this->record = $record;
+ if ($e instanceof NotFound) {
+ /** @var Component */
+ return Component::of(static fn(
+ ServerRequest $request,
+ SideEffect $_,
+ ) => Attempt::error($e));
+ }
+
+ /** @var Attempt */
+ $result = ($this->exception)($e)->flatMap(
+ fn() => ($this->record)(
+ static fn($mutation) => $mutation->fail('crashed'),
+ ),
+ )->flatMap(static fn() => Attempt::error($e));
+
+ /** @var Component */
+ return Component::of(static fn(
+ ServerRequest $request,
+ SideEffect $_,
+ ) => $result);
}
- public function __invoke(ServerRequest $request): Response
+ /**
+ * @internal
+ */
+ public static function of(Exception $record): self
{
- try {
- return ($this->inner)($request);
- } catch (\Throwable $e) {
- ($this->record)($e);
+ return new self($record);
+ }
- throw $e;
- }
+ #[\Override]
+ public function push(Record $record): void
+ {
+ $this->record = $record;
+ $this->exception->push($record);
}
}
diff --git a/src/Http/StartProfile.php b/src/Http/StartProfile.php
index 78959de..93e2438 100644
--- a/src/Http/StartProfile.php
+++ b/src/Http/StartProfile.php
@@ -7,76 +7,60 @@
Recorder,
Record,
};
-use Innmind\Framework\Http\RequestHandler;
use Innmind\Profiler\Profiler;
-use Innmind\Http\{
- ServerRequest,
- Response,
+use Innmind\Router\Component;
+use Innmind\Http\ServerRequest;
+use Innmind\Immutable\{
+ Attempt,
+ SideEffect,
};
/**
* @internal
*/
-final class StartProfile implements RequestHandler
+final class StartProfile
{
- private Profiler $profiler;
- private Recorder $recorder;
- private RequestHandler $inner;
-
- public function __construct(
- Profiler $profiler,
- Recorder $recorder,
- RequestHandler $inner,
- ) {
- $this->profiler = $profiler;
- $this->recorder = $recorder;
- $this->inner = $inner;
- }
-
- public function __invoke(ServerRequest $request): Response
+ /**
+ * @internal
+ * @psalm-pure
+ *
+ * @return Component
+ */
+ public static function of(Profiler $profiler, Recorder $recorder): Component
{
- $inProfiler = \str_starts_with($request->url()->path()->toString(), '/_profiler');
-
- return match ($inProfiler) {
- true => $this->dontProfile($request),
- false => $this->profile($request),
- };
- }
-
- private function dontProfile(ServerRequest $request): Response
- {
- return ($this->inner)($request);
- }
-
- private function profile(ServerRequest $request): Response
- {
- $profile = $this->profiler->start(\sprintf(
- '%s %s',
- $request->method()->toString(),
- $request->url()->path()->toString(),
- ));
- $this->recorder->push(Record\Profile::of($this->profiler, $profile));
-
- try {
- $response = ($this->inner)($request);
- $this->profiler->mutate(
- $profile,
- static fn($mutation) => match ($response->statusCode()->successful()) {
- true => $mutation->succeed($response->statusCode()->toString()),
- false => $mutation->fail($response->statusCode()->toString()),
- },
- );
+ return Component::of(static function(
+ ServerRequest $request,
+ SideEffect $input,
+ ) use (
+ $profiler,
+ $recorder,
+ ) {
+ $inProfiler = \str_starts_with($request->url()->path()->toString(), '/_profiler');
- return $response;
- } catch (\Throwable $e) {
- $this->profiler->mutate(
- $profile,
- static fn($mutation) => $mutation->fail('crashed'),
- );
+ if ($inProfiler) {
+ return Attempt::result($input);
+ }
- throw $e;
- } finally {
- $this->recorder->push(new Record\Nothing);
- }
+ return $profiler
+ ->start(\sprintf(
+ '%s %s',
+ $request->method()->toString(),
+ $request->url()->path()->toString(),
+ ))
+ ->map(static fn($profile) => Record\Profile::of(
+ $profiler,
+ $profile,
+ ))
+ ->flatMap(
+ static fn($record) => $record(
+ static fn($mutation) => $mutation
+ ->sections()
+ ->http()
+ ->received(ServerRequest\Stringable::new()($request)),
+ )->map(static fn() => $record),
+ )
+ ->map($recorder->push(...))
+ ->map(static fn(): mixed => $input);
+ });
}
}
diff --git a/src/Kernel.php b/src/Kernel.php
index 0d41815..7c87edb 100644
--- a/src/Kernel.php
+++ b/src/Kernel.php
@@ -21,6 +21,7 @@ private function __construct(IDE $ide, FormatPath $formatPath)
$this->formatPath = $formatPath;
}
+ #[\Override]
public function __invoke(Application $app): Application
{
return $app
diff --git a/src/Kernel/App.php b/src/Kernel/App.php
index 282848f..2bf6558 100644
--- a/src/Kernel/App.php
+++ b/src/Kernel/App.php
@@ -9,6 +9,7 @@
Recorder,
IDE,
};
+use Innmind\Profiler;
use Innmind\Framework\{
Application,
Middleware,
@@ -31,92 +32,65 @@ public function __construct(IDE $ide, FormatPath $formatPath)
$this->formatPath = $formatPath;
}
+ #[\Override]
public function __invoke(Application $app): Application
{
return $app
- ->service('innmind/debug.appGraph', fn($get, $os, $env) => new Recorder\AppGraph(
- $os,
- $env->all()->filter(static fn($name) => $name === 'PATH'),
- $this->ide,
- ))
- ->service('innmind/debug.exception', fn($get, $os, $env) => new Recorder\Exception(
+ ->service(Services::exception, fn($get, $os, $env) => new Recorder\Exception(
$os,
$env->all()->filter(static fn($name) => $name === 'PATH'),
$this->ide,
$this->formatPath,
))
- ->mapCommand(static function($command, $get, $os, $env) {
- $appGraph = $get('innmind/debug.appGraph');
- $exception = $get('innmind/debug.exception');
-
- $recordAppGraph = new Cli\RecordAppGraph(
- $command,
- $appGraph,
- );
- $recordException = new Cli\RecordException(
- $recordAppGraph,
- $exception,
- );
- $recordEnvironment = new Cli\RecordEnvironment(
- $recordException,
- $env,
- );
- $all = [
- $appGraph,
- $exception,
- $recordEnvironment,
- ];
-
+ ->service(Services::httpEndProfile, static fn() => Http\EndProfile::new())
+ ->service(Services::httpEnvironment, static fn($_, $__, $env) => Http\RecordEnvironment::of($env))
+ ->service(Services::httpException, static fn($get) => Http\RecordException::of(
+ $get(Services::exception()),
+ ))
+ ->service(Services::httpRecorder, static function($get) {
try {
- $all[] = $get('innmind/debug.beacon');
- } catch (ServiceNotFound $e) {
- // pass
+ $os = [$get(Services::beacon())];
+ } catch (ServiceNotFound) {
// this means the user didn't use the OS kernel
+ $os = [];
}
- return new Cli\StartProfile(
- $get('innmind/profiler'),
- Recorder\All::of(...$all),
- $recordEnvironment,
+ return Recorder\All::of(
+ $get(Services::httpEndProfile()),
+ $get(Services::httpEnvironment()),
+ $get(Services::httpException()),
+ ...$os,
);
})
- ->mapRequestHandler(static function($handler, $get, $os, $env) {
- $appGraph = $get('innmind/debug.appGraph');
- $exception = $get('innmind/debug.exception');
-
- $recordAppGraph = new Http\RecordAppGraph(
- $handler,
- $appGraph,
- $get,
- );
- $recordException = new Http\RecordException(
- $recordAppGraph,
- $exception,
- );
- $recordEnvironment = new Http\RecordEnvironment(
- $recordException,
- $env,
- );
- $recordCall = new Http\RecordCall($recordEnvironment);
- $all = [
- $appGraph,
- $exception,
- $recordEnvironment,
- $recordCall,
- ];
-
+ ->service(Services::cliRecorder, static function($get) {
try {
- $all[] = $get('innmind/debug.beacon');
- } catch (ServiceNotFound $e) {
- // pass
+ $os = [$get(Services::beacon())];
+ } catch (ServiceNotFound) {
// this means the user didn't use the OS kernel
+ $os = [];
}
- return new Http\StartProfile(
- $get('innmind/profiler'),
- Recorder\All::of(...$all),
- $recordCall,
+ return Recorder\All::of(
+ $get(Services::exception()),
+ ...$os,
);
- });
+ })
+ ->mapCommand(static fn($command, $get) => Cli\Record::of(
+ $command,
+ $get(Profiler\Web\Services::profiler()),
+ $get(Services::cliRecorder()),
+ $get(Services::exception()),
+ ))
+ ->mapRoutes(
+ static fn($route, $get) => Http\StartProfile::of(
+ $get(Profiler\Web\Services::profiler()),
+ $get(Services::httpRecorder()),
+ )
+ ->pipe($get(Services::httpEnvironment()))
+ ->pipe($route)
+ ->otherwise($get(Services::httpException()))
+ ->pipe($get(Services::httpEndProfile()))
+ ->pipe(Http\FreeProfile::of($get(Services::httpRecorder()))),
+ );
}
}
diff --git a/src/Kernel/OperatingSystem.php b/src/Kernel/OperatingSystem.php
index 8e71615..a8dfebb 100644
--- a/src/Kernel/OperatingSystem.php
+++ b/src/Kernel/OperatingSystem.php
@@ -5,7 +5,7 @@
use Innmind\Debug\{
Recorder\Beacon,
- OperatingSystem as Debug,
+ OperatingSystem\Debug,
};
use Innmind\Framework\{
Application,
@@ -17,12 +17,13 @@
*/
final class OperatingSystem implements Middleware
{
+ #[\Override]
public function __invoke(Application $app): Application
{
$beacon = new Beacon;
return $app
- ->service('innmind/debug.beacon', static fn() => $beacon)
- ->mapOperatingSystem(static fn($os) => Debug::of($os, $beacon));
+ ->service(Services::beacon, static fn() => $beacon)
+ ->mapOperatingSystem(static fn($os) => $os->map(Debug::of($beacon)));
}
}
diff --git a/src/Kernel/Services.php b/src/Kernel/Services.php
new file mode 100644
index 0000000..59a68d2
--- /dev/null
+++ b/src/Kernel/Services.php
@@ -0,0 +1,105 @@
+
+ */
+enum Services implements Service
+{
+ case beacon;
+ case exception;
+ case cliRecorder;
+ case httpRecorder;
+ case httpEnvironment;
+ case httpException;
+ case httpEndProfile;
+
+ /**
+ * @internal
+ *
+ * @return self
+ */
+ public static function beacon(): self
+ {
+ /** @var self */
+ return self::beacon;
+ }
+
+ /**
+ * @internal
+ *
+ * @return self
+ */
+ public static function exception(): self
+ {
+ /** @var self */
+ return self::exception;
+ }
+
+ /**
+ * @internal
+ *
+ * @return self
+ */
+ public static function cliRecorder(): self
+ {
+ /** @var self */
+ return self::cliRecorder;
+ }
+
+ /**
+ * @internal
+ *
+ * @return self
+ */
+ public static function httpRecorder(): self
+ {
+ /** @var self */
+ return self::httpRecorder;
+ }
+
+ /**
+ * @internal
+ *
+ * @return self
+ */
+ public static function httpEndProfile(): self
+ {
+ /** @var self */
+ return self::httpEndProfile;
+ }
+
+ /**
+ * @internal
+ *
+ * @return self
+ */
+ public static function httpEnvironment(): self
+ {
+ /** @var self */
+ return self::httpEnvironment;
+ }
+
+ /**
+ * @internal
+ *
+ * @return self
+ */
+ public static function httpException(): self
+ {
+ /** @var self */
+ return self::httpException;
+ }
+}
diff --git a/src/OperatingSystem.php b/src/OperatingSystem.php
deleted file mode 100644
index e7cb1a8..0000000
--- a/src/OperatingSystem.php
+++ /dev/null
@@ -1,89 +0,0 @@
-inner = $inner;
- $this->beacon = $beacon;
- }
-
- public static function of(OS $inner, Beacon $beacon): self
- {
- return new self($inner, $beacon);
- }
-
- public function map(callable $map): self
- {
- return new self(
- $this->inner->map($map),
- $this->beacon,
- );
- }
-
- public function clock(): Clock
- {
- return $this->inner->clock();
- }
-
- public function filesystem(): Filesystem
- {
- return $this->inner->filesystem();
- }
-
- public function status(): ServerStatus
- {
- return $this->inner->status();
- }
-
- public function control(): ServerControl
- {
- return Control::of($this->inner->control(), $this->beacon);
- }
-
- public function ports(): Ports
- {
- return $this->inner->ports();
- }
-
- public function sockets(): Sockets
- {
- return $this->inner->sockets();
- }
-
- public function remote(): Remote
- {
- return RemoteDebug::of($this->inner->remote(), $this->beacon);
- }
-
- public function process(): CurrentProcess
- {
- return $this->inner->process();
- }
-}
diff --git a/src/OperatingSystem/Control.php b/src/OperatingSystem/Control.php
deleted file mode 100644
index dc4bbd9..0000000
--- a/src/OperatingSystem/Control.php
+++ /dev/null
@@ -1,55 +0,0 @@
-inner = $inner;
- $this->beacon = $beacon;
- }
-
- public static function of(Server $inner, Beacon $beacon): self
- {
- return new self($inner, $beacon);
- }
-
- public function processes(): Processes
- {
- return Debug::of($this->inner->processes(), $this->beacon);
- }
-
- public function volumes(): Volumes
- {
- return $this->inner->volumes();
- }
-
- public function reboot(): Either
- {
- return $this->inner->reboot();
- }
-
- public function shutdown(): Either
- {
- return $this->inner->shutdown();
- }
-}
diff --git a/src/OperatingSystem/Control/Process/Debug.php b/src/OperatingSystem/Control/Process/Debug.php
deleted file mode 100644
index 1066efd..0000000
--- a/src/OperatingSystem/Control/Process/Debug.php
+++ /dev/null
@@ -1,128 +0,0 @@
-command = $command;
- $this->inner = $inner;
- $this->record = $record;
- }
-
- public static function of(
- Command $command,
- Process $process,
- Record $record,
- ): self {
- return new self($command, $process, $record);
- }
-
- public function pid(): Maybe
- {
- return $this->inner->pid();
- }
-
- public function output(): Output
- {
- return $this->inner->output();
- }
-
- public function wait(): Either
- {
- $command = $this->command->toString();
- $command = $this->command->workingDirectory()->match(
- static fn($path) => $path->toString().': '.$command,
- static fn() => $command,
- );
-
- // Either
- return $this
- ->inner
- ->wait()
- ->map(function($success) use ($command) {
- $content = Sequence::lazyStartingWith(Str::of('[0] '.$command));
-
- if (!$this->command->outputToBeStreamed()) {
- $content = $content
- ->add(Str::of("\n"))
- ->append(
- $success
- ->output()
- ->chunks()
- ->map(static fn($pair) => $pair[0]),
- );
- }
-
- ($this->record)(
- static fn($mutation) => $mutation
- ->sections()
- ->processes()
- ->record(Content::ofChunks($content)),
- );
-
- return $success;
- })
- ->leftMap(function($error) use ($command) {
- $status = match (true) {
- $error instanceof Failed => $error->exitCode()->toString(),
- $error instanceof TimedOut => 'timed-out',
- $error instanceof Signaled => 'signaled',
- };
- $content = Sequence::lazyStartingWith(Str::of(\sprintf(
- '[%s] %s',
- $status,
- $command,
- )));
-
- if ($error instanceof Failed && !$this->command->outputToBeStreamed()) {
- $content = $content
- ->add(Str::of("\n"))
- ->append(
- $error
- ->output()
- ->chunks()
- ->map(static fn($pair) => $pair[0]),
- );
- }
-
- ($this->record)(
- static fn($mutation) => $mutation
- ->sections()
- ->processes()
- ->record(Content::ofChunks($content)),
- );
-
- return $error;
- });
- }
-}
diff --git a/src/OperatingSystem/Control/Processes.php b/src/OperatingSystem/Control/Processes.php
deleted file mode 100644
index 899290a..0000000
--- a/src/OperatingSystem/Control/Processes.php
+++ /dev/null
@@ -1,55 +0,0 @@
-inner = $inner;
- $this->beacon = $beacon;
- }
-
- public static function of(ProcessesInterface $inner, Beacon $beacon): self
- {
- return new self($inner, $beacon);
- }
-
- public function execute(Command $command): Process
- {
- if ($command->environment()->contains('X_INNMIND_DEBUG')) {
- return $this->inner->execute($command);
- }
-
- return Debug::of(
- $command,
- $this->inner->execute($command),
- $this->beacon->record(),
- );
- }
-
- public function kill(Pid $pid, Signal $signal): Either
- {
- return $this->inner->kill($pid, $signal);
- }
-}
diff --git a/src/OperatingSystem/Debug.php b/src/OperatingSystem/Debug.php
new file mode 100644
index 0000000..7391e01
--- /dev/null
+++ b/src/OperatingSystem/Debug.php
@@ -0,0 +1,216 @@
+mapServerControl(fn($server) => Control\Server::via(function($command) use ($server) {
+ // todo find a way to record the exit and output
+ $record = $this->beacon->record();
+
+ if ($command instanceof Command\OverSsh) {
+ $port = $command->port();
+ $port = match ($port) {
+ null => '',
+ default => $port->format(),
+ };
+ $ssh = \sprintf(
+ 'ssh: %s@%s%s',
+ $command->user()->toString(),
+ $command->host()->toString(),
+ $port,
+ );
+ $normalized = $command->normalize();
+ $inner = $command->command();
+ $name = $inner->toString();
+
+ if ($inner instanceof Command) {
+ $name = $inner->workingDirectory()->match(
+ static fn($path) => $path->toString().': '.$name,
+ static fn() => $name,
+ );
+ }
+
+ return $record(
+ static fn($mutation) => $mutation
+ ->sections()
+ ->remote()
+ ->processes()
+ ->record(Content::ofChunks(
+ Sequence::of($ssh, $name)
+ ->map(Str::of(...))
+ ->map(static fn($chunk) => $chunk->append("\n")),
+ )),
+ )->flatMap(static fn() => $server->processes()->execute($normalized));
+ }
+
+ // to not record this package usage of the OS
+ if ($command->environment()->contains('X_INNMIND_DEBUG')) {
+ return $server
+ ->processes()
+ ->execute($command);
+ }
+
+ $name = $command->toString();
+ $name = $command->workingDirectory()->match(
+ static fn($path) => $path->toString().': '.$name,
+ static fn() => $name,
+ );
+
+ return $record(
+ static fn($mutation) => $mutation
+ ->sections()
+ ->processes()
+ ->record(Content::ofString($name)),
+ )->flatMap(static fn() => $server->processes()->execute($command));
+ }))
+ ->mapHttpTransport(fn($transport) => Transport::via(function($request) use ($transport) {
+ $record = $this->beacon->record();
+ $toString = Response\Stringable::new();
+
+ return $record(
+ static fn($mutation) => $mutation
+ ->sections()
+ ->remote()
+ ->http()
+ ->sent(Request\Stringable::new()($request)),
+ )
+ ->either()
+ ->leftMap(static fn($e) => new Failure(
+ $request,
+ $e->getMessage(),
+ ))
+ ->flatMap(
+ static fn() => $transport($request)
+ ->flatMap(
+ static fn($success) => $record(
+ static fn($mutation) => $mutation
+ ->sections()
+ ->remote()
+ ->http()
+ ->got($toString($success->response())),
+ )
+ ->either()
+ ->leftMap(static fn($e) => new Failure(
+ $request,
+ $e->getMessage(),
+ ))
+ ->map(static fn() => $success),
+ )
+ ->otherwise(static function($error) use ($request, $record, $toString) {
+ $got = match (true) {
+ $error instanceof Information => $toString($error->response()),
+ $error instanceof Redirection => $toString($error->response()),
+ $error instanceof ClientError => $toString($error->response()),
+ $error instanceof ServerError => $toString($error->response()),
+ $error instanceof MalformedResponse => Content::ofString('malformed response'),
+ $error instanceof ConnectionFailed => Content::ofString($error->reason()),
+ $error instanceof Failure => Content::ofString($error->reason()),
+ };
+
+ return $record(
+ static fn($mutation) => $mutation
+ ->sections()
+ ->remote()
+ ->http()
+ ->got($got),
+ )
+ ->either()
+ ->leftMap(static fn($e) => new Failure(
+ $request,
+ $e->getMessage(),
+ ))
+ ->flatMap(static fn() => Either::left($error));
+ }),
+ );
+ }))
+ ->mapSQLConnection(fn($connection) => Connection::intercept(
+ $connection,
+ function($run, $driver, $query) {
+ $normalized = $query;
+
+ if ($normalized instanceof Query\Builder) {
+ $normalized = $normalized->normalize($driver);
+ }
+
+ $parameters = $normalized
+ ->parameters()
+ ->map(static fn($parameter) => \sprintf(
+ "%s: %s\n",
+ $parameter->name()->match(
+ static fn($name) => $name,
+ static fn() => '?',
+ ),
+ (string) $parameter->value(),
+ ))
+ ->map(Str::of(...));
+ // the sql connection throws when something fails
+ $_ = $this
+ ->beacon
+ ->record()(
+ static fn($mutation) => $mutation
+ ->sections()
+ ->remote()
+ ->sql()
+ ->record(Content::ofChunks(
+ Sequence::of($normalized->sql())
+ ->add("\n")
+ ->map(Str::of(...))
+ ->append($parameters),
+ )),
+ )
+ ->unwrap();
+
+ return $run($query);
+ },
+ ));
+ }
+
+ public static function of(Beacon $beacon): self
+ {
+ return new self($beacon);
+ }
+}
diff --git a/src/OperatingSystem/Remote.php b/src/OperatingSystem/Remote.php
deleted file mode 100644
index 3951d23..0000000
--- a/src/OperatingSystem/Remote.php
+++ /dev/null
@@ -1,64 +0,0 @@
-inner = $inner;
- $this->beacon = $beacon;
- }
-
- public static function of(RemoteInterface $inner, Beacon $beacon): self
- {
- return new self($inner, $beacon);
- }
-
- public function ssh(Url $server): Server
- {
- return Ssh::of($this->inner->ssh($server), $this->beacon, $server);
- }
-
- public function socket(Transport $transport, Authority $authority): Maybe
- {
- return $this->inner->socket($transport, $authority);
- }
-
- public function http(): HttpTransport
- {
- return Http::of($this->inner->http(), $this->beacon);
- }
-
- public function sql(Url $server): Connection
- {
- return Sql::of($this->inner->sql($server), $this->beacon);
- }
-}
diff --git a/src/OperatingSystem/Remote/Http.php b/src/OperatingSystem/Remote/Http.php
deleted file mode 100644
index f21912c..0000000
--- a/src/OperatingSystem/Remote/Http.php
+++ /dev/null
@@ -1,88 +0,0 @@
-inner = $inner;
- $this->beacon = $beacon;
- }
-
- public function __invoke(Request $request): Either
- {
- $record = $this->beacon->record();
- $record(
- static fn($mutation) => $mutation
- ->sections()
- ->remote()
- ->http()
- ->sent(Request\Stringable::new()($request)),
- );
- $toString = Response\Stringable::new();
-
- return ($this->inner)($request)
- ->map(static function($success) use ($record, $toString) {
- $record(
- static fn($mutation) => $mutation
- ->sections()
- ->remote()
- ->http()
- ->got($toString($success->response())),
- );
-
- return $success;
- })
- ->leftMap(static function($error) use ($record, $toString) {
- $got = match (true) {
- $error instanceof Information => $toString($error->response()),
- $error instanceof Redirection => $toString($error->response()),
- $error instanceof ClientError => $toString($error->response()),
- $error instanceof ServerError => $toString($error->response()),
- $error instanceof MalformedResponse => Content::ofString('malformed response'),
- $error instanceof ConnectionFailed => Content::ofString($error->reason()),
- $error instanceof Failure => Content::ofString($error->reason()),
- };
- $record(
- static fn($mutation) => $mutation
- ->sections()
- ->remote()
- ->http()
- ->got($got),
- );
-
- return $error;
- });
- }
-
- public static function of(Transport $inner, Beacon $beacon): self
- {
- return new self($inner, $beacon);
- }
-}
diff --git a/src/OperatingSystem/Remote/Sql.php b/src/OperatingSystem/Remote/Sql.php
deleted file mode 100644
index f89d61d..0000000
--- a/src/OperatingSystem/Remote/Sql.php
+++ /dev/null
@@ -1,64 +0,0 @@
-inner = $inner;
- $this->beacon = $beacon;
- }
-
- public function __invoke(Query $query): Sequence
- {
- $parameters = $query
- ->parameters()
- ->map(static fn($parameter) => \sprintf(
- "%s: %s\n",
- $parameter->name()->match(
- static fn($name) => $name,
- static fn() => '?',
- ),
- (string) $parameter->value(),
- ))
- ->map(Str::of(...));
- $this->beacon->record()(
- static fn($mutation) => $mutation
- ->sections()
- ->remote()
- ->sql()
- ->record(Content::ofChunks(
- Sequence::of($query->sql())
- ->add("\n")
- ->map(Str::of(...))
- ->append($parameters),
- )),
- );
-
- return ($this->inner)($query);
- }
-
- public static function of(Connection $inner, Beacon $beacon): self
- {
- return new self($inner, $beacon);
- }
-}
diff --git a/src/OperatingSystem/Remote/Ssh.php b/src/OperatingSystem/Remote/Ssh.php
deleted file mode 100644
index c1d2392..0000000
--- a/src/OperatingSystem/Remote/Ssh.php
+++ /dev/null
@@ -1,58 +0,0 @@
-inner = $inner;
- $this->beacon = $beacon;
- $this->server = $server;
- }
-
- public static function of(Server $inner, Beacon $beacon, Url $server): self
- {
- return new self($inner, $beacon, $server);
- }
-
- public function processes(): Processes
- {
- return Debug::of($this->inner->processes(), $this->beacon, $this->server);
- }
-
- public function volumes(): Volumes
- {
- return $this->inner->volumes();
- }
-
- public function reboot(): Either
- {
- return $this->inner->reboot();
- }
-
- public function shutdown(): Either
- {
- return $this->inner->shutdown();
- }
-}
diff --git a/src/OperatingSystem/Remote/Ssh/Process/Debug.php b/src/OperatingSystem/Remote/Ssh/Process/Debug.php
deleted file mode 100644
index 82f05d9..0000000
--- a/src/OperatingSystem/Remote/Ssh/Process/Debug.php
+++ /dev/null
@@ -1,143 +0,0 @@
-server = $server;
- $this->command = $command;
- $this->inner = $inner;
- $this->record = $record;
- }
-
- public static function of(
- Url $server,
- Command $command,
- Process $process,
- Record $record,
- ): self {
- return new self($server, $command, $process, $record);
- }
-
- public function pid(): Maybe
- {
- return $this->inner->pid();
- }
-
- public function output(): Output
- {
- return $this->inner->output();
- }
-
- public function wait(): Either
- {
- $ssh = \sprintf(
- "ssh: %s\n",
- $this->server->authority()->toString(),
- );
- $command = $this->command->toString();
- $command = $this->command->workingDirectory()->match(
- static fn($path) => $path->toString().': '.$command,
- static fn() => $command,
- );
-
- // Either
- return $this
- ->inner
- ->wait()
- ->map(function($success) use ($ssh, $command) {
- $content = Sequence::lazyStartingWith($ssh, '[0] '.$command)
- ->map(Str::of(...));
-
- if (!$this->command->outputToBeStreamed()) {
- $content = $content
- ->add(Str::of("\n"))
- ->append(
- $success
- ->output()
- ->chunks()
- ->map(static fn($pair) => $pair[0]),
- );
- }
-
- ($this->record)(
- static fn($mutation) => $mutation
- ->sections()
- ->remote()
- ->processes()
- ->record(Content::ofChunks($content)),
- );
-
- return $success;
- })
- ->leftMap(function($error) use ($ssh, $command) {
- $status = match (true) {
- $error instanceof Failed => $error->exitCode()->toString(),
- $error instanceof TimedOut => 'timed-out',
- $error instanceof Signaled => 'signaled',
- };
- $content = Sequence::lazyStartingWith(
- $ssh,
- \sprintf(
- '[%s] %s',
- $status,
- $command,
- ),
- )->map(Str::of(...));
-
- if ($error instanceof Failed && !$this->command->outputToBeStreamed()) {
- $content = $content
- ->add(Str::of("\n"))
- ->append(
- $error
- ->output()
- ->chunks()
- ->map(static fn($pair) => $pair[0]),
- );
- }
-
- ($this->record)(
- static fn($mutation) => $mutation
- ->sections()
- ->remote()
- ->processes()
- ->record(Content::ofChunks($content)),
- );
-
- return $error;
- });
- }
-}
diff --git a/src/OperatingSystem/Remote/Ssh/Processes.php b/src/OperatingSystem/Remote/Ssh/Processes.php
deleted file mode 100644
index 7535a19..0000000
--- a/src/OperatingSystem/Remote/Ssh/Processes.php
+++ /dev/null
@@ -1,61 +0,0 @@
-inner = $inner;
- $this->beacon = $beacon;
- $this->server = $server;
- }
-
- public static function of(
- ProcessesInterface $inner,
- Beacon $beacon,
- Url $server,
- ): self {
- return new self($inner, $beacon, $server);
- }
-
- public function execute(Command $command): Process
- {
- return Debug::of(
- $this->server,
- $command,
- $this->inner->execute($command),
- $this->beacon->record(),
- );
- }
-
- public function kill(Pid $pid, Signal $signal): Either
- {
- return $this->inner->kill($pid, $signal);
- }
-}
diff --git a/src/Record.php b/src/Record.php
index 276e558..5c4ca39 100644
--- a/src/Record.php
+++ b/src/Record.php
@@ -4,6 +4,10 @@
namespace Innmind\Debug;
use Innmind\Profiler\Profiler\Mutation;
+use Innmind\Immutable\{
+ Attempt,
+ SideEffect,
+};
/**
* @internal
@@ -11,7 +15,10 @@
interface Record
{
/**
- * @param callable(Mutation): void $mutation
+ * @param callable(Mutation): Attempt $mutation
+ *
+ * @return Attempt
*/
- public function __invoke(callable $mutation): void;
+ #[\NoDiscard]
+ public function __invoke(callable $mutation): Attempt;
}
diff --git a/src/Record/Nothing.php b/src/Record/Nothing.php
index ddadbae..7fff23f 100644
--- a/src/Record/Nothing.php
+++ b/src/Record/Nothing.php
@@ -4,14 +4,19 @@
namespace Innmind\Debug\Record;
use Innmind\Debug\Record;
+use Innmind\Immutable\{
+ Attempt,
+ SideEffect,
+};
/**
* @internal
*/
final class Nothing implements Record
{
- public function __invoke(callable $mutation): void
+ #[\Override]
+ public function __invoke(callable $mutation): Attempt
{
- // nothing to do
+ return Attempt::result(SideEffect::identity);
}
}
diff --git a/src/Record/Profile.php b/src/Record/Profile.php
index c9918f9..c63fb36 100644
--- a/src/Record/Profile.php
+++ b/src/Record/Profile.php
@@ -8,6 +8,7 @@
Profiler,
Profile\Id,
};
+use Innmind\Immutable\Attempt;
/**
* @internal
@@ -23,9 +24,10 @@ private function __construct(Profiler $profiler, Id $profile)
$this->profile = $profile;
}
- public function __invoke(callable $mutation): void
+ #[\Override]
+ public function __invoke(callable $mutation): Attempt
{
- $this->profiler->mutate($this->profile, $mutation);
+ return $this->profiler->mutate($this->profile, $mutation);
}
public static function of(Profiler $profiler, Id $profile): self
diff --git a/src/Recorder/All.php b/src/Recorder/All.php
index 94dae33..07dc3a6 100644
--- a/src/Recorder/All.php
+++ b/src/Recorder/All.php
@@ -32,6 +32,7 @@ public static function of(Recorder ...$recorders): self
return new self($recorders);
}
+ #[\Override]
public function push(Record $record): void
{
foreach ($this->recorders as $recorder) {
diff --git a/src/Recorder/AppGraph.php b/src/Recorder/AppGraph.php
deleted file mode 100644
index 79fcc0c..0000000
--- a/src/Recorder/AppGraph.php
+++ /dev/null
@@ -1,120 +0,0 @@
- */
- private Map $env;
- private Record $record;
- private Render $render;
- private Lookup $lookup;
-
- /**
- * @param Map $env
- */
- public function __construct(
- OperatingSystem $os,
- Map $env,
- IDE $ide,
- ) {
- $this->os = $os;
- $this->env = ($env)('X_INNMIND_DEBUG', 'true');
- $this->record = new Record\Nothing;
- $this->render = Render::of(match ($ide) {
- IDE::sublimeText => new SublimeHandler,
- default => null,
- })->fromTopToBottom();
- $this->lookup = Lookup::of();
- }
-
- public function __invoke(object $root): void
- {
- $graph = ($this->lookup)($root);
- $graph = $this->clean($graph);
- $svg = $this
- ->os
- ->control()
- ->processes()
- ->execute(
- Command::foreground('dot')
- ->withShortOption('Tsvg')
- ->withEnvironments($this->env)
- ->withInput(($this->render)($graph)),
- )
- ->wait()
- ->match(
- static fn($success) => Content::ofChunks(
- $success
- ->output()
- ->chunks()
- ->map(static fn($pair) => $pair[0]),
- ),
- static fn() => Content::ofString('Unable to render the app graph'),
- );
- ($this->record)(
- static fn($mutation) => $mutation
- ->sections()
- ->appGraph()
- ->record($svg),
- );
- }
-
- public function push(Record $record): void
- {
- $this->record = $record;
- }
-
- private function clean(Graph $graph): Graph
- {
- if ($graph->root()->class()->toString() !== Container::class) {
- return $graph;
- }
-
- $children = $graph->nodes()->remove($graph->root());
- $unwanted = $graph
- ->root()
- ->relations()
- ->filter(static fn($relation) => $relation->property()->toString() !== 'services')
- ->map(static fn($relation) => $relation->reference());
- $toRemove = $children
- ->filter(static fn($node) => $unwanted->any(
- static fn($reference) => $reference->equals($node->reference()),
- ))
- ->flatMap(static fn($node) => $node->relations())
- ->map(static fn($relation) => $relation->reference())
- ->merge($unwanted);
- $children = $children->filter(static fn($node) => !$toRemove->any(
- static fn($reference) => $reference->equals($node->reference()),
- ));
-
- return Graph::of(
- $graph
- ->root()
- ->filterRelation(static fn($relation) => $relation->property()->toString() === 'services'),
- $children,
- );
- }
-}
diff --git a/src/Recorder/Beacon.php b/src/Recorder/Beacon.php
index 72bb627..57ddc90 100644
--- a/src/Recorder/Beacon.php
+++ b/src/Recorder/Beacon.php
@@ -25,6 +25,7 @@ public function record(): Record
return $this->record;
}
+ #[\Override]
public function push(Record $record): void
{
$this->record = $record;
diff --git a/src/Recorder/Exception.php b/src/Recorder/Exception.php
index cb866ff..98de618 100644
--- a/src/Recorder/Exception.php
+++ b/src/Recorder/Exception.php
@@ -17,7 +17,11 @@
Link\SublimeHandler,
FormatPath,
};
-use Innmind\Immutable\Map;
+use Innmind\Immutable\{
+ Map,
+ Attempt,
+ SideEffect,
+};
/**
* @internal
@@ -51,7 +55,10 @@ public function __construct(
);
}
- public function __invoke(\Throwable $e): void
+ /**
+ * @return Attempt
+ */
+ public function __invoke(\Throwable $e): Attempt
{
$graph = $this
->os
@@ -63,17 +70,18 @@ public function __invoke(\Throwable $e): void
->withEnvironments($this->env)
->withInput(($this->render)(StackTrace::of($e))),
)
- ->wait()
+ ->maybe()
+ ->flatMap(static fn($process) => $process->wait()->maybe())
->match(
static fn($success) => Content::ofChunks(
$success
->output()
- ->chunks()
- ->map(static fn($pair) => $pair[0]),
+ ->map(static fn($chunk) => $chunk->data()),
),
static fn() => Content::ofString('Unable to render the exception graph'),
);
- ($this->record)(
+
+ return ($this->record)(
static fn($mutation) => $mutation
->sections()
->exception()
@@ -81,6 +89,7 @@ public function __invoke(\Throwable $e): void
);
}
+ #[\Override]
public function push(Record $record): void
{
$this->record = $record;
diff --git a/tests/FunctionalTest.php b/tests/FunctionalTest.php
index fb7b835..dde4011 100644
--- a/tests/FunctionalTest.php
+++ b/tests/FunctionalTest.php
@@ -11,11 +11,12 @@
};
use Innmind\CLI\{
Command as CliCommand,
+ Command\Usage,
Console,
- Environment\InMemory,
+ Environment as CliEnvironment,
};
use Innmind\OperatingSystem\Factory;
-use Innmind\Filesystem\Adapter\Filesystem;
+use Innmind\Filesystem\Adapter;
use Innmind\Server\Control\Server\Command;
use Innmind\Http\{
ServerRequest,
@@ -25,107 +26,133 @@
Response\StatusCode,
ProtocolVersion,
};
-use Innmind\Router\Route;
+use Innmind\DI\Service;
use Innmind\Url\{
Url,
Path,
};
use Innmind\Html\{
- Reader\Reader,
+ Reader,
Visitor\Elements,
Visitor\Element,
};
-use Innmind\Immutable\Str;
+use Innmind\Immutable\{
+ Str,
+ Attempt,
+};
use Innmind\BlackBox\PHPUnit\Framework\TestCase;
+enum _Services implements Service
+{
+ case os;
+}
+
class FunctionalTest extends TestCase
{
private Path $storage;
public function setUp(): void
{
- $this->storage = Path::of(\sys_get_temp_dir().'/innmind_debug/');
+ $this->storage = Path::of(\dirname(__DIR__).'/var/');
}
public function tearDown(): void
{
- $storage = Filesystem::mount($this->storage);
- $storage->root()->all()->foreach(
- static fn($file) => $storage->remove($file->name()),
- );
+ $storage = Adapter::mount($this->storage)->unwrap();
+ $_ = $storage
+ ->root()
+ ->all()
+ ->exclude(static fn($file) => $file->name()->toString() === '.gitkeep')
+ ->foreach(
+ static fn($file) => $storage->remove($file->name()),
+ );
}
public function testRecordHttpContext()
{
+ $debug = Debug::inApp()->removePathFromStackTrace(
+ Url::of(__DIR__),
+ );
$app = Application::http(Factory::build(), Environment::test([['PATH', \getenv('PATH')]]))
->map(Profiler::inApp($this->storage))
- ->map(Debug::inApp())
- ->appendRoutes(static fn($routes, $_, $os) => $routes->add(
- Route::literal('GET /')->handle(static function() use ($os) {
- $os
- ->control()
- ->processes()
- ->execute(Command::foreground('cat hello'))
- ->wait()
- ->match(
- static fn() => null,
- static fn() => null,
- );
- $os
- ->control()
- ->processes()
- ->execute(Command::foreground('echo hello'))
- ->wait()
- ->match(
- static fn() => null,
- static fn() => null,
- );
- $os
- ->remote()
- ->http()(Request::of(
- Url::of('https://github.com'),
- Method::get,
- ProtocolVersion::v11,
- ))
- ->match(
- static fn() => null,
- static fn() => null,
- );
- $os
- ->remote()
- ->http()(Request::of(
- Url::of('http://example.com/unknown'),
- Method::get,
- ProtocolVersion::v11,
- ))
- ->match(
- static fn() => null,
- static fn() => null,
- );
-
- throw new \Exception('foo');
- }),
- ));
+ ->map($debug)
+ ->service(_Services::os, static fn($_, $os) => $os)
+ ->route(
+ static fn($pipe, $get) => $pipe
+ ->get()
+ ->endpoint('/')
+ ->handle(
+ static function() use ($get) {
+ $os = $get(_Services::os);
+ $_ = $os
+ ->control()
+ ->processes()
+ ->execute(Command::foreground('cat hello'))
+ ->flatMap(static fn($process) => $process->wait()->attempt(
+ static fn() => new \Exception,
+ ))
+ ->match(
+ static fn() => null,
+ static fn() => null,
+ );
+ $_ = $os
+ ->control()
+ ->processes()
+ ->execute(Command::foreground('echo hello'))
+ ->flatMap(static fn($process) => $process->wait()->attempt(
+ static fn() => new \Exception,
+ ))
+ ->match(
+ static fn() => null,
+ static fn() => null,
+ );
+ $_ = $os
+ ->remote()
+ ->http()(Request::of(
+ Url::of('https://github.com'),
+ Method::get,
+ ProtocolVersion::v11,
+ ))
+ ->match(
+ static fn() => null,
+ static fn() => null,
+ );
+ $_ = $os
+ ->remote()
+ ->http()(Request::of(
+ Url::of('http://example.com/unknown'),
+ Method::get,
+ ProtocolVersion::v11,
+ ))
+ ->match(
+ static fn() => null,
+ static fn() => null,
+ );
+
+ return Attempt::error(new \Exception('foo'));
+ },
+ ),
+ );
- try {
- $app->run(ServerRequest::of(
- Url::of('/'),
- Method::get,
- ProtocolVersion::v11,
- ));
- $this->fail('it should throw');
- } catch (\Exception $e) {
- $this->assertSame('foo', $e->getMessage());
- }
+ $this->assert()->throws(
+ static fn() => $app
+ ->run(ServerRequest::of(
+ Url::of('/'),
+ Method::get,
+ ProtocolVersion::v11,
+ ))
+ ->unwrap(),
+ );
$response = $app->run(ServerRequest::of(
Url::of('/_profiler/'),
Method::get,
ProtocolVersion::v11,
- ));
+ ))->unwrap();
$this->assertSame(StatusCode::ok, $response->statusCode());
- $profile = Reader::default()($response->body())
+ $profile = Reader::new()($response->body())
+ ->maybe()
->flatMap(Element::body())
->flatMap(Element::of('main'))
->flatMap(Element::of('li'))
@@ -140,82 +167,93 @@ public function testRecordHttpContext()
$profile,
Method::get,
ProtocolVersion::v11,
- ));
+ ))->unwrap();
$this->assertSame(StatusCode::ok, $response->statusCode());
- $sections = Reader::default()($response->body())
+ $sections = Reader::new()($response->body())
+ ->maybe()
->flatMap(Element::body())
->flatMap(Element::of('header'))
- ->map(static fn($header) => Elements::of('li')($header))
- ->map(
- static fn($lis) => $lis
- ->flatMap(Elements::of('a'))
- ->map(static fn($a) => $a->content())
- ->map(Str::of(...))
- ->map(static fn($text) => $text->trim()->toString()),
- )
- ->match(
- static fn($sections) => $sections->toList(),
- static fn() => [],
- );
+ ->toSequence()
+ ->flatMap(Elements::of('li'))
+ ->flatMap(Elements::of('a'))
+ ->flatMap(static fn($a) => $a->normalize()->children())
+ ->map(static fn($a) => $a->content())
+ ->map(Str::of(...))
+ ->map(static fn($text) => $text->trim()->toString())
+ ->toList();
$this->assertSame(
- ['Exception', 'Http', 'Environment', 'Processes', 'Remote/Http', 'App graph'],
+ ['Exception', 'Http', 'Environment', 'Processes', 'Remote/Http'],
$sections,
);
}
public function testRecordHttpContextWithoutOSLayer()
{
+ $debug = Debug::inApp()->removePathFromStackTrace(
+ Url::of(__DIR__),
+ );
$app = Application::http(Factory::build(), Environment::test([['PATH', \getenv('PATH')]]))
->map(Profiler::inApp($this->storage))
- ->map(Debug::inApp()->app())
- ->appendRoutes(static fn($routes, $_, $os) => $routes->add(
- Route::literal('GET /')->handle(static function() use ($os) {
- $os
- ->control()
- ->processes()
- ->execute(Command::foreground('cat hello'))
- ->wait()
- ->match(
- static fn() => null,
- static fn() => null,
- );
- $os
- ->remote()
- ->http()(Request::of(
- Url::of('https://github.com'),
- Method::get,
- ProtocolVersion::v11,
- ))
- ->match(
- static fn() => null,
- static fn() => null,
- );
-
- throw new \Exception('foo');
- }),
- ));
+ ->map($debug->app())
+ ->service(_Services::os, static fn($_, $os) => $os)
+ ->route(
+ static fn($pipe, $get) => $pipe
+ ->get()
+ ->endpoint('/')
+ ->handle(
+ static function() use ($get) {
+ $os = $get(_Services::os);
+ $_ = $os
+ ->control()
+ ->processes()
+ ->execute(Command::foreground('cat hello'))
+ ->maybe()
+ ->flatMap(static fn($process) => $process->wait()->maybe())
+ ->match(
+ static fn() => null,
+ static fn() => null,
+ );
+ $_ = $os
+ ->remote()
+ ->http()(Request::of(
+ Url::of('https://github.com'),
+ Method::get,
+ ProtocolVersion::v11,
+ ))
+ ->match(
+ static fn() => null,
+ static fn() => null,
+ );
+
+ return Attempt::error(new \Exception('foo'));
+ },
+ ),
+ );
- try {
- $app->run(ServerRequest::of(
+ $error = $app
+ ->run(ServerRequest::of(
Url::of('/'),
Method::get,
ProtocolVersion::v11,
- ));
- $this->fail('it should throw');
- } catch (\Exception $e) {
- $this->assertSame('foo', $e->getMessage());
- }
+ ))
+ ->match(
+ static fn() => null,
+ static fn($e) => $e,
+ );
+
+ $this->assertSame('foo', $error->getMessage());
$response = $app->run(ServerRequest::of(
Url::of('/_profiler/'),
Method::get,
ProtocolVersion::v11,
- ));
+ ))->unwrap();
$this->assertSame(StatusCode::ok, $response->statusCode());
- $profile = Reader::default()($response->body())
+ $profile = Reader::new()($response->body())
+ ->maybe()
->flatMap(Element::body())
->flatMap(Element::of('main'))
->flatMap(Element::of('li'))
@@ -230,51 +268,52 @@ public function testRecordHttpContextWithoutOSLayer()
$profile,
Method::get,
ProtocolVersion::v11,
- ));
+ ))->unwrap();
$this->assertSame(StatusCode::ok, $response->statusCode());
- $sections = Reader::default()($response->body())
+ $sections = Reader::new()($response->body())
+ ->maybe()
->flatMap(Element::body())
->flatMap(Element::of('header'))
- ->map(static fn($header) => Elements::of('li')($header))
- ->map(
- static fn($lis) => $lis
- ->flatMap(Elements::of('a'))
- ->map(static fn($a) => $a->content())
- ->map(Str::of(...))
- ->map(static fn($text) => $text->trim()->toString()),
- )
- ->match(
- static fn($sections) => $sections->toList(),
- static fn() => [],
- );
+ ->toSequence()
+ ->flatMap(Elements::of('li'))
+ ->flatMap(Elements::of('a'))
+ ->flatMap(static fn($a) => $a->normalize()->children())
+ ->map(static fn($a) => $a->content())
+ ->map(Str::of(...))
+ ->map(static fn($text) => $text->trim()->toString())
+ ->toList();
$this->assertSame(
- ['Exception', 'Http', 'Environment', 'App graph'],
+ ['Exception', 'Http', 'Environment'],
$sections,
);
}
public function testRecordHttpResponse()
{
+ $debug = Debug::inApp()->removePathFromStackTrace(
+ Url::of(__DIR__),
+ );
$app = Application::http(Factory::build(), Environment::test([['PATH', \getenv('PATH')]]))
- ->map(Debug::inApp()->operatingSystem())
+ ->map($debug->operatingSystem())
->map(Profiler::inApp($this->storage))
- ->map(Debug::inApp()->app())
- ->appendRoutes(static fn($routes, $_, $os) => $routes->add(
- Route::literal('GET /')->handle(static function($request) use ($os) {
- return Response::of(
+ ->map($debug->app())
+ ->route(
+ static fn($pipe, $get) => $pipe
+ ->get()
+ ->endpoint('/')
+ ->handle(static fn($request) => Attempt::result(Response::of(
StatusCode::ok,
$request->protocolVersion(),
- );
- }),
- ));
+ ))),
+ );
$response = $app->run(ServerRequest::of(
Url::of('/'),
Method::get,
ProtocolVersion::v11,
- ));
+ ))->unwrap();
$this->assertSame(StatusCode::ok, $response->statusCode());
@@ -282,10 +321,11 @@ public function testRecordHttpResponse()
Url::of('/_profiler/'),
Method::get,
ProtocolVersion::v11,
- ));
+ ))->unwrap();
$this->assertSame(StatusCode::ok, $response->statusCode());
- $profile = Reader::default()($response->body())
+ $profile = Reader::new()($response->body())
+ ->maybe()
->flatMap(Element::body())
->flatMap(Element::of('main'))
->flatMap(Element::of('li'))
@@ -300,54 +340,56 @@ public function testRecordHttpResponse()
$profile,
Method::get,
ProtocolVersion::v11,
- ));
+ ))->unwrap();
$this->assertSame(StatusCode::ok, $response->statusCode());
- $sections = Reader::default()($response->body())
+ $sections = Reader::new()($response->body())
+ ->maybe()
->flatMap(Element::body())
->flatMap(Element::of('header'))
- ->map(static fn($header) => Elements::of('li')($header))
- ->map(
- static fn($lis) => $lis
- ->flatMap(Elements::of('a'))
- ->map(static fn($a) => $a->content())
- ->map(Str::of(...))
- ->map(static fn($text) => $text->trim()->toString()),
- )
- ->match(
- static fn($sections) => $sections->toList(),
- static fn() => [],
- );
+ ->toSequence()
+ ->flatMap(Elements::of('li'))
+ ->flatMap(Elements::of('a'))
+ ->flatMap(static fn($a) => $a->normalize()->children())
+ ->map(static fn($a) => $a->content())
+ ->map(Str::of(...))
+ ->map(static fn($text) => $text->trim()->toString())
+ ->toList();
$this->assertSame(
- ['Http', 'Environment', 'App graph'],
+ ['Http', 'Environment'],
$sections,
);
}
public function testRecordCliContext()
{
+ $debug = Debug::inApp()->removePathFromStackTrace(
+ Url::of(__DIR__),
+ );
$app = Application::cli(Factory::build(), Environment::test([['PATH', \getenv('PATH')]]))
->map(Profiler::inApp($this->storage))
- ->map(Debug::inApp())
- ->command(static fn($_, $os) => new class($os) implements CliCommand {
+ ->map($debug)
+ ->service(_Services::os, static fn($_, $os) => $os)
+ ->command(static fn($get) => new class($get(_Services::os)) implements CliCommand {
public function __construct(private $os)
{
}
- public function __invoke(Console $console): Console
+ public function __invoke(Console $console): Attempt
{
- $this
+ $_ = $this
->os
->control()
->processes()
->execute(Command::foreground('cat hello'))
- ->wait()
+ ->maybe()
+ ->flatMap(static fn($process) => $process->wait()->maybe())
->match(
static fn() => null,
static fn() => null,
);
- $this
+ $_ = $this
->os
->remote()
->http()(Request::of(
@@ -360,41 +402,44 @@ public function __invoke(Console $console): Console
static fn() => null,
);
- throw new \Exception('foo');
+ return Attempt::error(new \Exception('foo'));
}
- public function usage(): string
+ public function usage(): Usage
{
- return 'hello-world';
+ return Usage::parse('hello-world');
}
});
- try {
- $app->run(InMemory::of(
+ $error = $app
+ ->run(CliEnvironment::inMemory(
[],
true,
['bin', 'hello-world'],
[['PATH', \getenv('PATH')]],
'/',
- ));
- $this->fail('it should throw');
- } catch (\Exception $e) {
- $this->assertSame('foo', $e->getMessage());
- }
+ ))
+ ->match(
+ static fn() => null,
+ static fn($e) => $e,
+ );
+
+ $this->assertSame('foo', $error->getMessage());
$app = Application::http(Factory::build(), Environment::test([['PATH', \getenv('PATH')]]))
- ->map(Debug::inApp()->operatingSystem())
+ ->map($debug->operatingSystem())
->map(Profiler::inApp($this->storage))
- ->map(Debug::inApp()->app());
+ ->map($debug->app());
$response = $app->run(ServerRequest::of(
Url::of('/_profiler/'),
Method::get,
ProtocolVersion::v11,
- ));
+ ))->unwrap();
$this->assertSame(StatusCode::ok, $response->statusCode());
- $profile = Reader::default()($response->body())
+ $profile = Reader::new()($response->body())
+ ->maybe()
->flatMap(Element::body())
->flatMap(Element::of('main'))
->flatMap(Element::of('li'))
@@ -409,54 +454,56 @@ public function usage(): string
$profile,
Method::get,
ProtocolVersion::v11,
- ));
+ ))->unwrap();
$this->assertSame(StatusCode::ok, $response->statusCode());
- $sections = Reader::default()($response->body())
+ $sections = Reader::new()($response->body())
+ ->maybe()
->flatMap(Element::body())
->flatMap(Element::of('header'))
- ->map(static fn($header) => Elements::of('li')($header))
- ->map(
- static fn($lis) => $lis
- ->flatMap(Elements::of('a'))
- ->map(static fn($a) => $a->content())
- ->map(Str::of(...))
- ->map(static fn($text) => $text->trim()->toString()),
- )
- ->match(
- static fn($sections) => $sections->toList(),
- static fn() => [],
- );
+ ->toSequence()
+ ->flatMap(Elements::of('li'))
+ ->flatMap(Elements::of('a'))
+ ->flatMap(static fn($a) => $a->normalize()->children())
+ ->map(static fn($a) => $a->content())
+ ->map(Str::of(...))
+ ->map(static fn($text) => $text->trim()->toString())
+ ->toList();
$this->assertSame(
- ['Exception', 'Environment', 'Processes', 'Remote/Http', 'App graph'],
+ ['Exception', 'Environment', 'Processes', 'Remote/Http'],
$sections,
);
}
public function testRecordCliContextWithoutOSLayer()
{
+ $debug = Debug::inApp()->removePathFromStackTrace(
+ Url::of(__DIR__),
+ );
$app = Application::cli(Factory::build(), Environment::test([['PATH', \getenv('PATH')]]))
->map(Profiler::inApp($this->storage))
- ->map(Debug::inApp()->app())
- ->command(static fn($_, $os) => new class($os) implements CliCommand {
+ ->map($debug->app())
+ ->service(_Services::os, static fn($_, $os) => $os)
+ ->command(static fn($get) => new class($get(_Services::os)) implements CliCommand {
public function __construct(private $os)
{
}
- public function __invoke(Console $console): Console
+ public function __invoke(Console $console): Attempt
{
- $this
+ $_ = $this
->os
->control()
->processes()
->execute(Command::foreground('cat hello'))
- ->wait()
+ ->maybe()
+ ->flatMap(static fn($process) => $process->wait()->maybe())
->match(
static fn() => null,
static fn() => null,
);
- $this
+ $_ = $this
->os
->remote()
->http()(Request::of(
@@ -469,41 +516,44 @@ public function __invoke(Console $console): Console
static fn() => null,
);
- throw new \Exception('foo');
+ return Attempt::error(new \Exception('foo'));
}
- public function usage(): string
+ public function usage(): Usage
{
- return 'hello-world';
+ return Usage::parse('hello-world');
}
});
- try {
- $app->run(InMemory::of(
+ $error = $app
+ ->run(CliEnvironment::inMemory(
[],
true,
['bin', 'hello-world'],
[['PATH', \getenv('PATH')]],
'/',
- ));
- $this->fail('it should throw');
- } catch (\Exception $e) {
- $this->assertSame('foo', $e->getMessage());
- }
+ ))
+ ->match(
+ static fn() => null,
+ static fn($e) => $e,
+ );
+
+ $this->assertSame('foo', $error->getMessage());
$app = Application::http(Factory::build(), Environment::test([['PATH', \getenv('PATH')]]))
- ->map(Debug::inApp()->operatingSystem())
+ ->map($debug->operatingSystem())
->map(Profiler::inApp($this->storage))
- ->map(Debug::inApp()->app());
+ ->map($debug->app());
$response = $app->run(ServerRequest::of(
Url::of('/_profiler/'),
Method::get,
ProtocolVersion::v11,
- ));
+ ))->unwrap();
$this->assertSame(StatusCode::ok, $response->statusCode());
- $profile = Reader::default()($response->body())
+ $profile = Reader::new()($response->body())
+ ->maybe()
->flatMap(Element::body())
->flatMap(Element::of('main'))
->flatMap(Element::of('li'))
@@ -518,59 +568,55 @@ public function usage(): string
$profile,
Method::get,
ProtocolVersion::v11,
- ));
+ ))->unwrap();
$this->assertSame(StatusCode::ok, $response->statusCode());
- $sections = Reader::default()($response->body())
+ $sections = Reader::new()($response->body())
+ ->maybe()
->flatMap(Element::body())
->flatMap(Element::of('header'))
- ->map(static fn($header) => Elements::of('li')($header))
- ->map(
- static fn($lis) => $lis
- ->flatMap(Elements::of('a'))
- ->map(static fn($a) => $a->content())
- ->map(Str::of(...))
- ->map(static fn($text) => $text->trim()->toString()),
- )
- ->match(
- static fn($sections) => $sections->toList(),
- static fn() => [],
- );
+ ->toSequence()
+ ->flatMap(Elements::of('li'))
+ ->flatMap(Elements::of('a'))
+ ->flatMap(static fn($a) => $a->normalize()->children())
+ ->map(static fn($a) => $a->content())
+ ->map(Str::of(...))
+ ->map(static fn($text) => $text->trim()->toString())
+ ->toList();
$this->assertSame(
- ['Exception', 'Environment', 'App graph'],
+ ['Exception', 'Environment'],
$sections,
);
}
public function testRecordCliReturnCode()
{
+ $debug = Debug::inApp()->removePathFromStackTrace(
+ Url::of(__DIR__),
+ );
$app = Application::cli(Factory::build(), Environment::test([['PATH', \getenv('PATH')]]))
->map(Profiler::inApp($this->storage))
- ->map(Debug::inApp()->app())
- ->command(static fn($_, $os) => new class($os) implements CliCommand {
- public function __construct(private $os)
- {
- }
-
- public function __invoke(Console $console): Console
+ ->map($debug->app())
+ ->command(static fn() => new class implements CliCommand {
+ public function __invoke(Console $console): Attempt
{
- return $console->exit(1);
+ return Attempt::result($console->exit(1));
}
- public function usage(): string
+ public function usage(): Usage
{
- return 'hello-world';
+ return Usage::parse('hello-world');
}
});
- $environment = $app->run(InMemory::of(
+ $environment = $app->run(CliEnvironment::inMemory(
[],
true,
['bin', 'hello-world'],
[['PATH', \getenv('PATH')]],
'/',
- ));
+ ))->unwrap();
$this->assertSame(1, $environment->exitCode()->match(
static fn($exit) => $exit->toInt(),
@@ -578,18 +624,19 @@ public function usage(): string
));
$app = Application::http(Factory::build(), Environment::test([['PATH', \getenv('PATH')]]))
- ->map(Debug::inApp()->operatingSystem())
+ ->map($debug->operatingSystem())
->map(Profiler::inApp($this->storage))
- ->map(Debug::inApp()->app());
+ ->map($debug->app());
$response = $app->run(ServerRequest::of(
Url::of('/_profiler/'),
Method::get,
ProtocolVersion::v11,
- ));
+ ))->unwrap();
$this->assertSame(StatusCode::ok, $response->statusCode());
- $profile = Reader::default()($response->body())
+ $profile = Reader::new()($response->body())
+ ->maybe()
->flatMap(Element::body())
->flatMap(Element::of('main'))
->flatMap(Element::of('li'))
@@ -604,74 +651,66 @@ public function usage(): string
$profile,
Method::get,
ProtocolVersion::v11,
- ));
+ ))->unwrap();
$this->assertSame(StatusCode::ok, $response->statusCode());
- $sections = Reader::default()($response->body())
+ $sections = Reader::new()($response->body())
+ ->maybe()
->flatMap(Element::body())
->flatMap(Element::of('header'))
- ->map(static fn($header) => Elements::of('li')($header))
- ->map(
- static fn($lis) => $lis
- ->flatMap(Elements::of('a'))
- ->map(static fn($a) => $a->content())
- ->map(Str::of(...))
- ->map(static fn($text) => $text->trim()->toString()),
- )
- ->match(
- static fn($sections) => $sections->toList(),
- static fn() => [],
- );
+ ->toSequence()
+ ->flatMap(Elements::of('li'))
+ ->flatMap(Elements::of('a'))
+ ->flatMap(static fn($a) => $a->normalize()->children())
+ ->map(static fn($a) => $a->content())
+ ->map(Str::of(...))
+ ->map(static fn($text) => $text->trim()->toString())
+ ->toList();
$this->assertSame(
- ['Environment', 'App graph'],
+ ['Environment'],
$sections,
);
}
public function testMakeSureCorrectCommandIsRun()
{
+ $debug = Debug::inApp()->removePathFromStackTrace(
+ Url::of(__DIR__),
+ );
$app = Application::cli(Factory::build(), Environment::test([['PATH', \getenv('PATH')]]))
->map(Profiler::inApp($this->storage))
- ->map(Debug::inApp()->app())
- ->command(static fn($_, $os) => new class($os) implements CliCommand {
- public function __construct(private $os)
+ ->map($debug->app())
+ ->command(static fn() => new class implements CliCommand {
+ public function __invoke(Console $console): Attempt
{
+ return Attempt::result($console->exit(1));
}
- public function __invoke(Console $console): Console
+ public function usage(): Usage
{
- return $console->exit(1);
- }
-
- public function usage(): string
- {
- return 'foo';
+ return Usage::parse('foo');
}
})
- ->command(static fn($_, $os) => new class($os) implements CliCommand {
- public function __construct(private $os)
+ ->command(static fn() => new class implements CliCommand {
+ public function __invoke(Console $console): Attempt
{
+ return Attempt::result($console);
}
- public function __invoke(Console $console): Console
+ public function usage(): Usage
{
- return $console;
- }
-
- public function usage(): string
- {
- return 'hello-world';
+ return Usage::parse('hello-world');
}
});
- $environment = $app->run(InMemory::of(
+ $environment = $app->run(CliEnvironment::inMemory(
[],
true,
['bin', 'hello-world'],
[['PATH', \getenv('PATH')]],
'/',
- ));
+ ))->unwrap();
$this->assertNull($environment->exitCode()->match(
static fn($exit) => $exit->toInt(),
diff --git a/var/.gitkeep b/var/.gitkeep
new file mode 100644
index 0000000..e69de29