-
Notifications
You must be signed in to change notification settings - Fork 0
/
Compile.php
289 lines (239 loc) · 10.4 KB
/
Compile.php
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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
<?php namespace NSRosenqvist\Blade\Console\Command;
// Console
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
// Blade
use InvalidArgumentException;
use NSRosenqvist\Blade\Compiler;
class Compile extends Command
{
protected $name = 'compile';
protected $description = 'Compiles a single or multiple Laravel Blade templates';
protected function configure()
{
$this->setName($this->name);
$this->setDescription($this->description);
$this->addArgument(
'template',
InputArgument::REQUIRED | InputArgument::IS_ARRAY,
'The template path can be specified as a relative URI, absolute and also as how Blade natively handles include references (pages/index.blade.php vs pages.index). If supplied as a Blade reference then a base directory must be set'
);
$this->addOption(
'data',
null,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Variables passed on to the template as a JSON file/string or a PHP file returning an associative array'
);
$this->addOption(
'output-dir',
null,
InputOption::VALUE_REQUIRED,
'Output path relative from current working directory or absolute'
);
$this->addOption(
'base-dir',
null,
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
'Base directory to look for template files from. If not set, template\'s containing dir is assumed'
);
$this->addOption(
'output-ext',
null,
InputOption::VALUE_REQUIRED,
'When an output dir is specified you can also set what file extension the compiled template should be created with',
'txt'
);
$this->addOption(
'extend',
null,
InputOption::VALUE_REQUIRED,
'This option accepts a path to a PHP file with user code to extend the compiler by using $compiler->extend()'
);
$this->addOption(
'dynamic-base',
null,
InputOption::VALUE_NONE,
'Automatically add the parent directories of all templates as base directories. This requires a new Blade compiler instance for each template file which adds overhead but simplifies processing multiple templates at once and have each be a self-contained template hierarchy tree. This is not compatible with templates supplied as native Blade references'
);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$templates = $input->getArgument('template');
$dataPaths = $input->getOption('data');
$outputDir = $input->getOption('output-dir');
$outputExt = $input->getOption('output-ext');
$extend = $input->getOption('extend');
$baseDirs = $input->getOption('base-dir');
$dynamicBase = $input->getOption('dynamic-base');
// Validate multi file mode
if (count($templates) > 1) {
// Make sure that an outputDir is set if we're processing multiple templates
if ( ! $outputDir) {
throw new InvalidArgumentException("If you specify more than one template to compile you must also set a target directory with --output-dir");
}
// Make sure we have at least one base directory
if (empty($baseDirs) && ! $dynamicBase) {
$baseDirs[] = getcwd();
}
}
else {
// Validate single file mode
// If no base dir is set we try and get it from the template path
if (empty($baseDirs)) {
$reference = $this->normalizeReference($templates[0], $baseDirs);
// Try and get it from the path
if (file_exists($reference)) {
$baseDirs[] = dirname($reference);
}
else {
// If we can't get it from the path and no base dir is set,
// then most likely the template would be residing in the current
// working directory, so let's use that one.
$baseDirs[] = getcwd();
// If the reference points to a template in a subdir we must add
// that one too
if (strpos($reference, '.') !== false) {
$directory = dirname(str_replace('.', DIRECTORY_SEPARATOR, $reference));
$directory = getcwd().DIRECTORY_SEPARATOR.$directory;
$baseDirs[] = $directory;
}
}
}
}
// Load data file
$data = [];
foreach ($dataPaths as $dataPath) {
$data = array_merge($data, $this->loadData($dataPath));
}
// Create compiler
$cacheDir = sys_get_temp_dir().'/blade/views';
$blade = new Compiler($cacheDir, $baseDirs);
if (file_exists($extend)) {
includeExtensions($blade, $extend);
}
// Loop through all templates
foreach ($templates as $template) {
// Compile template
$reference = $this->normalizeReference($template, $baseDirs);
// If not using dynamic base, use the global compiler
if ( ! $dynamicBase) {
$compiled = $blade->render($reference, $data);
}
else {
// If using a dynamic base, create a new compiler instance for
// each template we're compiling
$dynamicBase = dirname($reference);
$dynamicBase = ($dynamicBase == '.') ? getcwd() : $dynamicBase;
$dynamicDirs = array_merge($baseDirs, [$dynamicBase]);
$bladeDyn = new Compiler($cacheDir, $dynamicDirs);
if (file_exists($extend)) {
includeExtensions($bladeDyn, $extend);
}
$compiled = $blade->render($reference, $data);
}
// Write file
if ($outputDir) {
// If it's a template reference we convert it into a filesystem URI
// so that sub-templates with the same name don't overwrite each other
if ( ! file_exists($reference)) {
$uri = str_replace('.', '/', $template);
$directory = $outputDir.DIRECTORY_SEPARATOR.dirname($uri);
$filename = basename($uri);
$path = $this->outputPath($directory, $filename, $outputExt);
}
else {
// Make sure we keep the file hierarchy
$directory = $outputDir;
if (dirname($template) !== '.') {
$directory .= DIRECTORY_SEPARATOR.dirname($template);
}
$path = $this->outputPath($directory, $reference, $outputExt);
}
// output to file
$this->writeFile($path, $compiled);
}
else {
// Output to stdOut (this should only be possible if we're
// processing only one template since we're throwing an exception
// when $outputDir isn't set in multi file mode)
$output->writeln($compiled);
}
}
}
protected function strEndsWith($haystack, $needle) {
$length = strlen($needle);
if ($length == 0) {
return true;
}
return (substr($haystack, -$length) === $needle);
}
protected function strStartsWith($haystack, $needle) {
$length = strlen($needle);
return (substr($haystack, 0, $length) === $needle);
}
protected function outputPath($directory, $template, $extension) {
// Remove original file extension
$filename = pathinfo($template, PATHINFO_FILENAME);
// Remove .blade as well
if ($this->strEndsWith($filename, '.blade')) {
$filename = pathinfo($filename, PATHINFO_FILENAME);
}
// Build path
$basename = $filename.'.'.$extension;
$path = $directory.DIRECTORY_SEPARATOR.$basename;
return $path;
}
protected function normalizeReference($template, array $baseDirs = []) {
// If it's a real file name and not a template name (pages/index.blade.php vs pages.index)
// It must be converted into an absolute path for the compiler to process it correctly
// due to the nature of how shell scripts usually processes file input from the CLI
// Is it a direct reference?
if (file_exists($template)) {
return realpath($template);
}
// Is it a relative path within one of the base dirs?
foreach ($baseDirs as $base) {
$path = $base.DIRECTORY_SEPARATOR.$template;
if (file_exists($path)) {
return realpath($path);
}
}
// If nothing has been returned yet then it has to be a template name
return $template;
}
protected function loadData($path = null)
{
if ($path) {
// See if it's a real file or a JSON string
if (file_exists($path)) {
$extension = strtolower(pathinfo($path, PATHINFO_EXTENSION));
// Include the file
switch ($extension) {
case 'json': $data = json_decode(file_get_contents($path), true); break;
case 'php': $data = include $path; break;
}
}
// Try parsing JSON string
else {
$data = json_decode($path, true);
}
}
return $data ?? [];
}
protected function writeFile($path, $content) {
// If the directory for the file doesn't exist we create it recursively
if ( ! file_exists(dirname($path))) {
mkdir(dirname($path), 0755, true);
}
// Write file to target directory
if ( ! file_put_contents($path, $content)) {
throw new ErrorException("Failed to write output to file: ".$path);
}
}
}
function includeExtensions(&$compiler, $path) {
include $path;
}