From 484900f99ed96799401212324b3f40bc0938dc45 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Tue, 2 Dec 2025 15:21:14 +0100 Subject: [PATCH 01/12] add Set --- composer.json | 3 +- psalm.xml | 3 ++ src/.gitkeep | 0 src/Set.php | 127 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 1 deletion(-) delete mode 100644 src/.gitkeep create mode 100644 src/Set.php diff --git a/composer.json b/composer.json index d797ac8..f4f9543 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ "issues": "http://github.com/innmind/mutable/issues" }, "require": { - "php": "~8.4" + "php": "~8.4", + "innmind/immutable": "dev-next" }, "autoload": { "psr-4": { 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/.gitkeep b/src/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/Set.php b/src/Set.php new file mode 100644 index 0000000..7ef8959 --- /dev/null +++ b/src/Set.php @@ -0,0 +1,127 @@ + $set + */ + private function __construct( + private Immutable\Set $set, + ) { + } + + /** + * Add an element to the set + * + * @param T $element + */ + public function __invoke($element): void + { + $this->set = ($this->set)($element); + } + + /** + * @template A + * @no-named-arguments + * + * @param A $values + * + * @return self + */ + public static function of(mixed ...$values): self + { + return new self(Immutable\Set::of(...$values)); + } + + /** + * @return int<0, max> + */ + #[\NoDiscard] + public function size(): int + { + return $this->set->size(); + } + + /** + * Add an element to the set + * + * @param T $element + */ + public function add($element): void + { + $this->set = ($this->set)($element); + } + + /** + * Check if the set contains the given element + * + * @param T $element + */ + #[\NoDiscard] + public function contains($element): bool + { + return $this->set->contains($element); + } + + /** + * Remove the element from the set + * + * @param T $element + */ + public function remove($element): void + { + $this->set = $this->set->remove($element); + } + + /** + * Remove all elements that don't satisfy the given predicate + * + * @param callable(T): bool $predicate + */ + public function filter(callable $predicate): void + { + $this->set = $this->set->filter($predicate); + } + + /** + * Remove all elements that satisfy the given predicate + * + * @param callable(T): bool $predicate + */ + public function exclude(callable $predicate): void + { + $this->set = $this->set->exclude($predicate); + } + + /** + * Apply the given function to all elements of the set + * + * @param callable(T): void $function + */ + public function foreach(callable $function): void + { + $_ = $this->set->foreach($function); + } + + /** + * Removes all elements from the set + */ + public function clear(): void + { + $this->set = $this->set->clear(); + } + + #[\NoDiscard] + public function empty(): bool + { + return $this->set->empty(); + } +} From 1dfe9ecd7b3393c16be54d85360ac21d1b9ece80 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Tue, 2 Dec 2025 15:30:03 +0100 Subject: [PATCH 02/12] add Map --- src/Map.php | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 src/Map.php diff --git a/src/Map.php b/src/Map.php new file mode 100644 index 0000000..fba9f7a --- /dev/null +++ b/src/Map.php @@ -0,0 +1,147 @@ + $map + */ + private function __construct( + private Immutable\Map $map, + ) { + } + + /** + * Set a new key/value pair + * + * @param K $key + * @param V $value + */ + public function __invoke($key, $value): void + { + $this->map = ($this->map)($key, $value); + } + + /** + * @template A + * @template B + * @no-named-arguments + * + * @param list $pairs + * + * @return self + */ + public static function of(array ...$pairs): self + { + return new self(Immutable\Map::of(...$pairs)); + } + + /** + * @return int<0, max> + */ + #[\NoDiscard] + public function size(): int + { + return $this->map->size(); + } + + /** + * Set a new key/value pair + * + * @param K $key + * @param V $value + */ + public function put($key, $value): void + { + $this->map = ($this->map)($key, $value); + } + + /** + * Return the element with the given key + * + * @param K $key + * + * @return Maybe + */ + #[\NoDiscard] + public function get($key): Maybe + { + return $this->map->get($key); + } + + /** + * Check if there is an element for the given key + * + * @param K $key + */ + #[\NoDiscard] + public function contains($key): bool + { + return $this->map->contains($key); + } + + /** + * Remove all elements from the map + */ + public function clear(): void + { + $this->map = $this->map->clear(); + } + + /** + * Remove all elements that don't match the predicate + * + * @param callable(K, V): bool $predicate + */ + public function filter(callable $predicate): void + { + $this->map = $this->map->filter($predicate); + } + + /** + * Remove all elements that match the predicate + * + * @param callable(K, V): bool $predicate + */ + public function exclude(callable $predicate): void + { + $this->map = $this->map->exclude($predicate); + } + + /** + * Run the given function for each element of the map + * + * @param callable(K, V): void $function + */ + public function foreach(callable $function): void + { + $_ = $this->map->foreach($function); + } + + /** + * Remove the element with the given key + * + * @param K $key + */ + public function remove($key): void + { + $this->map = $this->map->remove($key); + } + + #[\NoDiscard] + public function empty(): bool + { + return $this->map->empty(); + } +} From 97e3d32946be977fb701e8373feefd44214533cb Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Tue, 2 Dec 2025 15:34:44 +0100 Subject: [PATCH 03/12] CS --- src/Map.php | 10 +++++----- src/Set.php | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Map.php b/src/Map.php index fba9f7a..ea48cc6 100644 --- a/src/Map.php +++ b/src/Map.php @@ -28,7 +28,7 @@ private function __construct( * @param K $key * @param V $value */ - public function __invoke($key, $value): void + public function __invoke(mixed $key, mixed $value): void { $this->map = ($this->map)($key, $value); } @@ -62,7 +62,7 @@ public function size(): int * @param K $key * @param V $value */ - public function put($key, $value): void + public function put(mixed $key, mixed $value): void { $this->map = ($this->map)($key, $value); } @@ -75,7 +75,7 @@ public function put($key, $value): void * @return Maybe */ #[\NoDiscard] - public function get($key): Maybe + public function get(mixed $key): Maybe { return $this->map->get($key); } @@ -86,7 +86,7 @@ public function get($key): Maybe * @param K $key */ #[\NoDiscard] - public function contains($key): bool + public function contains(mixed $key): bool { return $this->map->contains($key); } @@ -134,7 +134,7 @@ public function foreach(callable $function): void * * @param K $key */ - public function remove($key): void + public function remove(mixed $key): void { $this->map = $this->map->remove($key); } diff --git a/src/Set.php b/src/Set.php index 7ef8959..f1e1d17 100644 --- a/src/Set.php +++ b/src/Set.php @@ -23,7 +23,7 @@ private function __construct( * * @param T $element */ - public function __invoke($element): void + public function __invoke(mixed $element): void { $this->set = ($this->set)($element); } @@ -55,7 +55,7 @@ public function size(): int * * @param T $element */ - public function add($element): void + public function add(mixed $element): void { $this->set = ($this->set)($element); } @@ -66,7 +66,7 @@ public function add($element): void * @param T $element */ #[\NoDiscard] - public function contains($element): bool + public function contains(mixed $element): bool { return $this->set->contains($element); } @@ -76,7 +76,7 @@ public function contains($element): bool * * @param T $element */ - public function remove($element): void + public function remove(mixed $element): void { $this->set = $this->set->remove($element); } From fb6000017186726916f9ec9482d997d3192467d1 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Tue, 2 Dec 2025 15:43:54 +0100 Subject: [PATCH 04/12] add Queue --- blackbox.php | 2 +- proofs/.gitkeep | 0 proofs/queue.php | 43 ++++++++++++++++++++++++++ src/Queue.php | 79 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 1 deletion(-) delete mode 100644 proofs/.gitkeep create mode 100644 proofs/queue.php create mode 100644 src/Queue.php diff --git a/blackbox.php b/blackbox.php index ff9d705..2588bdc 100644 --- a/blackbox.php +++ b/blackbox.php @@ -11,7 +11,7 @@ Application::new($argv) ->when( - \get_env('ENABLE_COVERAGE') !== false, + \getenv('ENABLE_COVERAGE') !== false, static fn(Application $app) => $app ->scenariiPerProof(1) ->codeCoverage( diff --git a/proofs/.gitkeep b/proofs/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/proofs/queue.php b/proofs/queue.php new file mode 100644 index 0000000..973c1c3 --- /dev/null +++ b/proofs/queue.php @@ -0,0 +1,43 @@ +same(0, $queue->size()); + $assert->true($queue->empty()); + + foreach ($values as $i => $value) { + $queue->push($value); + + $assert->false($queue->empty()); + $assert->same($i + 1, $queue->size()); + } + + $pulled = []; + $size = $queue->size(); + + foreach ($values as $i => $_) { + $pulled[] = $queue->pull()->match( + static fn($value) => $value, + static fn() => null, + ); + + $assert->same($size - 1, $queue->size()); + $size = $queue->size(); + } + + $assert->true($queue->empty()); + $assert->same($values, $pulled); + }, + ); +}; diff --git a/src/Queue.php b/src/Queue.php new file mode 100644 index 0000000..24ce214 --- /dev/null +++ b/src/Queue.php @@ -0,0 +1,79 @@ + $data + */ + private function __construct( + private Sequence $data, + ) { + } + + /** + * @template A + * @no-named-arguments + * + * @param A ...$values + * + * @return self + */ + public static function of(mixed ...$values): self + { + return new self(Sequence::of(...$values)); + } + + /** + * @param T $element + */ + public function push(mixed $element): void + { + $this->data = ($this->data)($element); + } + + /** + * @return Maybe + */ + #[\NoDiscard] + public function pull(): Maybe + { + $value = $this->data->first(); + $this->data = $this->data->drop(1); + + return $value; + } + + /** + * @return int<0, max> + */ + #[\NoDiscard] + public function size(): int + { + return $this->data->size(); + } + + #[\NoDiscard] + public function empty(): bool + { + return $this->data->empty(); + } + + /** + * Remove all elements from the queue + */ + public function clear(): void + { + $this->data = $this->data->clear(); + } +} From 05d4ac6682fb7808702dcf0cc8429440f39ace99 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Tue, 2 Dec 2025 15:45:41 +0100 Subject: [PATCH 05/12] add Stack --- proofs/stack.php | 43 ++++++++++++++++++++++++++ src/Stack.php | 79 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 proofs/stack.php create mode 100644 src/Stack.php diff --git a/proofs/stack.php b/proofs/stack.php new file mode 100644 index 0000000..2843c85 --- /dev/null +++ b/proofs/stack.php @@ -0,0 +1,43 @@ +same(0, $stack->size()); + $assert->true($stack->empty()); + + foreach ($values as $i => $value) { + $stack->push($value); + + $assert->false($stack->empty()); + $assert->same($i + 1, $stack->size()); + } + + $pulled = []; + $size = $stack->size(); + + foreach ($values as $i => $_) { + $pulled[] = $stack->pull()->match( + static fn($value) => $value, + static fn() => null, + ); + + $assert->same($size - 1, $stack->size()); + $size = $stack->size(); + } + + $assert->true($stack->empty()); + $assert->same(\array_reverse($values), $pulled); + }, + ); +}; diff --git a/src/Stack.php b/src/Stack.php new file mode 100644 index 0000000..457b6c6 --- /dev/null +++ b/src/Stack.php @@ -0,0 +1,79 @@ + $data + */ + private function __construct( + private Sequence $data, + ) { + } + + /** + * @template A + * @no-named-arguments + * + * @param A ...$values + * + * @return self + */ + public static function of(mixed ...$values): self + { + return new self(Sequence::of(...$values)); + } + + /** + * @param T $element + */ + public function push(mixed $element): void + { + $this->data = ($this->data)($element); + } + + /** + * @return Maybe + */ + #[\NoDiscard] + public function pull(): Maybe + { + $value = $this->data->last(); + $this->data = $this->data->dropEnd(1); + + return $value; + } + + /** + * @return int<0, max> + */ + #[\NoDiscard] + public function size(): int + { + return $this->data->size(); + } + + #[\NoDiscard] + public function empty(): bool + { + return $this->data->empty(); + } + + /** + * Remove all elements from the queue + */ + public function clear(): void + { + $this->data = $this->data->clear(); + } +} From f074e08df860cb106e4daede7abf0dd62241b256 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Tue, 2 Dec 2025 16:07:37 +0100 Subject: [PATCH 06/12] add Ring --- proofs/ring.php | 79 +++++++++++++++++++++++++++++++++++++++++++++++++ src/Ring.php | 53 +++++++++++++++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 proofs/ring.php create mode 100644 src/Ring.php diff --git a/proofs/ring.php b/proofs/ring.php new file mode 100644 index 0000000..0329669 --- /dev/null +++ b/proofs/ring.php @@ -0,0 +1,79 @@ +atLeast(1), + Set::integers()->between(1, 10), + ), + static function($assert, $values, $rotations) { + $ring = Ring::of(...$values); + $pulled = []; + + foreach (\range(1, $rotations) as $_) { + foreach (\range(1, \count($values)) as $__) { + $pulled[] = $ring->pull()->match( + static fn($value) => $value, + static fn() => null, + ); + } + } + + $expected = \array_merge( + ...\array_fill( + 0, + $rotations, + $values, + ), + ); + + $assert->same($expected, $pulled); + }, + ); + + yield proof( + 'Partial Ring rotations', + given( + Set::sequence(Set::type())->atLeast(1), + Set::integers()->between(1, 1_000), + ), + static function($assert, $values, $toPull) { + $ring = Ring::of(...$values); + $pulled = []; + + foreach (\range(1, $toPull) as $_) { + $pulled[] = $ring->pull()->match( + static fn($value) => $value, + static fn() => null, + ); + } + + $expected = \array_slice( + \array_merge( + ...\array_fill( + 0, + (int) \ceil($toPull / \count($values)), + $values, + ), + ), + 0, + $toPull, + ); + + $assert->same($expected, $pulled); + }, + ); + + yield test( + 'Empty Ring returns nothing', + static fn($assert) => $assert->false(Ring::of()->pull()->match( + static fn() => true, + static fn() => false, + )), + ); +}; diff --git a/src/Ring.php b/src/Ring.php new file mode 100644 index 0000000..e6d3314 --- /dev/null +++ b/src/Ring.php @@ -0,0 +1,53 @@ + $data + */ + private function __construct( + private array $data, + ) { + } + + /** + * @template A + * @no-named-arguments + * + * @param A ...$values + * + * @return self + */ + public static function of(mixed ...$values): self + { + return new self($values); + } + + /** + * @return Maybe + */ + public function pull(): Maybe + { + if (!\array_key_exists(0, $this->data)) { + /** @var Maybe */ + return Maybe::nothing(); + } + + $value = Maybe::just(\current($this->data)); + \next($this->data); + + if (\is_null(\key($this->data))) { + \reset($this->data); + } + + return $value; + } +} From 28d26ef5e15ddcffc7a3ca3b308e4c32c8a25070 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Tue, 2 Dec 2025 16:12:10 +0100 Subject: [PATCH 07/12] fix CI badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3af62f3..b57c631 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # mutable -[![Build Status](https://github.com/innmind/mutable/workflows/CI/badge.svg?branch=main)](https://github.com/innmind/mutable/actions?query=workflow%3ACI) +[![Build Status](https://github.com/Innmind/mutable/actions/workflows/ci.yml/badge.svg)](https://github.com/Innmind/mutable/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/innmind/mutable/branch/develop/graph/badge.svg)](https://codecov.io/gh/innmind/mutable) [![Type Coverage](https://shepherd.dev/github/innmind/mutable/coverage.svg)](https://shepherd.dev/github/innmind/mutable) From 5a6be3fd3641c88491ad401a4aba30836bd05dab Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 4 Dec 2025 13:43:20 +0100 Subject: [PATCH 08/12] declare constructors as pure --- src/Map.php | 3 +++ src/Queue.php | 3 +++ src/Ring.php | 3 +++ src/Set.php | 3 +++ src/Stack.php | 3 +++ 5 files changed, 15 insertions(+) diff --git a/src/Map.php b/src/Map.php index ea48cc6..f65b470 100644 --- a/src/Map.php +++ b/src/Map.php @@ -15,6 +15,8 @@ final class Map { /** + * @psalm-mutation-free + * * @param Immutable\Map $map */ private function __construct( @@ -37,6 +39,7 @@ public function __invoke(mixed $key, mixed $value): void * @template A * @template B * @no-named-arguments + * @psalm-pure * * @param list $pairs * diff --git a/src/Queue.php b/src/Queue.php index 24ce214..9a1df4a 100644 --- a/src/Queue.php +++ b/src/Queue.php @@ -14,6 +14,8 @@ final class Queue { /** + * @psalm-mutation-free + * * @param Sequence $data */ private function __construct( @@ -24,6 +26,7 @@ private function __construct( /** * @template A * @no-named-arguments + * @psalm-pure * * @param A ...$values * diff --git a/src/Ring.php b/src/Ring.php index e6d3314..c25dccd 100644 --- a/src/Ring.php +++ b/src/Ring.php @@ -11,6 +11,8 @@ final class Ring { /** + * @psalm-mutation-free + * * @param list $data */ private function __construct( @@ -21,6 +23,7 @@ private function __construct( /** * @template A * @no-named-arguments + * @psalm-pure * * @param A ...$values * diff --git a/src/Set.php b/src/Set.php index f1e1d17..a3b193d 100644 --- a/src/Set.php +++ b/src/Set.php @@ -11,6 +11,8 @@ final class Set { /** + * @psalm-mutation-free + * * @param Immutable\Set $set */ private function __construct( @@ -31,6 +33,7 @@ public function __invoke(mixed $element): void /** * @template A * @no-named-arguments + * @psalm-pure * * @param A $values * diff --git a/src/Stack.php b/src/Stack.php index 457b6c6..f86c75d 100644 --- a/src/Stack.php +++ b/src/Stack.php @@ -14,6 +14,8 @@ final class Stack { /** + * @psalm-mutation-free + * * @param Sequence $data */ private function __construct( @@ -24,6 +26,7 @@ private function __construct( /** * @template A * @no-named-arguments + * @psalm-pure * * @param A ...$values * From 832b90a3af9b9baf1bfce959068cb13fad50c307 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Thu, 4 Dec 2025 14:43:51 +0100 Subject: [PATCH 09/12] add Ring::reset() --- src/Ring.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Ring.php b/src/Ring.php index c25dccd..a909b44 100644 --- a/src/Ring.php +++ b/src/Ring.php @@ -53,4 +53,12 @@ public function pull(): Maybe return $value; } + + /** + * Move the ring cursor to the first value + */ + public function reset(): void + { + \reset($this->data); + } } From eca7c468dd0aec5e8f42e6c1a7521a9c1fcf4493 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 14 Dec 2025 16:53:43 +0100 Subject: [PATCH 10/12] expose an immutable snapshots of sets/maps --- src/Map.php | 9 +++++++++ src/Set.php | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/src/Map.php b/src/Map.php index f65b470..70df0a1 100644 --- a/src/Map.php +++ b/src/Map.php @@ -147,4 +147,13 @@ public function empty(): bool { return $this->map->empty(); } + + /** + * @return Immutable\Map + */ + #[\NoDiscard] + public function snapshot(): Immutable\Map + { + return $this->map; + } } diff --git a/src/Set.php b/src/Set.php index a3b193d..7f2cd49 100644 --- a/src/Set.php +++ b/src/Set.php @@ -127,4 +127,13 @@ public function empty(): bool { return $this->set->empty(); } + + /** + * @return Immutable\Set + */ + #[\NoDiscard] + public function snapshot(): Immutable\Set + { + return $this->set; + } } From 5a6724db062c7b0799df7476e1e749583da83bcd Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 18 Jan 2026 15:09:01 +0100 Subject: [PATCH 11/12] add readme --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b57c631..2bd64a3 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# mutable +# Mutable [![Build Status](https://github.com/Innmind/mutable/actions/workflows/ci.yml/badge.svg)](https://github.com/Innmind/mutable/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/innmind/mutable/branch/develop/graph/badge.svg)](https://codecov.io/gh/innmind/mutable) [![Type Coverage](https://shepherd.dev/github/innmind/mutable/coverage.svg)](https://shepherd.dev/github/innmind/mutable) -Description +This a collection of mutable data structures. ## Installation @@ -14,4 +14,10 @@ composer require innmind/mutable ## Usage -Todo +Available structures: + +- `Innmind\Mutable\Map` +- `Innmind\Mutable\Queue` FIFO queue +- `Innmind\Mutable\Ring` Circle through a fixed sequence of data in an infinite loop +- `Innmind\Mutable\Set` +- `Innmind\Mutable\Stack` LIFO queue From 4273c60751c4fd6c03bc17c386d79a282168e4e2 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 18 Jan 2026 15:10:10 +0100 Subject: [PATCH 12/12] tag dependencies --- .github/workflows/ci.yml | 8 ++++---- composer.json | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 779f162..2f3eecb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,11 +4,11 @@ on: [push, pull_request] jobs: blackbox: - uses: innmind/github-workflows/.github/workflows/black-box-matrix.yml@next + uses: innmind/github-workflows/.github/workflows/black-box-matrix.yml@main coverage: - uses: innmind/github-workflows/.github/workflows/coverage-matrix.yml@next + uses: innmind/github-workflows/.github/workflows/coverage-matrix.yml@main secrets: inherit psalm: - uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@next + uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@main cs: - uses: innmind/github-workflows/.github/workflows/cs.yml@next + uses: innmind/github-workflows/.github/workflows/cs.yml@main diff --git a/composer.json b/composer.json index f4f9543..96ccac4 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,7 @@ }, "require": { "php": "~8.4", - "innmind/immutable": "dev-next" + "innmind/immutable": "~6.0" }, "autoload": { "psr-4": { @@ -24,7 +24,7 @@ } }, "require-dev": { - "innmind/static-analysis": "^1.2.1", + "innmind/static-analysis": "~1.3", "innmind/black-box": "~6.5", "innmind/coding-standard": "~2.0" }