mirror of
https://github.com/Respect/Validation.git
synced 2026-03-14 22:35:45 +01:00
I ran the `bin/console spdx --fix` with different strategies for different files. For most of the core classes, since they've been drastically rebuilt, I've run it with the `git-blame` strategy, for for the `src/Validators`, in which the API changed completely but the logic remains the same, I use the `git-log` strategy.
312 lines
9.2 KiB
PHP
312 lines
9.2 KiB
PHP
<?php
|
|
|
|
/*
|
|
* SPDX-License-Identifier: MIT
|
|
* SPDX-FileCopyrightText: (c) Respect Project Contributors
|
|
* SPDX-FileContributor: Alexandre Gomes Gaigalas <alganet@gmail.com>
|
|
* SPDX-FileContributor: Henrique Moody <henriquemoody@gmail.com>
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Respect\Dev\Markdown;
|
|
|
|
use ArrayIterator;
|
|
use IteratorAggregate;
|
|
use UnexpectedValueException;
|
|
|
|
use function array_fill;
|
|
use function array_find_key;
|
|
use function array_keys;
|
|
use function array_map;
|
|
use function array_slice;
|
|
use function count;
|
|
use function explode;
|
|
use function file_get_contents;
|
|
use function implode;
|
|
use function max;
|
|
use function preg_match;
|
|
use function preg_replace;
|
|
use function reset;
|
|
use function rtrim;
|
|
use function sprintf;
|
|
use function str_pad;
|
|
use function str_repeat;
|
|
use function str_replace;
|
|
use function str_starts_with;
|
|
use function strlen;
|
|
use function strrpos;
|
|
use function trim;
|
|
|
|
use const PHP_EOL;
|
|
use const STR_PAD_BOTH;
|
|
use const STR_PAD_LEFT;
|
|
use const STR_PAD_RIGHT;
|
|
|
|
final class Content implements IteratorAggregate
|
|
{
|
|
public const string REFERENCES_SECTION = '/^\[.+\]: .+$/';
|
|
|
|
public function __construct(
|
|
/** @var array<string> */
|
|
private array $lines = [],
|
|
) {
|
|
}
|
|
|
|
public static function from(string $filename): self
|
|
{
|
|
return new self(explode(PHP_EOL, file_get_contents($filename)));
|
|
}
|
|
|
|
public function build(): string
|
|
{
|
|
return trim(implode(PHP_EOL, $this->lines)) . PHP_EOL;
|
|
}
|
|
|
|
public function raw(string ...$lines): void
|
|
{
|
|
$this->lines = [...$this->lines, ...$lines];
|
|
}
|
|
|
|
public function paragraph(string $text): void
|
|
{
|
|
$this->lines[] = $text;
|
|
}
|
|
|
|
public function emptyLine(): void
|
|
{
|
|
$this->lines[] = '';
|
|
}
|
|
|
|
public function hr(): void
|
|
{
|
|
$this->lines[] = '---' . PHP_EOL;
|
|
}
|
|
|
|
public function heading(string $title, int $level): void
|
|
{
|
|
$this->lines[] = str_repeat('#', $level) . ' ' . $title . PHP_EOL;
|
|
}
|
|
|
|
public function h1(string $title): void
|
|
{
|
|
$this->heading($title, 1);
|
|
}
|
|
|
|
public function h2(string $title): void
|
|
{
|
|
$this->heading($title, 2);
|
|
}
|
|
|
|
public function h3(string $title): void
|
|
{
|
|
$this->heading($title, 3);
|
|
}
|
|
|
|
public function listItem(string $item): void
|
|
{
|
|
$this->lines[] = '- ' . $item;
|
|
}
|
|
|
|
public function anchorListItem(string $title, string $href): void
|
|
{
|
|
$this->listItem(sprintf('[%s](%s)', $title, $href));
|
|
}
|
|
|
|
public function reference(string $title, string $href, string|null $description = null): void
|
|
{
|
|
$this->lines[] = match ($description) {
|
|
null => sprintf('[%s]: %s', $title, $href),
|
|
default => sprintf('[%s]: %s "%s"', $title, $href, $description),
|
|
};
|
|
}
|
|
|
|
public function extractSpdx(): self
|
|
{
|
|
$start = 0;
|
|
$end = 0;
|
|
foreach ($this->lines as $position => $line) {
|
|
if (preg_match('/^<!--/', $line) === 1) {
|
|
$start = $position;
|
|
continue;
|
|
}
|
|
|
|
if (preg_match('/-->/', $line) === 1) {
|
|
$end = $position;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return new self(array_slice($this->lines, $start, $end + 2));
|
|
}
|
|
|
|
public static function stripRefs(string $text): string
|
|
{
|
|
return preg_replace('/\[(.+?)\](?:\[\]|\(.+?\))/', '$1', $text) ?? $text;
|
|
}
|
|
|
|
public function withSection(Content $content): self
|
|
{
|
|
$firstLine = reset($content->lines);
|
|
|
|
// Strip trailing whitespace/newlines for comparison
|
|
$firstLineTrimmed = rtrim($firstLine);
|
|
$sectionStart = array_find_key($this->lines, static fn($line) => $line === $firstLineTrimmed);
|
|
|
|
// If exact match not found, try comparing trimmed versions
|
|
if ($sectionStart === null) {
|
|
$sectionStart = array_find_key($this->lines, static fn($line) => rtrim($line) === $firstLineTrimmed);
|
|
}
|
|
|
|
if ($sectionStart === null) {
|
|
return new self([...$this->lines, ...$content->lines]);
|
|
}
|
|
|
|
$headingLevel = str_starts_with($firstLine, '#') ? strrpos($firstLine, '#') + 1 : 0;
|
|
$sectionEnd = count($this->lines) - 1;
|
|
|
|
for ($index = $sectionStart + 1; $index < count($this->lines); $index++) {
|
|
$currentLine = $this->lines[$index];
|
|
if (
|
|
$headingLevel === 0
|
|
&& (str_starts_with($currentLine, '#') || preg_match(self::REFERENCES_SECTION, $currentLine) === 1)
|
|
) {
|
|
$sectionEnd = $index;
|
|
break;
|
|
}
|
|
|
|
if (
|
|
($headingLevel > 0 && str_starts_with($currentLine, str_repeat('#', $headingLevel) . ' '))
|
|
|| preg_match(self::REFERENCES_SECTION, $currentLine) === 1
|
|
) {
|
|
$sectionEnd = $index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
$before = array_slice($this->lines, 0, $sectionStart);
|
|
$after = array_slice($this->lines, $sectionEnd);
|
|
|
|
return new self([...$before, ...$content->lines, ...$after]);
|
|
}
|
|
|
|
public function getSection(string $text): self
|
|
{
|
|
$sectionStart = array_find_key($this->lines, static fn($line) => $line === $text);
|
|
if ($sectionStart === null) {
|
|
throw new UnexpectedValueException('Section not found: ' . $text . ': ' . implode(', ', $this->lines));
|
|
}
|
|
|
|
$headingLevel = str_starts_with($text, '#') ? strrpos($text, '#') + 1 : 0;
|
|
$sectionEnd = count($this->lines);
|
|
for ($index = $sectionStart + 1; $index < count($this->lines); $index++) {
|
|
$currentLine = $this->lines[$index];
|
|
if (
|
|
$headingLevel === 0
|
|
&& (str_starts_with($currentLine, '#') || preg_match(self::REFERENCES_SECTION, $currentLine) === 1)
|
|
) {
|
|
$sectionEnd = $index;
|
|
break;
|
|
}
|
|
|
|
if (
|
|
str_starts_with($currentLine, str_repeat('#', $headingLevel) . ' ')
|
|
|| preg_match(self::REFERENCES_SECTION, $currentLine) === 1
|
|
) {
|
|
$sectionEnd = $index;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return new self(array_slice($this->lines, $sectionStart, $sectionEnd - $sectionStart));
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $headers
|
|
* @param array<int, array<int, string>> $rows
|
|
* @param array<int, int> $alignment
|
|
*/
|
|
public function table(array $headers, array $rows, array $alignment = []): void
|
|
{
|
|
$lengths = [];
|
|
$alignment = $alignment ?: array_fill(0, count($headers), 0);
|
|
|
|
$filteredHeaders = [];
|
|
foreach ($headers as $key => $header) {
|
|
$filteredHeader = $this->formatTableCell($header);
|
|
$filteredHeaders[$key] = $filteredHeader;
|
|
|
|
$lengths[$key] ??= 0;
|
|
$lengths[$key] = max($lengths[$key], strlen($filteredHeader));
|
|
}
|
|
|
|
$filteredRows = [];
|
|
foreach ($rows as $rowIndex => $row) {
|
|
$filteredRows[$rowIndex] = [];
|
|
foreach ($row as $key => $cell) {
|
|
$filteredCell = $this->formatTableCell($cell);
|
|
$filteredRows[$rowIndex][$key] = $filteredCell;
|
|
$lengths[$key] ??= 0;
|
|
$lengths[$key] = max($lengths[$key], strlen($filteredCell));
|
|
}
|
|
}
|
|
|
|
$this->lines[] = $this->formatTableRow($filteredHeaders, $lengths, $alignment);
|
|
$this->lines[] = $this->formatTableRow(
|
|
array_map(
|
|
static function (int $key) use ($lengths, $alignment): string {
|
|
$length = $lengths[$key];
|
|
|
|
return match ($alignment[$key] ?? 0) {
|
|
-1 => ':' . str_repeat('-', $length - 1),
|
|
1 => str_repeat('-', $length - 1) . ':',
|
|
default => str_repeat('-', $length),
|
|
};
|
|
},
|
|
array_keys($headers),
|
|
),
|
|
$lengths,
|
|
$alignment,
|
|
);
|
|
$this->lines = [
|
|
...$this->lines,
|
|
...array_map(fn(array $row) => $this->formatTableRow($row, $lengths, $alignment), $filteredRows),
|
|
'',
|
|
];
|
|
}
|
|
|
|
public function getIterator(): ArrayIterator
|
|
{
|
|
return new ArrayIterator($this->lines);
|
|
}
|
|
|
|
/** @return array<string> */
|
|
public function toArray(): array
|
|
{
|
|
return $this->lines;
|
|
}
|
|
|
|
private function formatTableCell(string $value): string
|
|
{
|
|
return str_replace('|', '|', $value);
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $row
|
|
* @param array<int, int> $lengths
|
|
* @param array<int, int> $alignment
|
|
*/
|
|
private function formatTableRow(array $row, array $lengths, array $alignment): string
|
|
{
|
|
$cells = [];
|
|
foreach ($row as $key => $cell) {
|
|
$cells[] = match ($alignment[$key] ?? 0) {
|
|
-1 => str_pad($cell, $lengths[$key], ' ', STR_PAD_RIGHT),
|
|
1 => str_pad($cell, $lengths[$key], ' ', STR_PAD_LEFT),
|
|
default => str_pad($cell, $lengths[$key], ' ', STR_PAD_BOTH),
|
|
};
|
|
}
|
|
|
|
return '| ' . implode(' | ', $cells) . ' |';
|
|
}
|
|
}
|