From 5b8c9ed8de0f12444794b38e5aa97029b500247f Mon Sep 17 00:00:00 2001 From: Joshua Estes Date: Thu, 3 Oct 2024 12:19:46 -0400 Subject: [PATCH] [Bard] Adding ability to compile into a phar file (#229) --- .gitignore | 1 + src/SonsOfPHP/Bard/bin/compile | 12 +++ src/SonsOfPHP/Bard/src/Compiler.php | 135 ++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100755 src/SonsOfPHP/Bard/bin/compile create mode 100644 src/SonsOfPHP/Bard/src/Compiler.php diff --git a/.gitignore b/.gitignore index d680779d..0aedc477 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ vendor/ /.php-cs-fixer.php /.phpunit.result.cache /.phpunit +bard.phar composer.lock phpunit.xml composer.phar diff --git a/src/SonsOfPHP/Bard/bin/compile b/src/SonsOfPHP/Bard/bin/compile new file mode 100755 index 00000000..c80fa394 --- /dev/null +++ b/src/SonsOfPHP/Bard/bin/compile @@ -0,0 +1,12 @@ +#!/usr/bin/env php +compile(); diff --git a/src/SonsOfPHP/Bard/src/Compiler.php b/src/SonsOfPHP/Bard/src/Compiler.php new file mode 100644 index 00000000..4eb52bd0 --- /dev/null +++ b/src/SonsOfPHP/Bard/src/Compiler.php @@ -0,0 +1,135 @@ + + */ +final class Compiler +{ + public function compile(string $pharFile = 'bard.phar'): void + { + if (file_exists($pharFile)) { + unlink($pharFile); + } + + $phar = new \Phar($pharFile, 0, 'bard.phar'); + $phar->setSignatureAlgorithm(\Phar::SHA512); + $phar->startBuffering(); + + // >>> + // Add source files + $finder = new Finder(); + $finder->files() + ->ignoreVCS(true) + ->name('*.php') + ->notPath('Tests') + ->notName('Compiler.php') + ->in(__DIR__) + ; + + /** @var \Symfony\Component\Finder\SplFileInfo $file */ + foreach ($finder as $file) { + $this->addFile($phar, $file); + } + + // <<< + + // >>> + // Add vendor files + $finder = new Finder(); + $finder->files() + ->ignoreVCS(true) + ->notPath('composer.json') + ->notPath('composer.lock') + ->notPath('phpunit.xml.dist') + ->notPath('*.md') + ->notPath('docs') + ->notPath('Tests') + ->notPath('tests') + ->in(__DIR__ . '/../vendor') + ; + /** @var \Symfony\Component\Finder\SplFileInfo $file */ + foreach ($finder as $file) { + $this->addFile($phar, $file); + } + + // <<< + + $this->addBin($phar); + $this->setStub($phar); + $phar->stopBuffering(); + } + + private function addFile(\Phar $phar, \SplFileInfo $file): void + { + $contents = file_get_contents((string) $file); + $contents = $this->stripeWhitespace($contents); + if ('LICENSE' === $file->getFilename()) { + $contents = "\n" . $contents . "\n"; + } + + $phar->addFromString($this->getLocalName($file), $contents); + } + + private function stripeWhitespace(string $contents): string + { + $output = ''; + foreach (token_get_all($contents) as $token) { + if (is_string($token)) { + $output .= $token; + } elseif (in_array($token[0], [T_COMMENT, T_DOC_COMMENT])) { + $output .= ''; + } elseif (T_WHITESPACE === $token[0]) { + $whitespace = preg_replace('{[ \t]+}', ' ', $token[1]); + $whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace); + $whitespace = preg_replace('{\n +}', "\n", $whitespace); + $whitespace = preg_replace('{\n}', '', $whitespace); + $output .= $whitespace; + } else { + $output .= $token[1]; + } + } + + return $output; + } + + private function getLocalName(\SplFileInfo $file): string + { + $realPath = $file->getRealPath(); + $pathPrefix = dirname(__DIR__, 1) . DIRECTORY_SEPARATOR; + $pos = strpos($realPath, $pathPrefix); + + $relativePath = ($pos !== false) ? substr_replace($realPath, '', $pos, strlen($pathPrefix)) : $realPath; + + return strtr($relativePath, '\\', '/'); + } + + private function addBin(\Phar $phar): void + { + $contents = file_get_contents(__DIR__ . '/../bin/bard'); + $contents = preg_replace('{^#!/usr/bin/env php\s*}', '', $contents); + + $phar->addFromString('bin/bard', $contents); + } + + private function setStub(\Phar $phar): void + { + $phar->setStub($this->getStub()); + } + + private function getStub(): string + { + return <<<'STUB' + #!/usr/bin/env php +