Skip to content

Commit 5b8c9ed

Browse files
authored
[Bard] Adding ability to compile into a phar file (#229)
1 parent cab5a4f commit 5b8c9ed

3 files changed

Lines changed: 148 additions & 0 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ vendor/
66
/.php-cs-fixer.php
77
/.phpunit.result.cache
88
/.phpunit
9+
bard.phar
910
composer.lock
1011
phpunit.xml
1112
composer.phar

src/SonsOfPHP/Bard/bin/compile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env php
2+
<?php
3+
4+
require __DIR__.'/../vendor/autoload.php';
5+
6+
use SonsOfPHP\Bard\Compiler;
7+
8+
error_reporting(-1);
9+
ini_set('display_errors', '1');
10+
11+
$compiler = new Compiler();
12+
$compiler->compile();
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SonsOfPHP\Bard;
6+
7+
use Symfony\Component\Finder\Finder;
8+
9+
/**
10+
* @author Joshua Estes <joshua@sonsofphp.com>
11+
*/
12+
final class Compiler
13+
{
14+
public function compile(string $pharFile = 'bard.phar'): void
15+
{
16+
if (file_exists($pharFile)) {
17+
unlink($pharFile);
18+
}
19+
20+
$phar = new \Phar($pharFile, 0, 'bard.phar');
21+
$phar->setSignatureAlgorithm(\Phar::SHA512);
22+
$phar->startBuffering();
23+
24+
// >>>
25+
// Add source files
26+
$finder = new Finder();
27+
$finder->files()
28+
->ignoreVCS(true)
29+
->name('*.php')
30+
->notPath('Tests')
31+
->notName('Compiler.php')
32+
->in(__DIR__)
33+
;
34+
35+
/** @var \Symfony\Component\Finder\SplFileInfo $file */
36+
foreach ($finder as $file) {
37+
$this->addFile($phar, $file);
38+
}
39+
40+
// <<<
41+
42+
// >>>
43+
// Add vendor files
44+
$finder = new Finder();
45+
$finder->files()
46+
->ignoreVCS(true)
47+
->notPath('composer.json')
48+
->notPath('composer.lock')
49+
->notPath('phpunit.xml.dist')
50+
->notPath('*.md')
51+
->notPath('docs')
52+
->notPath('Tests')
53+
->notPath('tests')
54+
->in(__DIR__ . '/../vendor')
55+
;
56+
/** @var \Symfony\Component\Finder\SplFileInfo $file */
57+
foreach ($finder as $file) {
58+
$this->addFile($phar, $file);
59+
}
60+
61+
// <<<
62+
63+
$this->addBin($phar);
64+
$this->setStub($phar);
65+
$phar->stopBuffering();
66+
}
67+
68+
private function addFile(\Phar $phar, \SplFileInfo $file): void
69+
{
70+
$contents = file_get_contents((string) $file);
71+
$contents = $this->stripeWhitespace($contents);
72+
if ('LICENSE' === $file->getFilename()) {
73+
$contents = "\n" . $contents . "\n";
74+
}
75+
76+
$phar->addFromString($this->getLocalName($file), $contents);
77+
}
78+
79+
private function stripeWhitespace(string $contents): string
80+
{
81+
$output = '';
82+
foreach (token_get_all($contents) as $token) {
83+
if (is_string($token)) {
84+
$output .= $token;
85+
} elseif (in_array($token[0], [T_COMMENT, T_DOC_COMMENT])) {
86+
$output .= '';
87+
} elseif (T_WHITESPACE === $token[0]) {
88+
$whitespace = preg_replace('{[ \t]+}', ' ', $token[1]);
89+
$whitespace = preg_replace('{(?:\r\n|\r|\n)}', "\n", $whitespace);
90+
$whitespace = preg_replace('{\n +}', "\n", $whitespace);
91+
$whitespace = preg_replace('{\n}', '', $whitespace);
92+
$output .= $whitespace;
93+
} else {
94+
$output .= $token[1];
95+
}
96+
}
97+
98+
return $output;
99+
}
100+
101+
private function getLocalName(\SplFileInfo $file): string
102+
{
103+
$realPath = $file->getRealPath();
104+
$pathPrefix = dirname(__DIR__, 1) . DIRECTORY_SEPARATOR;
105+
$pos = strpos($realPath, $pathPrefix);
106+
107+
$relativePath = ($pos !== false) ? substr_replace($realPath, '', $pos, strlen($pathPrefix)) : $realPath;
108+
109+
return strtr($relativePath, '\\', '/');
110+
}
111+
112+
private function addBin(\Phar $phar): void
113+
{
114+
$contents = file_get_contents(__DIR__ . '/../bin/bard');
115+
$contents = preg_replace('{^#!/usr/bin/env php\s*}', '', $contents);
116+
117+
$phar->addFromString('bin/bard', $contents);
118+
}
119+
120+
private function setStub(\Phar $phar): void
121+
{
122+
$phar->setStub($this->getStub());
123+
}
124+
125+
private function getStub(): string
126+
{
127+
return <<<'STUB'
128+
#!/usr/bin/env php
129+
<?php
130+
Phar::mapPhar('bard.phar');
131+
require 'phar://bard.phar/bin/bard';
132+
__HALT_COMPILER();
133+
STUB;
134+
}
135+
}

0 commit comments

Comments
 (0)