#!/usr/pkg/bin/php83
<?php

function findPhpFiles($dir, &$files = [])
{
    if (substr($dir, -1, 1) !== DIRECTORY_SEPARATOR) {
        $dir .= DIRECTORY_SEPARATOR;
    }
    if ($dh = opendir($dir)) {
        while (($name = readdir($dh)) !== false) {
            $path = $dir . $name;

            if (substr($name, 0, 1) === '.') {
                continue;
            } elseif (is_file($path) && preg_match('~\.ph(p|tml)$~i', $path)) {
                $files[] = $path;
            } elseif (is_dir($path)) {
                findPhpFiles($path, $files);
            }
        }
        closedir($dh);

        return $files;
    } else {
        throw new Exception('Could not read directory: ' . $dir);
    }
}

function stdout($t, $color = '32')
{
    if (posix_isatty(STDOUT) && $color) {
        $t = "\e[${color}m" . $t . "\e[39m";
    }
    fwrite(STDOUT, $t);
}

function stderr($t)
{
    if (posix_isatty(STDERR)) {
        $t = "\e[91m" . $t . "\e[39m";
    }
    fwrite(STDERR, $t);
}

function checkFile($path, &$errors)
{
    $escapedPath = escapeshellarg($path);
    exec("php -l ${escapedPath} 2>&1 >/dev/null", $output, $rc);

    if (! empty($output) || $rc != 0) {
        stdout('E', '91');

        foreach ($output as $line) {
            // remove own name from text
            $line = preg_replace('~ in ' . preg_quote($path) . '~i', '', $line);

            $errors[$path][] = $line;
        }
    } else {
        stdout('.');
    }
}

function usage()
{
    printf("Usage: %s [--verbose] [--exclude file-regex] [path]\n\n", $_SERVER['argv'][0]);
}

function main($argv)
{
    $fileCount = 0;
    $verbose = false;
    $errors = [];
    $excludes = [];
    $searchPaths = [];

    for ($i = 1; $i < count($argv); $i++) {
        $arg = $argv[$i];
        switch ($arg) {
            case '-h':
            case '--help':
                usage();
                return 1;
            case '-v':
            case '--verbose':
                $verbose = true;
                break;
            case '-e':
            case '--exclude':
                $excludes[] = $argv[++$i];
                break;
            default:
                if (substr($arg, 0, 1) === '-') {
                    stderr("Unknown argument: $arg");
                    return 1;
                } else {
                    $searchPaths[] = $arg;
                }
        }
    }

    if (empty($searchPaths)) {
        $searchPaths = ['.'];
    }

    $files = [];
    foreach ($searchPaths as $basePath) {
        findPhpFiles($basePath, $files);
    }

    foreach ($files as $file) {
        foreach ($excludes as $exclude) {
            if (preg_match("~$exclude~", $file)) {
                continue 2;
            }
        }

        $fileCount++;

        if ($verbose) {
            printf("%s\n", $file);
        }
        checkFile($file, $errors);
    }

    $errorCount = count($errors);
    if ($fileCount === 0) {
        stderr("error: No files found!\n");
        return 2;
    } elseif ($errorCount > 0) {
        stdout("\n");

        foreach ($errors as $file => $errList) {
            stderr("\n$file\n    " . join("\n    ", $errList) . "\n");
        }

        stderr(sprintf("\nFound syntax errors in %d of %d files! \n", $errorCount, $fileCount));
        return 1;
    } else {
        stdout(sprintf("\n\nChecked %d files successfully! \n", $fileCount));
        return 0;
    }
}

exit(main($_SERVER['argv']));
