Merge pull request #22186 from eileenmcnaughton/smarty12
[civicrm-core.git] / tools / mixin / bin / mixer
CommitLineData
b233b6ca
TO
1#!/usr/bin/env php
2<?php
3
4require_once dirname(__DIR__) . '/src/Mixlib.php';
5
6function mixlib(): Mixlib {
7 static $mixlib;
8 if ($mixlib === NULL) {
9 $mixlib = new Mixlib();
10 }
11 return $mixlib;
12}
13
14/**
15 * Generate a test extension which uses the given mixins..
16 *
17 * @param array $options
18 * Ex: ['force' => TRUE]
19 * @param string $targetDir
20 * The output directory.
21 * @param mixed ...$mixinNames
22 * List of mixins to include in the generated extension.
23 * @return string
24 */
25function task_create(array $options, string $targetDir, ...$mixinNames) {
26 if (file_exists($targetDir)) {
27 if (!empty($options['force'])) {
28 fprintf(STDOUT, "Remove %s\n", $targetDir);
29 remove_dir($targetDir);
30 }
31 else {
32 throw new \RuntimeException("Cannot overwrite $targetDir");
33 }
34 }
35
36 $mixinNames = resolve_mixin_names($mixinNames);
37 fprintf(STDOUT, "Create %s for %s\n", $targetDir, implode(',', $mixinNames));
38
39 $srcDirs = [];
40 $srcDirs[] = mixer_shimmy_dir();
41 foreach ($mixinNames as $mixinName) {
42 if (is_dir(mixer_mixlib_dir() . "/$mixinName/example")) {
43 $srcDirs[] = mixer_mixlib_dir() . "/$mixinName/example";
44 }
45 }
46 deep_copy($srcDirs, $targetDir);
47
48 $mixins = [];
49 foreach ($mixinNames as $mixinName) {
50 $mixins[$mixinName] = mixlib()->assertValid(mixlib()->get($mixinName));
51 }
52
53 if (empty($options['bare'])) {
54 mkdir("$targetDir/mixin");
55 file_put_contents("$targetDir/mixin/polyfill.php", mixlib()->get('polyfill')['src']);
56 foreach ($mixins as $mixin) {
57 file_put_contents("$targetDir/mixin/{$mixin['mixinName']}@{$mixin['mixinVersion']}.mixin.php", $mixin['src']);
58 }
59 }
60
61 rename(assert_file("$targetDir/info.xml.template"), "$targetDir/info.xml");
62 update_xml("$targetDir/info.xml", function (SimpleXMLElement $info) use ($mixins) {
63 $mixinsXml = $info->addChild('mixins');
64 foreach ($mixins as $mixinName => $mixin) {
65 $mixinsXml->addChild('mixin', $mixin['mixinName'] . '@' . $mixin['mixinVersion']);
66 }
67 if (!empty($options['bare'])) {
68 // If the example doesn't include mixins, then we must get them from elsewhere.
69 $requiresXml = $info->addChild('requires');
70 $requiresXml->addChild('ext', 'mixinlib');
71 }
72 });
73
74 return $targetDir;
75}
76
77/**
78 * Generate and test an extension that uses the given mixins..
79 *
80 * @param array $options
81 * Ex: ['force' => TRUE]
82 * Ex: ['isolate' => TRUE]
83 * @param string $targetDir
84 * The output directory.
85 * @param mixed ...$mixinNames
86 * List of mixins to include in the generated extension.
87 */
88function task_test(array $options, string $targetDir, ...$args) {
89 if (($split = array_search('--', $args)) !== FALSE) {
90 $mixinNames = array_slice($args, 0, $split);
91 $phpunitArgs = array_slice($args, $split + 1);
92 }
93 else {
94 $mixinNames = $args;
95 $phpunitArgs = explode(' ', '--group e2e --debug --stop-on-failure');
96 }
97
98 $mixinNames = resolve_mixin_names($mixinNames);
99 $errors = [];
100 if (!empty($options['isolate']) && count($mixinNames) > 1) {
101 foreach ($mixinNames as $mixinName) {
102 try {
103 task_test($options + ['force' => TRUE], $targetDir, $mixinName, '--', ...$phpunitArgs);
104 }
105 catch (\Throwable $t) {
106 fprintf(STDERR, "Error testing $mixinName\n%s\n\n", $t->getTraceAsString());
107 $errors = [$mixinName];
108 }
109 }
110 if ($errors) {
111 fprintf(STDERR, "Error processing mixins: %s\n", implode(' ', $errors));
112 exit(1);
113 }
114 return;
115 }
116
117 if (is_dir($targetDir) || !empty($options['force'])) {
118 $targetDir = task_create($options, $targetDir, ...$mixinNames);
119 }
120 if (empty(glob("$targetDir/tests/mixin/*.php"))) {
121 fprintf(STDERR, "Skip. No tests found for %s\n", implode(',', $mixinNames));
122 return;
123 }
124 fprintf(STDOUT, "Test %s\n", implode(',', $mixinNames));
125 with_dir($targetDir, function () use ($phpunitArgs) {
126 phpunit($phpunitArgs);
127 });
128}
129
130function task_list(array $options, ...$mixinNames) {
131 $mixinNames = resolve_mixin_names($mixinNames);
132 foreach ($mixinNames as $mixinName) {
133 $mixin = mixlib()->get($mixinName);
134 fprintf(STDOUT, "%-20s %-10s %s\n", $mixin['mixinName'], $mixin['mixinVersion'] ?? '', $mixin['description'] ?? '');
135 }
136}
137
138function task_help(array $options) {
139 $cmd = basename($GLOBALS['argv'][0]);
140 fprintf(STDERR, "%s - Test utility for extension mixins\n", $cmd);
141 fprintf(STDERR, "\n");
142 fprintf(STDERR, "Usage:\n");
143 fprintf(STDERR, " %s create [-f] [--bare] <new-ext-path> [<mixin-name>...]\n", $cmd);
144 fprintf(STDERR, " %s test [-f] [--bare] [--isolate] <new-ext-path> [<mixin-name>...] -- [<phpunit-args>...]\n", $cmd);
145 fprintf(STDERR, " %s list [<mixin-name>...]\n", $cmd);
146}
147
148function mixer_mixlib_dir(): string {
149 return dirname(__DIR__, 3) . '/mixin';
150}
151
152function mixer_shimmy_dir(): string {
153 return dirname(__DIR__, 3) . '/tests/extensions/shimmy';
154}
155
156function assert_dir(string $dir): string {
157 if (!file_exists($dir) || !is_dir($dir)) {
158 throw new \RuntimeException("Directory does not exist ($dir)");
159 }
160 return $dir;
161}
162
163function assert_file(string $file): string {
164 if (!file_exists($file)) {
165 throw new \RuntimeException("File does not exist ($file)");
166 }
167 return $file;
168}
169
170function remove_dir(string $dir): void {
171 if (file_exists($dir)) {
172 passthru_ok("rm -rf " . escapeshellarg($dir));
173 }
174}
175
176function deep_copy(array $srcDirs, string $targetDir): void {
177 $srcDirs = (array) $srcDirs;
178 foreach ($srcDirs as $srcDir) {
179 assert_dir($srcDir);
180 }
181
182 if (!file_exists($targetDir)) {
183 mkdir($targetDir);
184 }
185
186 foreach ($srcDirs as $srcDir) {
187 passthru_ok(sprintf('rsync -a %s/./ %s/./', escapeshellarg($srcDir), escapeshellarg($targetDir)));
188 }
189}
190
191function phpunit(array $args = []) {
192 $argString = implode(' ' , array_map('escapeshellarg', $args));
193 passthru_ok('phpunit8 ' . $argString);
194}
195
196function passthru_ok($cmd) {
197 passthru($cmd, $return);
198 if ($return !== 0) {
199 throw new \RuntimeException("Command failed ($cmd)");
200 }
201}
202
203function resolve_mixin_names(array $mixinNames): array {
204 if (empty($mixinNames)) {
205 return mixlib()->getList();
206 }
207 else {
208 return array_map(
209 function (string $mixinName) {
210 return trim($mixinName, '/' . DIRECTORY_SEPARATOR);
211 }, $mixinNames);
212 }
213}
214
215function with_dir(string $dir, callable $callback) {
216 assert_dir($dir);
217 $orig = getcwd();
218 try {
219 chdir($dir);
220 $callback();
221 } finally {
222 chdir($orig);
223 }
224}
225
226function update_xml(string $file, callable $filter): void {
227 $dom = new DomDocument();
228 $dom->load($file);
229 $dom->xinclude();
230 $simpleXml = simplexml_import_dom($dom);
231 $filter($simpleXml);
232 // force pretty printing with encode/decode cycle
233 $outXML = $simpleXml->saveXML();
234 $xml = new DOMDocument();
235 $xml->encoding = 'iso-8859-1';
236 $xml->preserveWhiteSpace = FALSE;
237 $xml->formatOutput = TRUE;
238 $xml->loadXML($outXML);
239 file_put_contents($file, $xml->saveXML());
240}
241
242function main($args) {
243 $cmd = array_shift($args);
244
245 $isParsingOptions = TRUE;
246 $newOptions = $newArgs = [];
247 $task = NULL;
248 foreach ($args as $arg) {
249 if (!$isParsingOptions) {
250 $newArgs[] = $arg;
251 }
252 elseif ($arg === '--') {
253 $isParsingOptions = FALSE;
254 $newArgs[] = $arg;
255 }
256 elseif ($arg === '-f') {
257 $newOptions['force'] = TRUE;
258 }
259 elseif (preg_match(';^--(bare|isolate|force)$;', $arg, $m)) {
260 $newOptions[$m[1]] = TRUE;
261 }
262 elseif ($task === NULL) {
263 $task = 'task_' . preg_replace(';[^\w];', '_', $arg);
264 }
265 else {
266 $newArgs[] = $arg;
267 }
268 }
269
270 if (function_exists($task)) {
271 call_user_func($task, $newOptions, ...$newArgs);
272 }
273 else {
274 task_help([]);
275 exit(1);
276 }
277}
278
279main($argv);