diff --git a/genphdfiles.php b/genphdfiles.php new file mode 100644 index 00000000..3ba8c770 --- /dev/null +++ b/genphdfiles.php @@ -0,0 +1,248 @@ + version banners) + * - sources.xml (xml:id -> {lang, path} map) + * - fileModHistory.php (file modification history) + * + * These were historically produced as a tail step of doc-base/configure.php. + * They are now generated here, from the doc-base/temp/phd-conf.json handoff, + * so that configure.php can focus solely on assembling and validating the XML. + * + * Usage: + * php genphdfiles.php [path/to/phd-conf.json] + * + * The logic mirrors the former configure.php functions verbatim, to keep the + * output byte-identical. + */ + +$confPath = $argv[1] ?? (__DIR__ . '/../doc-base/temp/phd-conf.json'); +if (!is_file($confPath)) { + fwrite(STDERR, "phd-conf.json not found: {$confPath}\n"); + exit(1); +} +$conf = json_decode(file_get_contents($confPath), true); +if (!is_array($conf)) { + fwrite(STDERR, "phd-conf.json is not valid JSON: {$confPath}\n"); + exit(1); +} + +if (!empty($conf['outputs']['history'])) { + phd_history($conf); +} +if (!empty($conf['outputs']['sources'])) { + phd_sources($conf); +} +if (!empty($conf['outputs']['version'])) { + phd_version($conf); +} + +exit(0); + +// ----------------------------------------------------------------------------- + +function phd_history(array $conf): void +{ + echo 'PhD history:'; + + $lang_mod_file = (($conf['lang'] !== 'en') + ? "{$conf['rootdir']}/{$conf['enDir']}" + : "{$conf['rootdir']}/{$conf['langDir']}") . "/fileModHistory.php"; + $doc_base_mod_file = $conf['srcdir'] . "/fileModHistory.php"; + + $history_file = null; + if (file_exists($lang_mod_file)) { + $history_file = include $lang_mod_file; + if (is_array($history_file)) { + echo ' copy,'; + $isFileCopied = copy($lang_mod_file, $doc_base_mod_file); + echo $isFileCopied ? "" : " failed,"; + } else { + echo " corrupted file '$lang_mod_file,'"; + } + } else { + echo " not found,"; + } + + if (!is_array($history_file)) { + $history_file = []; + echo " creating empty,"; + file_put_contents($doc_base_mod_file, " $source_lang, + 'path' => $source_path, + ); + } + } + } + } + asort($source_map); + echo ' transforming,'; + $dom = new DOMDocument; + $dom->formatOutput = true; + $sources_elem = $dom->appendChild($dom->createElement("sources")); + foreach ($source_map as $id => $source) { + $el = $dom->createElement('item'); + $el->setAttribute('id', $id); + $el->setAttribute('lang', $source["lang"]); + $el->setAttribute('path', $source["path"]); + $sources_elem->appendChild($el); + } + echo " saving,"; + if ($dom->save($dest)) { + if ($cacheable) { + copy($dest, $cache); + } + echo " done.\n"; + } else { + echo " fail!\n"; + } +} + +function phd_version(array $conf): void +{ + echo 'PhD version:'; + + $dom = new DOMDocument; + $dom->preserveWhiteSpace = false; + $dom->formatOutput = true; + + $tmp = new DOMDocument; + $tmp->preserveWhiteSpace = false; + + $versions = $dom->appendChild($dom->createElement("versions")); + + echo ' reading,'; + if ($conf["generate"] != "no") { + $globdir = dirname($conf["generate"]) . "/{../../}versions.xml"; + } + else { + $globdir = $conf['rootdir'] . '/en'; + $globdir .= "/*/*/versions.xml"; + } + echo ' transforming,'; + if (!defined('GLOB_BRACE')) { + define('GLOB_BRACE', 0); + } + foreach(glob($globdir, GLOB_BRACE) as $file) { + if($tmp->load($file)) { + foreach($tmp->getElementsByTagName("function") as $function) { + $function = $dom->importNode($function, true); + $versions->appendChild($function); + } + } else { + print_xml_errors($conf); + errors_are_bad(1); + } + } + echo ' saving,'; + + if ($dom->save($conf['srcdir'] . '/version.xml')) { + echo " done.\n"; + } else { + echo " fail!\n"; + } +} + +// ----------------------------------------------------------------------------- +// Helpers ported from doc-base/configure.php +// ----------------------------------------------------------------------------- + +function find_xml_files($path) +{ + $path = rtrim($path, '/'); + $prefix_len = strlen($path . '/'); + $files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)); + foreach ($files as $fileinfo) { + if ($fileinfo->getExtension() === 'xml') { + yield substr($fileinfo->getPathname(), $prefix_len); + } + } +} + +function print_xml_errors(array $conf): void +{ + $report = $conf['lang'] == 'en' || !empty($conf['xpointerReporting']); + $output = !empty($conf['stderrToStdout']) ? STDOUT : STDERR; + + $errors = libxml_get_errors(); + libxml_clear_errors(); + + $filePrefix = "file:///"; + $tempPrefix = realpath($conf['srcdir'] . "/temp") . "/"; + $rootPrefix = realpath($conf['rootdir']) . "/"; + + if (count($errors) > 0) { + fprintf($output, "\n"); + } + + foreach ($errors as $error) { + $mssg = rtrim($error->message); + $file = $error->file; + $line = $error->line; + $clmn = $error->column; + + if (str_starts_with($mssg, 'XPointer evaluation failed:') && !$report) { + continue; + } + + if (str_starts_with($file, $filePrefix)) { + $file = substr($file, strlen($filePrefix)); + } + if (str_starts_with($file, $tempPrefix)) { + $file = substr($file, strlen($tempPrefix)); + } + if (str_starts_with($file, $rootPrefix)) { + $file = substr($file, strlen($rootPrefix)); + } + + $prefix = $error->level === LIBXML_ERR_FATAL ? "FATAL" : "error"; + + fwrite($output, "[$prefix $file {$line}:{$clmn}] {$mssg}\n"); + } +} + +function errors_are_bad($status) +{ + echo "\nEyh man. No worries. Happ shittens. Try again after fixing the errors above.\n"; + exit($status); +} diff --git a/tests/genphdfiles/genphdfiles_001.phpt b/tests/genphdfiles/genphdfiles_001.phpt new file mode 100644 index 00000000..f883fd21 --- /dev/null +++ b/tests/genphdfiles/genphdfiles_001.phpt @@ -0,0 +1,56 @@ +--TEST-- +genphdfiles 001 - generate version.xml, sources.xml and fileModHistory.php (en) +--FILE-- +\n\n"); +file_put_contents("$root/funcindex.xml", + "\n\n"); +file_put_contents("$root/en/reference/foo/versions.xml", + "\n\n \n\n"); +file_put_contents("$root/en/reference/foo/foo.xml", + "\n\n"); + +$conf = [ + 'rootdir' => $root, 'srcdir' => $root, 'lang' => 'en', + 'enDir' => 'en', 'langDir' => 'en', 'generate' => 'no', + 'xpointerReporting' => true, 'stderrToStdout' => false, + 'outputs' => ['version' => true, 'sources' => true, 'history' => true], +]; +file_put_contents("$root/phd-conf.json", json_encode($conf)); + +exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg(__DIR__ . '/../../genphdfiles.php') + . ' ' . escapeshellarg("$root/phd-conf.json"), $out, $rc); + +echo "rc=$rc\n"; +echo "== version.xml ==\n" . file_get_contents("$root/version.xml"); +echo "== sources.xml ==\n" . file_get_contents("$root/sources.xml"); +echo "== fileModHistory.php ==\n" . file_get_contents("$root/fileModHistory.php"); + +$it = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS), + RecursiveIteratorIterator::CHILD_FIRST); +foreach ($it as $f) { $f->isDir() ? rmdir($f) : unlink($f); } +rmdir($root); +?> +--EXPECT-- +rc=0 +== version.xml == + + + + +== sources.xml == + + + + + + +== fileModHistory.php == +\n\n"); +file_put_contents("$root/funcindex.xml", + "\n\n"); +file_put_contents("$root/en/reference/foo/versions.xml", + "\n\n \n\n"); +file_put_contents("$root/en/reference/foo/foo.xml", + "\n\n"); +// The fr translation carries the same xml:id, so it must win in the source map. +file_put_contents("$root/fr/reference/foo/foo.xml", + "\n\n"); + +$conf = [ + 'rootdir' => $root, 'srcdir' => $root, 'lang' => 'fr', + 'enDir' => 'en', 'langDir' => 'fr', 'generate' => 'no', + 'xpointerReporting' => true, 'stderrToStdout' => false, + 'outputs' => ['version' => true, 'sources' => true, 'history' => true], +]; +file_put_contents("$root/phd-conf.json", json_encode($conf)); + +exec(escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg(__DIR__ . '/../../genphdfiles.php') + . ' ' . escapeshellarg("$root/phd-conf.json"), $out, $rc); + +echo "rc=$rc\n"; +echo "== version.xml ==\n" . file_get_contents("$root/version.xml"); +echo "== sources.xml ==\n" . file_get_contents("$root/sources.xml"); + +$it = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS), + RecursiveIteratorIterator::CHILD_FIRST); +foreach ($it as $f) { $f->isDir() ? rmdir($f) : unlink($f); } +rmdir($root); +?> +--EXPECT-- +rc=0 +== version.xml == + + + + +== sources.xml == + + + + + + diff --git a/tests/genphdfiles/genphdfiles_003.phpt b/tests/genphdfiles/genphdfiles_003.phpt new file mode 100644 index 00000000..0bf2fd73 --- /dev/null +++ b/tests/genphdfiles/genphdfiles_003.phpt @@ -0,0 +1,64 @@ +--TEST-- +genphdfiles 003 - sources.xml is cached under temp/ and reused on a second run +--FILE-- +\n\n"); +file_put_contents("$root/funcindex.xml", + "\n\n"); +file_put_contents("$root/en/reference/foo/versions.xml", + "\n\n \n\n"); +file_put_contents("$root/en/reference/foo/foo.xml", + "\n\n"); + +$conf = [ + 'rootdir' => $root, 'srcdir' => $root, 'lang' => 'en', + 'enDir' => 'en', 'langDir' => 'en', 'generate' => 'no', + 'xpointerReporting' => true, 'stderrToStdout' => false, + 'outputs' => ['version' => false, 'sources' => true, 'history' => false], +]; +file_put_contents("$root/phd-conf.json", json_encode($conf)); + +$cmd = escapeshellarg(PHP_BINARY) . ' ' . escapeshellarg(__DIR__ . '/../../genphdfiles.php') + . ' ' . escapeshellarg("$root/phd-conf.json"); + +// First run: cold, scans the sources and stashes the copy in temp/. +exec($cmd, $out1, $rc1); +echo "run1: $rc1 " . implode('', $out1) . "\n"; +echo "cache exists: " . (is_file("$root/temp/phd-sources.xml") ? "yes" : "no") . "\n"; +$first = file_get_contents("$root/sources.xml"); + +// Delete the sources so a scan-based run could not recreate them identically by +// accident, then remove the input files so only the cache can serve run 2. +unlink("$root/sources.xml"); +unlink("$root/en/reference/foo/foo.xml"); + +exec($cmd, $out2, $rc2); +echo "run2: $rc2 " . implode('', $out2) . "\n"; +$second = file_get_contents("$root/sources.xml"); + +echo "identical: " . ($first === $second ? "yes" : "no") . "\n"; +echo "== sources.xml ==\n" . $second; + +$it = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($root, FilesystemIterator::SKIP_DOTS), + RecursiveIteratorIterator::CHILD_FIRST); +foreach ($it as $f) { $f->isDir() ? rmdir($f) : unlink($f); } +rmdir($root); +?> +--EXPECT-- +run1: 0 PhD sources: reading, transforming, saving, done. +cache exists: yes +run2: 0 PhD sources: cached, done. +identical: yes +== sources.xml == + + + + + +