From a199571e28deac52aa3da2003ca6b184aa2384f3 Mon Sep 17 00:00:00 2001 From: Caen De Silva Date: Fri, 12 Jul 2024 13:44:01 +0200 Subject: [PATCH] Refactor the Markdown formatter script with Claude Confirmed to have no effect on the results. --- monorepo/scripts/docs/MarkdownFormatter.php | 722 ++++++++++---------- 1 file changed, 378 insertions(+), 344 deletions(-) diff --git a/monorepo/scripts/docs/MarkdownFormatter.php b/monorepo/scripts/docs/MarkdownFormatter.php index a4200fae602..3a34c0e70ec 100644 --- a/monorepo/scripts/docs/MarkdownFormatter.php +++ b/monorepo/scripts/docs/MarkdownFormatter.php @@ -17,8 +17,7 @@ $links = []; $warnings = []; -// Buffer headings so we can check for style -$headings = []; // [filename => [line => heading]] +$headings = []; $checksHeadings = false; $fixesHeadings = false; @@ -38,91 +37,133 @@ public function __construct(string $input, string $filename = 'Input') protected function run(): void { - $stream = $this->input; - $filename = $this->filename; + $text = $this->normalizeLineEndings($this->input); - $text = $stream; + if ($this->isEmptyContent($text)) { + return; + } + + $lines = explode("\n", $text); + $newLines = $this->processLines($lines); + + $this->output = $this->finalizeOutput($newLines); + } + + protected function normalizeLineEndings(string $text): string + { $text = str_replace("\r\n", "\n", $text); - $text = str_replace("\t", ' ', $text); + return str_replace("\t", ' ', $text); + } + + protected function isEmptyContent(string $text): bool + { if (empty(trim($text))) { - // Warn global $warnings; - $warnings[] = "File $filename is empty"; + $warnings[] = "File {$this->filename} is empty"; - return; + return true; } - $lines = explode("\n", $text); - $new_lines = []; + return false; + } - $last_line = ''; - $was_last_line_heading = false; - $is_inside_fenced_code_block = false; - $is_inside_fenced_fenced_code_block = false; + /** + * @param string[] $lines + * @return string[] + */ + protected function processLines(array $lines): array + { + $newLines = []; + $lastLine = ''; + $wasLastLineHeading = false; + $isInsideFencedCodeBlock = false; + $isInsideFencedFencedCodeBlock = false; $firstHeadingLevel = null; + foreach ($lines as $index => $line) { global $linesCounted; $linesCounted++; - /** Normalization */ - - // Remove multiple empty lines - if (trim($line) == '' && trim($last_line) == '') { + if ($this->shouldSkipLine($line, $lastLine)) { continue; } - // Make sure there is a space after headings - if ($was_last_line_heading && trim($line) != '') { - $new_lines[] = ''; - } + $line = $this->processLine($line, $lastLine, $wasLastLineHeading, $isInsideFencedCodeBlock, $isInsideFencedFencedCodeBlock, $firstHeadingLevel, $index); - // Make sure there is exactly one empty line before any heading - if (! $is_inside_fenced_code_block && preg_match('/^#{1,6} /', $line) && trim($last_line) != '') { - $new_lines[] = ''; - } + $newLines[] = $line; + $lastLine = $line; + $wasLastLineHeading = $this->isHeading($line); + $isInsideFencedCodeBlock = $this->updateFencedCodeBlockStatus($line, $isInsideFencedCodeBlock); + $isInsideFencedFencedCodeBlock = $this->updateFencedFencedCodeBlockStatus($line, $isInsideFencedFencedCodeBlock); + $firstHeadingLevel = $this->updateFirstHeadingLevel($line, $firstHeadingLevel, $index); + } - if ($firstHeadingLevel === null && str_starts_with($line, '# ')) { - $firstHeadingLevel = $index; - } + return $newLines; + } - // Check if line is a heading - if (preg_match('/^#{1,6} /', $line)) { - $was_last_line_heading = true; - global $headings; - $headings[$filename][$index + 1] = $line; - } else { - $was_last_line_heading = false; - } + protected function shouldSkipLine(string $line, string $lastLine): bool + { + return trim($line) === '' && trim($lastLine) === ''; + } - // Make sure there is a space before opening a fenced code block (search for ```language) - if (str_starts_with($line, '```') && $line !== '```' && trim($last_line) != '') { - if (! $is_inside_fenced_fenced_code_block) { - $new_lines[] = ''; - } - } + protected function processLine(string $line, string $lastLine, bool $wasLastLineHeading, bool $isInsideFencedCodeBlock, bool $isInsideFencedFencedCodeBlock, ?int $firstHeadingLevel, int $index): string + { + if ($wasLastLineHeading && trim($line) !== '') { + $line = "\n".$line; + } - // Check if line is a fenced code block - if (str_starts_with($line, '``')) { - $is_inside_fenced_code_block = ! $is_inside_fenced_code_block; - } + if (! $isInsideFencedCodeBlock && $this->isHeading($line) && trim($lastLine) !== '') { + $line = "\n".$line; + } - // Check if line is an escaped fenced code block - if (str_starts_with($line, '````')) { - $is_inside_fenced_fenced_code_block = ! $is_inside_fenced_fenced_code_block; - } + if ($this->shouldAddEmptyLineBeforeFencedCodeBlock($line, $lastLine, $isInsideFencedFencedCodeBlock)) { + $line = "\n".$line; + } + + $this->processHeading($line, $index); + + return rtrim($line); + } + + protected function isHeading(string $line): bool + { + return preg_match('/^#{1,6} /', $line) === 1; + } + + protected function updateFencedCodeBlockStatus(string $line, bool $currentStatus): bool + { + return str_starts_with($line, '``') ? ! $currentStatus : $currentStatus; + } + + protected function updateFencedFencedCodeBlockStatus(string $line, bool $currentStatus): bool + { + return str_starts_with($line, '````') ? ! $currentStatus : $currentStatus; + } - // Remove trailing spaces - $line = rtrim($line); + protected function updateFirstHeadingLevel(string $line, ?int $currentLevel, int $index): ?int + { + return $currentLevel === null && str_starts_with($line, '# ') ? $index : $currentLevel; + } - $new_lines[] = $line; - $last_line = $line; + protected function shouldAddEmptyLineBeforeFencedCodeBlock(string $line, string $lastLine, bool $isInsideFencedFencedCodeBlock): bool + { + return str_starts_with($line, '```') && $line !== '```' && trim($lastLine) !== '' && ! $isInsideFencedFencedCodeBlock; + } + + protected function processHeading(string $line, int $index): void + { + if ($this->isHeading($line)) { + global $headings; + $headings[$this->filename][$index + 1] = $line; } + } - $new_content = implode("\n", $new_lines); - $new_content = trim($new_content)."\n"; + protected function finalizeOutput(array $newLines): string + { + $newContent = implode("\n", $newLines); - $this->output = $new_content; + return trim($newContent)."\n"; } public function getOutput(): string @@ -133,11 +174,9 @@ public function getOutput(): string function lint(string $filename): void { - /** Linting */ $text = file_get_contents($filename); if (empty(trim($text))) { - // Warn global $warnings; $warnings[] = "File $filename is empty"; @@ -145,206 +184,241 @@ function lint(string $filename): void } $lines = explode("\n", $text); - $is_inside_fenced_code_block = false; + $isInsideFencedCodeBlock = false; + foreach ($lines as $index => $line) { - // Check if line is a fenced code block - if (str_starts_with($line, '``')) { - $is_inside_fenced_code_block = ! $is_inside_fenced_code_block; - } + $isInsideFencedCodeBlock = processLine($filename, $line, $index, $isInsideFencedCodeBlock); + } +} - // if not inside fenced code block - if (! $is_inside_fenced_code_block) { - // Add any links to buffer, so we can check them later - preg_match_all('/\[([^\[]+)]\((.*)\)/', $line, $matches); - if (count($matches) > 0) { - foreach ($matches[2] as $match) { - // If link is for an anchor, prefix the filename - if (str_starts_with($match, '#')) { - $match = 'ANCHOR_'.basename($filename).$match; - } - - global $links; - $links[] = [ - 'filename' => $filename, - 'line' => $index + 1, - 'link' => $match, - ]; - } - } +function processLine(string $filename, string $line, int $index, bool $isInsideFencedCodeBlock): bool +{ + $isInsideFencedCodeBlock = updateFencedCodeBlockStatus($line, $isInsideFencedCodeBlock); + + if (! $isInsideFencedCodeBlock) { + processLinks($filename, $line, $index); + checkInlineCode($filename, $line, $index); + checkCommandSignatures($filename, $line, $index); + checkLineLengthAndLegacyMarkers($filename, $line, $index); + checkLegacyTerms($filename, $line, $index); + } - // Check for un-backtick-ed inline code - // If line contains $ - if (str_contains($line, '$') && ! str_contains($line, '[Blade]:') && ! str_contains($line, '$ php')) { - // Check character before the $ is not a backtick - $pos = strpos($line, '$'); - if ($pos > 0) { - $charBefore = substr($line, $pos - 1, 1); - if ($charBefore !== '`') { - global $warnings; - $warnings['Inline code'][] = sprintf('Unformatted inline code found in %s:%s', $filename, $index + 1); - } - } - } - // If line contains command - if (str_contains($line, 'php hyde') && ! str_contains($line, '[Blade]:') && ! str_contains($line, '$ php')) { - // Check character before the php hyde is not a backtick - $pos = strpos($line, 'php hyde'); - if ($pos > 0) { - $charBefore = substr($line, $pos - 1, 1); - if ($charBefore !== '`') { - global $warnings; - $warnings['Inline code'][] = sprintf('Unformatted inline command found in %s:%s', $filename, $index + 1); - } - } - } - // If word ends in .php - if (str_contains($line, '.php') && ! str_contains($line, '[Blade]:') && ! str_contains($line, '$ php') && ! str_contains($line, 'http') && ! str_contains(strtolower($line), 'filepath')) { - // Check character after the .php is not a backtick - $pos = strpos($line, '.php'); - if ($pos > 0) { - $charAfter = substr($line, $pos + 4, 1); - if ($charAfter !== '`') { - global $warnings; - $warnings['Inline code'][] = sprintf('Unformatted inline filename found in %s:%s', $filename, $index + 1); - } - } - } + return $isInsideFencedCodeBlock; +} - // If word ends in .json - if (str_contains($line, '.json') && ! str_contains($line, '[Blade]:') && ! str_contains($line, '$ json') && ! str_contains($line, 'http') && ! str_contains(strtolower($line), 'filepath')) { - // Check character after the .json is not a backtick - $pos = strpos($line, '.json'); - if ($pos > 0) { - $charAfter = substr($line, $pos + 5, 1); - if ($charAfter !== '`') { - global $warnings; - $warnings['Inline code'][] = sprintf('Unformatted inline filename found in %s:%s', $filename, $index + 1); - } - } - } - // if word ends with () - if (str_contains($line, '()') && ! str_contains($line, '[Blade]:')) { - // Check character after the () is not a backtick - $pos = strpos($line, '()'); - if ($pos > 0) { - $charAfter = substr($line, $pos + 2, 1); - if ($charAfter !== '`') { - global $warnings; - $warnings['Inline code'][] = sprintf('Unformatted inline function found in %s:%s', $filename, $index + 1); - } - } - } +function updateFencedCodeBlockStatus(string $line, bool $currentStatus): bool +{ + return str_starts_with($line, '``') ? ! $currentStatus : $currentStatus; +} - // Check for invalid command signatures - if (str_contains($line, 'php hyde')) { - // Extract signature from line - $start = strpos($line, 'php hyde'); - $substr = substr($line, $start); - $explode = explode(' ', $substr, 3); - $signature = $explode[0].' '.$explode[1].' '.$explode[2]; - $end = strpos($signature, '`'); - if ($end === false) { - $end = strpos($signature, '<'); - if ($end === false) { - $end = strlen($signature); - } - } - $signature = substr($signature, 0, $end); - $signatures = getSignatures(); - if (! in_array($signature, $signatures)) { - global $warnings; - $warnings['Invalid command signatures'][] = sprintf('Invalid command signature \'%s\' found in %s:%s', $signature, $filename, $index + 1); - } - } +function checkInlineCode(string $filename, string $line, int $index): void +{ + $inlineCodePatterns = [ + '/\$/' => 'Unformatted inline code', + '/php hyde/' => 'Unformatted inline command', + '/\.php/' => 'Unformatted inline filename', + '/\.json/' => 'Unformatted inline filename', + '/\(\)/' => 'Unformatted inline function', + ]; + + foreach ($inlineCodePatterns as $pattern => $warningType) { + if (preg_match($pattern, $line) && ! isExcludedFromInlineCodeCheck($line)) { + global $warnings; + $warnings['Inline code'][] = sprintf('%s found in %s:%s', $warningType, $filename, $index + 1); } + } +} + +function isExcludedFromInlineCodeCheck(string $line): bool +{ + return str_contains($line, '[Blade]:') || str_contains($line, '$ php') || str_contains($line, 'http') || str_contains(strtolower($line), 'filepath'); +} - // Check if line is too long - if (strlen($line) > 120) { +function checkCommandSignatures(string $filename, string $line, int $index): void +{ + if (str_contains($line, 'php hyde')) { + $signature = extractCommandSignature($line); + $signatures = getSignatures(); + if (! in_array($signature, $signatures)) { global $warnings; - // $warnings[] = 'Line '.$linesCounted.' in file '.$filename.' is too long'; + $warnings['Invalid command signatures'][] = sprintf('Invalid command signature \'%s\' found in %s:%s', $signature, $filename, $index + 1); } + } +} - // Warn if documentation contains legacy markers (experimental, beta, etc) - $markers = ['experimental', 'beta', 'alpha', 'v0.']; - foreach ($markers as $marker) { - if (str_contains($line, $marker)) { - global $warnings; - $warnings['Legacy markers'][] = sprintf('Legacy marker found in %s:%s Found "%s"', $filename, $index + 1, $marker); - } +function extractCommandSignature(string $line): string +{ + $start = strpos($line, 'php hyde'); + $substr = substr($line, $start); + $explode = explode(' ', $substr, 3); + $signature = $explode[0].' '.$explode[1].' '.$explode[2]; + $end = strpos($signature, '`') ?: strpos($signature, '<') ?: strlen($signature); + + return substr($signature, 0, $end); +} + +function checkLineLengthAndLegacyMarkers(string $filename, string $line, int $index): void +{ + // Todo: Implement line length check + $markers = ['experimental', 'beta', 'alpha', 'v0.']; + foreach ($markers as $marker) { + if (str_contains($line, $marker)) { + global $warnings; + $warnings['Legacy markers'][] = sprintf('Legacy marker found in %s:%s Found "%s"', $filename, $index + 1, $marker); } + } +} - // Warn when legacy terms are used (for example slug instead of identifier/route key) - $legacyTerms = [ - 'slug' => '"identifier" or "route key"', - 'slugs' => '"identifiers" or "route keys"', - ]; +function checkLegacyTerms(string $filename, string $line, int $index): void +{ + $legacyTerms = [ + 'slug' => '"identifier" or "route key"', + 'slugs' => '"identifiers" or "route keys"', + ]; - foreach ($legacyTerms as $legacyTerm => $newTerm) { - if (str_contains(strtolower($line), $legacyTerm)) { - global $warnings; - $warnings['Legacy terms'][] = sprintf('Legacy term found in %s:%s Found "%s", should be %s', $filename, $index + 1, $legacyTerm, $newTerm); - } + foreach ($legacyTerms as $legacyTerm => $newTerm) { + if (str_contains(strtolower($line), $legacyTerm)) { + global $warnings; + $warnings['Legacy terms'][] = sprintf('Legacy term found in %s:%s Found "%s", should be %s', $filename, $index + 1, $legacyTerm, $newTerm); } } } -function find_markdown_files($dir): array +/** + * @return string[] + */ +function find_markdown_files(string $dir): array { - $markdown_files = []; + $markdownFiles = []; $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)); foreach ($iterator as $file) { - // Skip _data directory if (str_contains($file->getPathname(), '_data')) { continue; } - if ($file->isFile() && strtolower($file->getExtension()) == 'md') { - $markdown_files[] = realpath($file->getPathname()); + if ($file->isFile() && strtolower($file->getExtension()) === 'md') { + $markdownFiles[] = realpath($file->getPathname()); } } - return $markdown_files; + return $markdownFiles; } function handle_file(string $file): void { - echo 'Handling '.$file."\n"; + echo "Handling $file\n"; normalize_lines($file); lint($file); } -function normalize_lines($filename): void +function normalize_lines(string $filename): void { $stream = file_get_contents($filename); $formatter = new MarkdownFormatter($stream, $filename); - $new_content = $formatter->getOutput(); + $newContent = $formatter->getOutput(); - file_put_contents($filename, $new_content); + file_put_contents($filename, $newContent); - if ($new_content !== $stream) { + if ($newContent !== $stream) { global $filesChanged; $filesChanged++; } } -$dir = __DIR__.'/../../../docs'; -$markdownFiles = find_markdown_files($dir); +/** + * @return string[] + */ +function getSignatures(): array +{ + static $signatures = null; -foreach ($markdownFiles as $file) { - handle_file($file); + if ($signatures === null) { + $cache = __DIR__.'/../cache/hyde-signatures.php'; + if (file_exists($cache)) { + $signatures = include $cache; + } else { + $signatures = [ + 'php hyde list', + 'php hyde change:sourceDirectory', + ]; + $commandRaw = shell_exec('cd ../../../ && php hyde list --raw'); + foreach (explode("\n", $commandRaw) as $command) { + $command = Str::before($command, ' '); + $signatures[] = trim('php hyde '.$command); + } + file_put_contents($cache, ' $fileHeadings) { + foreach ($fileHeadings as $heading) { + $headingLevel = substr_count($heading, '#'); + $headingText = trim(str_replace('#', '', $heading)); + + if (str_word_count($headingText) === 1 || str_word_count($headingText) > 5) { + continue; + } + + $expectedCase = $headingLevel < 3 ? Hyde\make_title($headingText) : Str::ucfirst($headingText); + $expectedCase = adjustCaseForSpecialWords($expectedCase); + + if ($headingText !== $expectedCase) { + $caseType = $headingLevel < 3 ? 'title' : 'sentence'; + $warnings['Headings'][] = "Heading '$headingText' should be $caseType case in $filename (expected '$expectedCase')"; + + if ($fixesHeadings) { + fixHeading($filename, $heading, $headingLevel, $expectedCase); + } + } + } + } +} + +function adjustCaseForSpecialWords(string $text): string +{ + $alwaysUppercase = ['PHP', 'HTML', 'CLI', 'API', 'YAML', 'XML', 'RSS', 'HydeKernel', 'GitHub']; + $alwaysLowercase = ['to', 'it']; + + $text = str_ireplace($alwaysUppercase, $alwaysUppercase, $text); + + return str_ireplace($alwaysLowercase, $alwaysLowercase, $text); } +function fixHeading(string $filename, string $heading, int $headingLevel, string $expectedCase): void +{ + $headingHashes = str_repeat('#', $headingLevel); + $newHeading = "$headingHashes $expectedCase"; -// Just to make PhpStorm happy -$links[] = [ - 'filename' => '', - 'line' => 1, - 'link' => '', -]; + $newContent = file_get_contents($filename); + $newContent = str_replace($heading, $newHeading, $newContent); + file_put_contents($filename, $newContent); + + echo "Fixed heading '$heading' to '$newHeading' in $filename\n"; +} + +function processLinks(): void +{ + global $links, $warnings; + + if (empty($links)) { + return; + } -if (count($links) > 0) { $uniqueLinks = []; foreach ($links as $data) { @@ -353,11 +427,7 @@ function normalize_lines($filename): void $line = $data['line']; if (str_starts_with($link, 'http')) { - // Check for outdated links - // laravel.com/docs/9.x - if (str_contains($link, 'laravel.com/docs/9.x')) { - $warnings['Outdated links'][] = "Outdated documentation link to $link found in $filename:$line"; - } + checkOutdatedLink($link, $filename, $line); continue; } @@ -365,143 +435,76 @@ function normalize_lines($filename): void continue; } - // Remove hash for anchors - $link = explode('#', $link)[0]; - // Remove anything before spaces (image alt text) - $link = explode(' ', $link)[0]; - // Trim any non-alphanumeric characters from the end of the link - $link = rtrim($link, '.,;:!?)'); + $link = cleanLink($link); if (! str_starts_with($link, 'ANCHOR_')) { - // Add to new unique array $uniqueLinks[$link] = "$filename:$line"; } } + validateLinks($uniqueLinks); +} + +function checkOutdatedLink(string $link, string $filename, int $line): void +{ + global $warnings; + + if (str_contains($link, 'laravel.com/docs/9.x')) { + $warnings['Outdated links'][] = "Outdated documentation link to $link found in $filename:$line"; + } +} + +function cleanLink(string $link): string +{ + $link = explode('#', $link)[0]; + $link = explode(' ', $link)[0]; + + return rtrim($link, '.,;:!?)'); +} + +function validateLinks(array $uniqueLinks): void +{ + global $warnings; + $base = __DIR__.'/../../../docs'; - // find all directories in the docs folder $directories = array_filter(glob($base.'/*'), 'is_dir'); foreach ($uniqueLinks as $link => $location) { - // Check uses pretty urls if (str_ends_with($link, '.html')) { $warnings['Bad links'][] = "Link to $link in $location should not use .html extension"; continue; } - // Check does not end with .md if (str_ends_with($link, '.md')) { $warnings['Bad links'][] = "Link to $link in $location must not use .md extension"; continue; } - // Check if file exists - if (! file_exists($base.'/'.$link)) { - $hasMatch = false; - foreach ($directories as $directory) { - if (file_exists($directory.'/'.$link.'.md')) { - $hasMatch = true; - break; - } - } - - if (! $hasMatch) { - // Check that link is not for search (dynamic page) - if (! str_contains($link, 'search')) { - $warnings['Broken links'][] = "Broken link to $link found in $location"; - } - } + if (! file_exists($base.'/'.$link) && ! linkExistsInDirectories($link, $directories) && ! str_contains($link, 'search')) { + $warnings['Broken links'][] = "Broken link to $link found in $location"; } } } -function getSignatures(): array +function linkExistsInDirectories(string $link, array $directories): bool { - static $signatures = null; - - if ($signatures === null) { - $cache = __DIR__.'/../cache/hyde-signatures.php'; - if (file_exists($cache)) { - $signatures = include $cache; - } else { - $signatures = [ - // Adds any hidden commands we know exist - 'php hyde list', - 'php hyde change:sourceDirectory', - ]; - $commandRaw = shell_exec('cd ../../../ && php hyde list --raw'); - foreach (explode("\n", $commandRaw) as $command) { - $command = Str::before($command, ' '); - $signatures[] = trim('php hyde '.$command); - } - file_put_contents($cache, ' $fileHeadings) { - $headingLevels = []; - foreach ($fileHeadings as $heading) { - $headingLevel = substr_count($heading, '#'); - $headingLevels[] = $headingLevel; - - // Check for style: 1-2 headings should be title case, 3+ should be sentence case - $headingText = trim(str_replace('#', '', $heading)); - $titleCase = Hyde\make_title($headingText); - $alwaysUppercase = ['PHP', 'HTML', 'CLI', 'API', 'YAML', 'XML', 'RSS', 'HydeKernel', 'GitHub']; - $alwaysLowercase = ['to', 'it']; - $titleCase = str_ireplace($alwaysUppercase, $alwaysUppercase, $titleCase); - $titleCase = str_ireplace($alwaysLowercase, $alwaysLowercase, $titleCase); - - $isTitleCase = $headingText === $titleCase; - $sentenceCase = Str::ucfirst($headingText); - $isSentenceCase = $headingText === $sentenceCase; - - // If it's just one word, or if it's more than 5 words, we can ignore it - $canIgnore = str_word_count($headingText) === 1 || str_word_count($headingText) > 5; - - if ($canIgnore) { - continue; - } - - $something = false; - if ($headingLevel < 3) { - if (! $isTitleCase) { - $warnings['Headings'][] = "Heading '$headingText' should be title case in $filename (expected '$titleCase')"; - } - } else { - if (! $isSentenceCase) { - $warnings['Headings'][] = "Heading '$headingText' should be sentence case in $filename (expected '$sentenceCase')"; - } - } - - if ($fixesHeadings) { - // Replace the heading with the expected case - - $headingHashes = str_repeat('#', $headingLevel); - $useCase = $headingLevel < 3 ? $titleCase : $sentenceCase; - $newHeading = "$headingHashes $useCase"; - - $newContent = file_get_contents($filename); - $newContent = str_replace($heading, $newHeading, $newContent); - file_put_contents($filename, $newContent); +function displayWarnings(): void +{ + global $warnings; - echo "Fixed heading '$headingText' to '$newHeading' in $filename\n"; - } - } + if (empty($warnings)) { + return; } -} -if (count($warnings) > 0) { echo "\n\033[31mWarnings:\033[0m \033[33m".count($warnings, COUNT_RECURSIVE) - count($warnings)." found \033[0m \n"; foreach ($warnings as $type => $messages) { echo "\n\033[33m$type:\033[0m \n"; @@ -511,38 +514,69 @@ function getSignatures(): array } } -$time = round((microtime(true) - $timeStart) * 1000, 2); -$linesTransformed = number_format($linesCounted); -$fileCount = count($markdownFiles); +function displaySummary(): void +{ + global $timeStart, $linesCounted, $markdownFiles, $filesChanged, $warnings; + + $time = round((microtime(true) - $timeStart) * 1000, 2); + $linesTransformed = number_format($linesCounted); + $fileCount = count($markdownFiles); -echo "\n\n\033[32mAll done!\033[0m Formatted, normalized, and validated $linesTransformed lines of Markdown in $fileCount files in {$time}ms\n"; + echo "\n\n\033[32mAll done!\033[0m Formatted, normalized, and validated $linesTransformed lines of Markdown in $fileCount files in {$time}ms\n"; + + if ($filesChanged > 0) { + echo "\n\033[32m$filesChanged files were changed.\033[0m "; + } else { + echo "\n\033[32mNo files were changed.\033[0m "; + } -if ($filesChanged > 0) { - echo "\n\033[32m$filesChanged files were changed.\033[0m "; -} else { - echo "\n\033[32mNo files were changed.\033[0m "; + $warningCount = count($warnings, COUNT_RECURSIVE) - count($warnings); + displayWarningComparison($warningCount); } -$warningCount = count($warnings, COUNT_RECURSIVE) - count($warnings); -if ($warningCount > 0) { - echo sprintf("\033[33m%s %s found.\033[0m", $warningCount, $warningCount === 1 ? 'warning' : 'warnings'); - if (file_exists(__DIR__.'/../cache/last-run-warnings-count.txt')) { - $lastRunWarningsCount = (int) file_get_contents(__DIR__.'/../cache/last-run-warnings-count.txt'); - if ($warningCount < $lastRunWarningsCount) { - echo sprintf(' Good job! You fixed %d %s!', $lastRunWarningsCount - $warningCount, $lastRunWarningsCount - $warningCount === 1 ? 'warning' : 'warnings'); - } elseif ($warningCount > $lastRunWarningsCount) { - echo sprintf(' Uh oh! You introduced %d new %s!', $warningCount - $lastRunWarningsCount, $warningCount - $lastRunWarningsCount === 1 ? 'warning' : 'warnings'); + +function displayWarningComparison(int $warningCount): void +{ + $lastRunWarningsFile = __DIR__.'/../cache/last-run-warnings-count.txt'; + + if ($warningCount > 0) { + echo sprintf("\033[33m%s %s found.\033[0m", $warningCount, $warningCount === 1 ? 'warning' : 'warnings'); + if (file_exists($lastRunWarningsFile)) { + $lastRunWarningsCount = (int) file_get_contents($lastRunWarningsFile); + if ($warningCount < $lastRunWarningsCount) { + echo sprintf(' Good job! You fixed %d %s!', $lastRunWarningsCount - $warningCount, $lastRunWarningsCount - $warningCount === 1 ? 'warning' : 'warnings'); + } elseif ($warningCount > $lastRunWarningsCount) { + echo sprintf(' Uh oh! You introduced %d new %s!', $warningCount - $lastRunWarningsCount, $warningCount - $lastRunWarningsCount === 1 ? 'warning' : 'warnings'); + } } } + file_put_contents($lastRunWarningsFile, $warningCount); + echo "\n"; } -file_put_contents(__DIR__.'/../cache/last-run-warnings-count.txt', $warningCount); -echo "\n"; -// If --git flag is passed, make a git commit -if (isset($argv[1]) && $argv[1] === '--git') { - if ($filesChanged > 0) { - echo "\n\033[33mCommitting changes to git...\033[0m\n"; - passthru('git commit -am "Format Markdown"'); - } else { - echo "\n\033[33mNo changes to commit\033[0m\n"; +function commitChanges(): void +{ + global $filesChanged; + + if (isset($argv[1]) && $argv[1] === '--git') { + if ($filesChanged > 0) { + echo "\n\033[33mCommitting changes to git...\033[0m\n"; + passthru('git commit -am "Format Markdown"'); + } else { + echo "\n\033[33mNo changes to commit\033[0m\n"; + } } } + +// Main execution +$dir = __DIR__.'/../../../docs'; +$markdownFiles = find_markdown_files($dir); + +foreach ($markdownFiles as $file) { + handle_file($file); +} + +processHeadings(); +processLinks(); +displayWarnings(); +displaySummary(); +commitChanges();