1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
|
<?php
namespace ClassPreloader\Command;
use ClassPreloader\Config;
use ClassPreloader\Parser\DirVisitor;
use ClassPreloader\Parser\NodeTraverser;
use ClassPreloader\Parser\FileVisitor;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Command\Command;
class PreCompileCommand extends Command
{
protected $input;
protected $output;
protected $printer;
protected $traverser;
protected $parser;
public function __construct()
{
parent::__construct();
$this->printer = new \PHPParser_PrettyPrinter_Zend();
$this->parser = new \PHPParser_Parser(new \PHPParser_Lexer());
}
/**
* {@inheritdoc}
*/
protected function configure()
{
parent::configure();
$this->setName('compile')
->setDescription('Compiles classes into a single file')
->addOption('config', null, InputOption::VALUE_REQUIRED, 'CSV of filenames to load, or the path to a PHP script that returns an array of file names')
->addOption('output', null, InputOption::VALUE_REQUIRED)
->addOption('fix_dir', null, InputOption::VALUE_REQUIRED, 'Convert __DIR__ constants to the original directory of a file', 1)
->addOption('fix_file', null, InputOption::VALUE_REQUIRED, 'Convert __FILE__ constants to the original path of a file', 1)
->addOption('strip_comments', null, InputOption::VALUE_REQUIRED, 'Set to 1 to strip comments from each source file', 0)
->setHelp(<<<EOF
The <info>%command.name%</info> command iterates over each script, normalizes
the file to be wrapped in namespaces, and combines each file into a single PHP
file.
EOF
);
}
/**
* Get the node traverser used by the command
*
* @return NodeTraverser
*/
protected function getTraverser()
{
if (!$this->traverser) {
$this->traverser = new NodeTraverser();
if ($this->input->getOption('fix_dir')) {
$this->traverser->addVisitor(new DirVisitor());
}
if ($this->input->getOption('fix_file')) {
$this->traverser->addVisitor(new FileVisitor());
}
}
return $this->traverser;
}
/**
* Get a pretty printed string of code from a file while applying visitors
*
* @param string $file Name of the file to get code from
*
* @return string
* @throws \RuntimeException
*/
protected function getCode($file)
{
if (!is_readable($file)) {
throw new \RuntimeException("Cannot open {$file} for reading");
}
if ($this->input->getOption('strip_comments')) {
$content = php_strip_whitespace($file);
} else {
$content = file_get_contents($file);
}
$stmts = $this->getTraverser()
->traverseFile($this->parser->parse($content), $file);
$pretty = $this->printer->prettyPrint($stmts);
// Remove the open PHP tag
if (substr($pretty, 6) == "<?php\n") {
$pretty = substr($pretty, 7);
}
// Add a wrapping namespace if needed
if (false === strpos($pretty, 'namespace ')) {
$pretty = "namespace {\n" . $pretty . "\n}\n";
}
return $pretty;
}
/**
* Validate the command options
*/
protected function validateCommand()
{
if (!$this->input->getOption('output')) {
throw new \InvalidArgumentException('An output option is required');
}
if (!$this->input->getOption('config')) {
throw new \InvalidArgumentException('A config option is required');
}
}
/**
* Get a list of files in order
*
* @param mixed $config Configuration option
*
* @return array
* @throws \InvalidArgumentException
*/
protected function getFileList($config)
{
$this->output->writeln('> Loading configuration file');
$filesystem = new Filesystem();
if (strpos($config, ',')) {
return array_filter(explode(',', $config));
}
// Ensure absolute paths are resolved
if (!$filesystem->isAbsolutePath($config)) {
$config = getcwd() . '/' . $config;
}
// Ensure that the config file exists
if (!file_exists($config)) {
throw new \InvalidArgumentException(sprintf('Configuration file "%s" does not exist.', $config));
}
$result = require $config;
if ($result instanceof Config) {
foreach ($result->getVisitors() as $visitor) {
$this->getTraverser()->addVisitor($visitor);
}
return $result;
} elseif (is_array($result)) {
return $result;
}
throw new \InvalidArgumentException(
'Config must return an array of filenames or a Config object'
);
}
/**
* Prepare the output file and directory
*
* @param string $outputFile The full path to the output file
*
* @throws \RuntimeException
*/
protected function prepareOutput($outputFile)
{
$dir = dirname($outputFile);
if (!is_dir($dir) && !mkdir($dir, 0777, true)) {
throw new \RuntimeException('Unable to create directory ' . $dir);
}
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->input = $input;
$this->output = $output;
$this->validateCommand();
$outputFile = $this->input->getOption('output');
$config = $this->input->getOption('config');
$files = $this->getFileList($config);
$output->writeLn('- Found ' . count($files) . ' files');
// Make sure that the output dir can be used or create it
$this->prepareOutput($outputFile);
if (!$handle = fopen($input->getOption('output'), 'w')) {
throw new \RuntimeException(
"Unable to open {$outputFile} for writing"
);
}
// Write the first line of the output
fwrite($handle, "<?php\n");
$output->writeln('> Compiling classes');
foreach ($files as $file) {
$this->output->writeln('- Writing ' . $file);
fwrite($handle, $this->getCode($file) . "\n");
}
fclose($handle);
$output->writeln("> Compiled loader written to {$outputFile}");
$output->writeln('- ' . (round(filesize($outputFile) / 1024)) . ' kb');
}
}
|