Add basic framework
This commit is contained in:
commit
17e575d38b
51
.github/workflows/ci.yaml
vendored
Normal file
51
.github/workflows/ci.yaml
vendored
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
php-cs-fixer:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
ref: ${{ github.head_ref }}
|
||||||
|
|
||||||
|
- name: Run php-cs-fixer
|
||||||
|
uses: docker://oskarstark/php-cs-fixer-ga
|
||||||
|
|
||||||
|
- name: Commit changes
|
||||||
|
uses: stefanzweifel/git-auto-commit-action@v4
|
||||||
|
with:
|
||||||
|
commit_message: Apply php-cs-fixer changes
|
||||||
|
|
||||||
|
phpunit:
|
||||||
|
name: PHPUnit (PHP ${{ matrix.php-versions }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
php-versions: ['8.0']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Composer install
|
||||||
|
uses: php-actions/composer@v5
|
||||||
|
|
||||||
|
- name: Run PHPUnit tests
|
||||||
|
run: composer test
|
||||||
|
|
||||||
|
psalm:
|
||||||
|
name: Psalm
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Psalm
|
||||||
|
uses: docker://vimeo/psalm-github-actions
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/vendor/
|
||||||
|
/.idea/
|
||||||
|
/.php-cs-fixer.cache
|
27
.php-cs-fixer.php
Normal file
27
.php-cs-fixer.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$finder = PhpCsFixer\Finder::create()
|
||||||
|
->in(__DIR__.'/src/');
|
||||||
|
|
||||||
|
$config = new PhpCsFixer\Config();
|
||||||
|
|
||||||
|
return $config
|
||||||
|
->setRules([
|
||||||
|
'@PSR2' => true,
|
||||||
|
'@Symfony' => true,
|
||||||
|
'@PhpCsFixer' => true,
|
||||||
|
'phpdoc_order' => true,
|
||||||
|
'ordered_class_elements' => true,
|
||||||
|
'multiline_whitespace_before_semicolons' => false,
|
||||||
|
'no_superfluous_phpdoc_tags' => false,
|
||||||
|
'phpdoc_annotation_without_dot' => false,
|
||||||
|
'phpdoc_types_order' => [
|
||||||
|
'null_adjustment' => 'always_last',
|
||||||
|
],
|
||||||
|
'yoda_style' => false,
|
||||||
|
'ternary_to_null_coalescing' => true,
|
||||||
|
'array_syntax' => ['syntax' => 'short'],
|
||||||
|
'php_unit_test_class_requires_covers' => false,
|
||||||
|
])
|
||||||
|
->setFinder($finder);
|
77
README.md
Normal file
77
README.md
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# Advent of Code 2021
|
||||||
|
|
||||||
|
In this repository, you'll find my solutions.
|
||||||
|
|
||||||
|
## 🛠 Setup and running
|
||||||
|
- Run `composer install` to install the dependencies.
|
||||||
|
- Run `./aoc21 {day}` to run the solution for a specific day (for example `./aoc21 1` to run the code for day 1)
|
||||||
|
- Run `composer test` to automatically validate the solutions.
|
||||||
|
|
||||||
|
## 🧩 Add a new puzzle/solution
|
||||||
|
- Create a directory in `./data` with the correct name.
|
||||||
|
- Create `example.txt` with the example values from the puzzle.
|
||||||
|
- Create `data.txt` with your personal input.
|
||||||
|
- Create `puzzle.md` with the puzzle. You can use [this plugin](https://github.com/kfarnung/aoc-to-markdown) to easily convert the puzzle to markdown.
|
||||||
|
- Create a new class in the `src` directory and make sure it has the structure defined below.
|
||||||
|
- Add this class to the `./aoc21` file, and you can run it.
|
||||||
|
- Add a new test in `./tests` with structure defined below.
|
||||||
|
- Run `composer test` to run all the tests.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Solution command structure</summary>
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace AdventOfCode21;
|
||||||
|
|
||||||
|
// Make sure the classname is correct.
|
||||||
|
class Day1 extends AbstractCommand
|
||||||
|
{
|
||||||
|
// Update this to the day number.
|
||||||
|
protected static int $day = 1;
|
||||||
|
|
||||||
|
protected function part1(array $data): int
|
||||||
|
{
|
||||||
|
// Solution for part 1.
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function part2(array $data): int
|
||||||
|
{
|
||||||
|
// Solution for part 2.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Solution test structure</summary>
|
||||||
|
|
||||||
|
```php
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests;
|
||||||
|
|
||||||
|
// Make sure the classname is correct.
|
||||||
|
class Day1Test extends AbstractTestCase
|
||||||
|
{
|
||||||
|
// Provide the expected results for part 1.
|
||||||
|
public static int $part1ExampleResult = 7;
|
||||||
|
public static int $part1Result = 1688;
|
||||||
|
|
||||||
|
// Provide the expected results for part 2.
|
||||||
|
public static int $part2ExampleResult = 5;
|
||||||
|
public static int $part2Result = 1728;
|
||||||
|
|
||||||
|
// Make a new instance of the command with the 'ReturnTestableResults' trait.
|
||||||
|
public function setupDay(): Day1
|
||||||
|
{
|
||||||
|
return new class() extends Day1 {
|
||||||
|
use ReturnTestableResults;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
13
aoc21
Executable file
13
aoc21
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env php
|
||||||
|
<?php
|
||||||
|
require __DIR__.'/vendor/autoload.php';
|
||||||
|
|
||||||
|
use AdventOfCode21\Day1;
|
||||||
|
use AdventOfCode21\Puzzle;
|
||||||
|
use Symfony\Component\Console\Application;
|
||||||
|
|
||||||
|
$application = new Application();
|
||||||
|
|
||||||
|
$application->add(new Puzzle());
|
||||||
|
|
||||||
|
$application->run();
|
39
composer.json
Normal file
39
composer.json
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"name": "trizz/adventofcode21",
|
||||||
|
"description": "My Advent of Code 2021 solutions",
|
||||||
|
"type": "project",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Tristan",
|
||||||
|
"email": "me@trizz.io"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": "^8.0",
|
||||||
|
"symfony/console": "^5",
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"cebe/markdown": "^1.2"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"friendsofphp/php-cs-fixer": "^3.3",
|
||||||
|
"phpunit/phpunit": "^9.5",
|
||||||
|
"symfony/var-dumper": "^6.0",
|
||||||
|
"vimeo/psalm": "^4.13",
|
||||||
|
"jetbrains/phpstorm-attributes": "^1.0"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"AdventOfCode21\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Tests\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "vendor/bin/phpunit ./tests --testdox",
|
||||||
|
"style": "vendor/bin/php-cs-fixer fix"
|
||||||
|
}
|
||||||
|
}
|
4968
composer.lock
generated
Normal file
4968
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
17
psalm.xml
Normal file
17
psalm.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<psalm
|
||||||
|
errorLevel="1"
|
||||||
|
resolveFromConfigFile="true"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="https://getpsalm.org/schema/config"
|
||||||
|
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
||||||
|
>
|
||||||
|
<projectFiles>
|
||||||
|
<directory name="src" />
|
||||||
|
<ignoreFiles>
|
||||||
|
<directory name="vendor" />
|
||||||
|
<!-- Skip for now -->
|
||||||
|
<file name="src/Utils/SymfonyConsoleMarkdown.php" />
|
||||||
|
</ignoreFiles>
|
||||||
|
</projectFiles>
|
||||||
|
</psalm>
|
125
src/AbstractCommand.php
Normal file
125
src/AbstractCommand.php
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace AdventOfCode21;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
abstract class AbstractCommand extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var int The day number.
|
||||||
|
*/
|
||||||
|
protected static int $day = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[] The data to use.
|
||||||
|
* @psalm-suppress PropertyNotSetInConstructor
|
||||||
|
*/
|
||||||
|
protected array $data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string[] The example data.
|
||||||
|
* @psalm-suppress PropertyNotSetInConstructor
|
||||||
|
*/
|
||||||
|
protected array $exampleData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string The title.
|
||||||
|
*/
|
||||||
|
private string $title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the command.
|
||||||
|
*/
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setName((string) static::$day)
|
||||||
|
->setDescription('Run day '.static::$day);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the command after the input has been bound and before the input
|
||||||
|
* is validated.
|
||||||
|
*/
|
||||||
|
protected function initialize(InputInterface $input, OutputInterface $output): void
|
||||||
|
{
|
||||||
|
$this->title = 'Advent of Code - Day '.static::$day;
|
||||||
|
|
||||||
|
$dataFile = sprintf('%s/../data/day%d/data.txt', __DIR__, static::$day);
|
||||||
|
$dataExampleFile = sprintf('%s/../data/day%d/example.txt', __DIR__, static::$day);
|
||||||
|
|
||||||
|
if (file_exists($dataFile)) {
|
||||||
|
$this->data = array_filter(explode(PHP_EOL, file_get_contents($dataFile)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_exists($dataExampleFile)) {
|
||||||
|
$this->exampleData = array_filter(explode(PHP_EOL, file_get_contents($dataExampleFile)));
|
||||||
|
}
|
||||||
|
|
||||||
|
$output->writeln('');
|
||||||
|
$output->writeln($this->title);
|
||||||
|
$output->writeln(str_repeat('-', strlen($this->title)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Executes the current command.
|
||||||
|
*
|
||||||
|
* @return int 0 if everything went fine, or an exit code
|
||||||
|
*/
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
// Solve the examples if available.
|
||||||
|
$resultPart1Example = 'n/a';
|
||||||
|
$resultPart2Example = 'n/a';
|
||||||
|
if ($this->exampleData) {
|
||||||
|
$resultPart1Example = $this->part1($this->exampleData);
|
||||||
|
$resultPart2Example = $this->part2($this->exampleData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solve the real puzzle if available.
|
||||||
|
$resultPart1 = 'n/a';
|
||||||
|
$resultPart2 = 'n/a';
|
||||||
|
if ($this->data) {
|
||||||
|
$resultPart1 = $this->part1($this->data);
|
||||||
|
$resultPart2 = $this->part2($this->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output all the results.
|
||||||
|
$output->writeln('<fg=bright-green>Part 1</>');
|
||||||
|
$output->writeln(sprintf('<fg=blue>Example:</> <comment>%s</comment>', $resultPart1Example));
|
||||||
|
$output->writeln(sprintf('<fg=blue>Result: </> <comment>%s</comment>', $resultPart1));
|
||||||
|
$output->writeln(str_repeat('-', strlen($this->title)));
|
||||||
|
$output->writeln('<fg=bright-green>Part 2</>');
|
||||||
|
$output->writeln(sprintf('<fg=blue>Example:</> <comment>%s</comment>', $resultPart2Example));
|
||||||
|
$output->writeln(sprintf('<fg=blue>Result: </> <comment>%s</comment>', $resultPart2));
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Solve the given data for part one of the puzzle.
|
||||||
|
*
|
||||||
|
* @param array $data The data to process.
|
||||||
|
*
|
||||||
|
* @return int|string The result or null if not (yet?) implemented.
|
||||||
|
*/
|
||||||
|
protected function part1(array $data): int|string
|
||||||
|
{
|
||||||
|
return 'n/a';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Solve the given data for part one of the puzzle.
|
||||||
|
*
|
||||||
|
* @param array $data The data to process.
|
||||||
|
*
|
||||||
|
* @return int|string The result or null if not (yet?) implemented.
|
||||||
|
*/
|
||||||
|
protected function part2(array $data): int|string
|
||||||
|
{
|
||||||
|
return 'n/a';
|
||||||
|
}
|
||||||
|
}
|
29
src/Puzzle.php
Normal file
29
src/Puzzle.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace AdventOfCode21;
|
||||||
|
|
||||||
|
use AdventOfCode21\Utils\SymfonyConsoleMarkdown;
|
||||||
|
use Symfony\Component\Console\Command\Command;
|
||||||
|
use Symfony\Component\Console\Input\InputArgument;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
|
||||||
|
class Puzzle extends Command
|
||||||
|
{
|
||||||
|
protected function configure(): void
|
||||||
|
{
|
||||||
|
$this
|
||||||
|
->setName('puzzle')
|
||||||
|
->addArgument('day', InputArgument::REQUIRED, 'The day number.');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||||
|
{
|
||||||
|
$contents = file_get_contents(sprintf('%s/../data/day%s/puzzle.md', __DIR__, (int) $input->getArgument('day')));
|
||||||
|
$rendered = (new SymfonyConsoleMarkdown())->render($contents);
|
||||||
|
|
||||||
|
$output->writeln($rendered);
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
}
|
230
src/Utils/SymfonyConsoleMarkdown.php
Normal file
230
src/Utils/SymfonyConsoleMarkdown.php
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace AdventOfCode21\Utils;
|
||||||
|
|
||||||
|
use cebe\markdown\GithubMarkdown;
|
||||||
|
use function explode;
|
||||||
|
use function implode;
|
||||||
|
use JetBrains\PhpStorm\Pure;
|
||||||
|
use function ltrim;
|
||||||
|
use function sprintf;
|
||||||
|
use function str_repeat;
|
||||||
|
use function str_replace;
|
||||||
|
use function substr;
|
||||||
|
use function trim;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on: https://github.com/phppkg/cli-markdown/blob/f9fbfd50cc09ff8904ea2bb47660b93036235b6d/src/CliMarkdown.php.
|
||||||
|
*
|
||||||
|
* @todo improve all the things and fix Psalm errors.
|
||||||
|
*/
|
||||||
|
class SymfonyConsoleMarkdown extends GithubMarkdown
|
||||||
|
{
|
||||||
|
public const NL = "\n";
|
||||||
|
|
||||||
|
public const NL2 = "\n\n";
|
||||||
|
|
||||||
|
#[Pure]
|
||||||
|
public function wrapColor(string $text, string $fg = null, string $bg = null, string $options = null): string
|
||||||
|
{
|
||||||
|
$values = urldecode(http_build_query(compact('fg', 'bg', 'options'), arg_separator: ';'));
|
||||||
|
|
||||||
|
if (empty($values)) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sprintf('<%s>%s</>', $values, $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render(string $text): string
|
||||||
|
{
|
||||||
|
return $this->parse($text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parse($text): string
|
||||||
|
{
|
||||||
|
$parsed = parent::parse($text);
|
||||||
|
|
||||||
|
return str_replace(["\n\n\n", "\n\n\n\n"], "\n\n", ltrim($parsed));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function renderHeadline($block): string
|
||||||
|
{
|
||||||
|
$level = (int) $block['level'];
|
||||||
|
|
||||||
|
$prefix = str_repeat('#', $level);
|
||||||
|
$title = $this->renderAbsy($block['content']);
|
||||||
|
|
||||||
|
$hlText = $prefix.' '.$title;
|
||||||
|
|
||||||
|
return self::NL.$this->wrapColor($hlText, fg: 'yellow', options: 'bold').self::NL2;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function renderParagraph($block): string
|
||||||
|
{
|
||||||
|
return self::NL.$this->renderAbsy($block['content']).self::NL;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function renderList($block): string
|
||||||
|
{
|
||||||
|
$output = self::NL;
|
||||||
|
|
||||||
|
foreach ($block['items'] as $itemLines) {
|
||||||
|
$output .= '● '.$this->renderAbsy($itemLines)."\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output.self::NL2;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function renderTable($block): string
|
||||||
|
{
|
||||||
|
$head = $body = '';
|
||||||
|
// $cols = $block['cols'];
|
||||||
|
|
||||||
|
$tabInfo = ['width' => 60];
|
||||||
|
$colWidths = [];
|
||||||
|
foreach ($block['rows'] as $row) {
|
||||||
|
foreach ($row as $c => $cell) {
|
||||||
|
$cellLen = $this->getCellWith($cell);
|
||||||
|
|
||||||
|
if (!isset($tabInfo[$c])) {
|
||||||
|
$colWidths[$c] = 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
$colWidths[$c] = $this->compareMax($cellLen, $colWidths[$c]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$colCount = count($colWidths);
|
||||||
|
$tabWidth = array_sum($colWidths);
|
||||||
|
|
||||||
|
$first = true;
|
||||||
|
$splits = [];
|
||||||
|
foreach ($block['rows'] as $row) {
|
||||||
|
// $cellTag = $first ? 'th' : 'td';
|
||||||
|
$tds = [];
|
||||||
|
foreach ($row as $c => $cell) {
|
||||||
|
$cellLen = $colWidths[$c];
|
||||||
|
|
||||||
|
// ︱||—― ̄====▪▪▭▭▃▃▄▄▁▁▕▏▎┇╇══
|
||||||
|
if ($first) {
|
||||||
|
$splits[] = str_pad('=', $cellLen + 1, '=');
|
||||||
|
}
|
||||||
|
|
||||||
|
$lastIdx = count($cell) - 1;
|
||||||
|
// padding space to last item contents.
|
||||||
|
foreach ($cell as $idx => &$item) {
|
||||||
|
if ($lastIdx === $idx) {
|
||||||
|
$item[1] = str_pad($item[1], $cellLen);
|
||||||
|
} else {
|
||||||
|
$cellLen -= mb_strlen($item[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unset($item);
|
||||||
|
|
||||||
|
$tds[] = trim($this->renderAbsy($cell), "\n\r");
|
||||||
|
}
|
||||||
|
|
||||||
|
$tdsStr = implode(' | ', $tds);
|
||||||
|
if ($first) {
|
||||||
|
$head .= sprintf("%s\n%s\n%s\n", implode('=', $splits), $tdsStr, implode('|', $splits));
|
||||||
|
} else {
|
||||||
|
$body .= $tdsStr."\n";
|
||||||
|
}
|
||||||
|
$first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return $this->composeTable($head, $body);
|
||||||
|
return $head.$body.str_pad('=', $tabWidth + $colCount + 1, '=').self::NL;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getCellWith(array $cellElems): int
|
||||||
|
{
|
||||||
|
$width = 0;
|
||||||
|
foreach ($cellElems as $elem) {
|
||||||
|
$width += mb_strlen($elem[1] ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $width;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function renderLink($block): string
|
||||||
|
{
|
||||||
|
preg_match('/(\[.*])(\(.*\))/', $block['orig'], $matches);
|
||||||
|
|
||||||
|
[, $title, $link] = $matches;
|
||||||
|
|
||||||
|
$title = substr($title, 1, -1);
|
||||||
|
$link = substr($link, 1, -1);
|
||||||
|
|
||||||
|
$value = $link === $title ? $link : sprintf('[%s](%s)', $title, $link);
|
||||||
|
|
||||||
|
return $this->wrapColor($value, fg: 'bright-blue');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Pure]
|
||||||
|
protected function renderAutoUrl($block): string
|
||||||
|
{
|
||||||
|
return $this->wrapColor($block[1], fg: 'bright-blue');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Pure]
|
||||||
|
protected function renderImage($block): string
|
||||||
|
{
|
||||||
|
return self::NL.$this->wrapColor('▨ '.$block['orig'], fg: 'blue');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function renderQuote($block): string
|
||||||
|
{
|
||||||
|
// ¶ §
|
||||||
|
$content = ltrim($this->renderAbsy($block['content']));
|
||||||
|
|
||||||
|
return self::NL.'¶ '.$this->wrapColor($content, fg: 'green', options: 'bold');
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Pure]
|
||||||
|
protected function renderCode($block): string
|
||||||
|
{
|
||||||
|
$lines = explode(self::NL, $block['content']);
|
||||||
|
$text = implode("\n ", $lines);
|
||||||
|
|
||||||
|
return "\n ".$this->wrapColor($text, fg: 'gray').self::NL2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Pure]
|
||||||
|
protected function renderInlineCode($block): string
|
||||||
|
{
|
||||||
|
return $this->wrapColor($block[1], fg: 'bright-red');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function renderStrong($block): string
|
||||||
|
{
|
||||||
|
$text = $this->renderAbsy($block[1]);
|
||||||
|
|
||||||
|
return $this->wrapColor(sprintf('**%s**', $text), options: 'bold');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function renderEmph($block): string
|
||||||
|
{
|
||||||
|
$text = $this->renderAbsy($block[1]);
|
||||||
|
|
||||||
|
return $this->wrapColor(sprintf('_%s_', $text), fg: 'bright-white');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @psalm-suppress ParamNameMismatch Mismatch is caused by a package.
|
||||||
|
*
|
||||||
|
* @param mixed $block
|
||||||
|
*/
|
||||||
|
protected function renderText($block): string
|
||||||
|
{
|
||||||
|
return $block[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function compareMax(int $len1, int $len2): int
|
||||||
|
{
|
||||||
|
return $len1 > $len2 ? $len1 : $len2;
|
||||||
|
}
|
||||||
|
}
|
30
tests/AbstractTestCase.php
Normal file
30
tests/AbstractTestCase.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests;
|
||||||
|
|
||||||
|
use AdventOfCode21\AbstractCommand;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
abstract class AbstractTestCase extends TestCase
|
||||||
|
{
|
||||||
|
public AbstractCommand $command;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
$this->command = $this->setupDay();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPart1(): void
|
||||||
|
{
|
||||||
|
$this->assertSame(static::$part1ExampleResult, $this->command->part1ExampleResult());
|
||||||
|
$this->assertSame(static::$part1Result, $this->command->part1Result());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPart2(): void
|
||||||
|
{
|
||||||
|
$this->assertSame(static::$part2ExampleResult, $this->command->part2ExampleResult());
|
||||||
|
$this->assertSame(static::$part2Result, $this->command->part2Result());
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public function setupDay(): AbstractCommand;
|
||||||
|
}
|
37
tests/ReturnTestableResults.php
Normal file
37
tests/ReturnTestableResults.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests;
|
||||||
|
|
||||||
|
use Symfony\Component\Console\Input\StringInput;
|
||||||
|
use Symfony\Component\Console\Output\NullOutput;
|
||||||
|
|
||||||
|
trait ReturnTestableResults
|
||||||
|
{
|
||||||
|
public function part1ExampleResult(): int|string|null
|
||||||
|
{
|
||||||
|
$this->initialize(new StringInput(''), new NullOutput());
|
||||||
|
|
||||||
|
return $this->part1($this->exampleData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function part1Result(): int|string|null
|
||||||
|
{
|
||||||
|
$this->initialize(new StringInput(''), new NullOutput());
|
||||||
|
|
||||||
|
return $this->part1($this->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function part2ExampleResult(): int|string|null
|
||||||
|
{
|
||||||
|
$this->initialize(new StringInput(''), new NullOutput());
|
||||||
|
|
||||||
|
return $this->part2($this->exampleData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function part2Result(): int|string|null
|
||||||
|
{
|
||||||
|
$this->initialize(new StringInput(''), new NullOutput());
|
||||||
|
|
||||||
|
return $this->part2($this->data);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user