mirror of
https://github.com/php/phd.git
synced 2026-03-24 15:12:18 +01:00
621 lines
19 KiB
PHP
621 lines
19 KiB
PHP
<?php
|
|
namespace phpdotnet\phd;
|
|
|
|
class Options_Handler implements Options_Interface
|
|
{
|
|
public function __construct(
|
|
private Config $config,
|
|
private Format_Factory $formatFactory,
|
|
private OutputHandler $outputHandler
|
|
) {}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public function optionList(): array
|
|
{
|
|
return [
|
|
'format:' => 'f:', // The format to render (xhtml, pdf...)
|
|
'noindex' => 'I', // Do not re-index
|
|
'forceindex' => 'r', // Force re-indexing under all circumstances
|
|
'notoc' => 't', // Do not re-create TOC
|
|
'docbook:' => 'd:', // The Docbook XML file to render from (.manual.xml)
|
|
'output:' => 'o:', // The output directory
|
|
'outputfilename:' => 'F:', // The output filename (only useful for bightml/bigpdf)
|
|
'partial:' => 'p:', // The ID to render (optionally ignoring its children)
|
|
'skip:' => 's:', // The ID to skip (optionally skipping its children too)
|
|
'verbose::' => 'v::', // Adjust the verbosity level
|
|
'list' => 'l', // List supported packages/formats
|
|
'lang:' => 'L:', // Language hint (used by the CHM)
|
|
'color:' => 'c:', // Use color output if possible
|
|
'highlighter:' => 'g:', // Class used as source code highlighter
|
|
'version' => 'V', // Print out version information
|
|
'help' => 'h', // Print out help
|
|
'package:' => 'P:', // The package of formats
|
|
'css:' => 'C:', // External CSS
|
|
'xinclude' => 'x', // Automatically process xinclude directives
|
|
'ext:' => 'e:', // The file-format extension to use, including the dot
|
|
'saveconfig::' => 'S::', // Save the generated config ?
|
|
'quit' => 'Q', // Do not run the render. Use with -S to just save the config.
|
|
'memoryindex' => 'M', // Use sqlite in memory rather then file
|
|
'packagedir:' => 'k:', // Include path for external packages
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, true>
|
|
*/
|
|
public function option_M(string $k, mixed $v): array
|
|
{
|
|
return $this->option_memoryindex($k, $v);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, true>
|
|
*/
|
|
public function option_memoryindex(string $k, mixed $v): array
|
|
{
|
|
return ['memoryIndex' => true];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string|array<string>>
|
|
*/
|
|
public function option_f(string $k, mixed $v): array
|
|
{
|
|
if ($k === "f") {
|
|
return $this->option_format($k, $v);
|
|
}
|
|
return $this->option_outputfilename($k, $v);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, array<string>>
|
|
*/
|
|
public function option_format(string $k, mixed $v): array
|
|
{
|
|
$formats = [];
|
|
foreach((array)$v as $val) {
|
|
if (!in_array($val, $formats)) {
|
|
$formats[] = $val;
|
|
}
|
|
}
|
|
return ['outputFormat' => $formats];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function option_e(string $k, mixed $v): array
|
|
{
|
|
return $this->option_ext($k, $v);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function option_ext(string $k, mixed $v): array
|
|
{
|
|
// `--ext=false`: no extension will be used
|
|
// `--ext=true`: use the default extension
|
|
// `--ext=".foo"`: use the ".foo" extension
|
|
return match (self::boolval($v)) {
|
|
false => ['ext' => ''],
|
|
true => ['ext' => null],
|
|
null => ['ext' => $v]
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public function option_g(string $k, mixed $v): array
|
|
{
|
|
return $this->option_highlighter($k, $v);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public function option_highlighter(string $k, mixed $v): array
|
|
{
|
|
return ['highlighter' => $v];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, true>
|
|
*/
|
|
public function option_i(string $k, mixed $v): array
|
|
{
|
|
return $this->option_noindex($k, $v);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, true>
|
|
*/
|
|
public function option_noindex(string $k, mixed $v): array
|
|
{
|
|
return ['noIndex' => true];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, true>
|
|
*/
|
|
public function option_r(string $k, mixed $v): array
|
|
{
|
|
return $this->option_forceindex($k, $v);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, true>
|
|
*/
|
|
public function option_forceindex(string $k, mixed $v): array
|
|
{
|
|
return ['forceIndex' => true];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, true>
|
|
*/
|
|
public function option_t(string $k, mixed $v): array
|
|
{
|
|
return $this->option_notoc($k, $v);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, true>
|
|
*/
|
|
public function option_notoc(string $k, mixed $v): array
|
|
{
|
|
return ['noToc' => true];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public function option_d(string $k, mixed $v): array
|
|
{
|
|
return $this->option_docbook($k, $v);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public function option_docbook(string $k, mixed $v): array
|
|
{
|
|
if (is_array($v)) {
|
|
trigger_error("Can only parse one file at a time", E_USER_ERROR);
|
|
}
|
|
if (!file_exists($v) || is_dir($v) || !is_readable($v)) {
|
|
trigger_error(sprintf("'%s' is not a readable docbook file", $v), E_USER_ERROR);
|
|
}
|
|
return [
|
|
'xmlRoot' => dirname($v),
|
|
'xmlFile' => $v,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public function option_o(string $k, mixed $v): array
|
|
{
|
|
return $this->option_output($k, $v);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public function option_output(string $k, mixed $v): array
|
|
{
|
|
if (is_array($v)) {
|
|
trigger_error("Only a single output location can be supplied", E_USER_ERROR);
|
|
}
|
|
if (!file_exists($v)) {
|
|
$this->outputHandler->v("Creating output directory..", VERBOSE_MESSAGES);
|
|
if (!mkdir($v, 0777, true)) {
|
|
trigger_error(vsprintf("Can't create output directory : %s", [$v]), E_USER_ERROR);
|
|
}
|
|
$this->outputHandler->v("Output directory created", VERBOSE_MESSAGES);
|
|
} elseif (!is_dir($v)) {
|
|
trigger_error("Output directory is a file?", E_USER_ERROR);
|
|
}
|
|
if (!is_dir($v) || !is_readable($v)) {
|
|
trigger_error(sprintf("'%s' is not a valid directory", $v), E_USER_ERROR);
|
|
}
|
|
$v = (substr($v, strlen($v) - strlen(DIRECTORY_SEPARATOR)) === DIRECTORY_SEPARATOR) ? $v : ($v . DIRECTORY_SEPARATOR);
|
|
|
|
return ['outputDir' => $v];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public function option_outputfilename(string $k, mixed $v): array
|
|
{
|
|
if (is_array($v)) {
|
|
trigger_error("Only a single output location can be supplied", E_USER_ERROR);
|
|
}
|
|
$file = basename($v);
|
|
|
|
return ['outputFilename' => $file];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, array<string>>
|
|
*/
|
|
public function option_p(string $k, mixed $v): array
|
|
{
|
|
if ($k === "P") {
|
|
return $this->option_package($k, $v);
|
|
}
|
|
return $this->option_partial($k, $v);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, array<string>>
|
|
*/
|
|
public function option_partial(string $k, mixed $v): array
|
|
{
|
|
$renderIds = $this->config->renderIds;
|
|
foreach((array)$v as $val) {
|
|
$recursive = true;
|
|
if (strpos($val, "=") !== false) {
|
|
list($val, $recursive) = explode("=", $val);
|
|
|
|
if (!is_numeric($recursive) && defined($recursive)) {
|
|
$recursive = constant($recursive);
|
|
}
|
|
$recursive = (bool) $recursive;
|
|
}
|
|
$renderIds[$val] = $recursive;
|
|
}
|
|
return ['renderIds' => $renderIds];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, array<string>>
|
|
*/
|
|
public function option_package(string $k, mixed $v): array
|
|
{
|
|
foreach((array)$v as $package) {
|
|
if (!in_array($package, $this->config->getSupportedPackages())) {
|
|
$supported = implode(', ', $this->config->getSupportedPackages());
|
|
trigger_error("Invalid Package (Tried: '$package' Supported: '$supported')", E_USER_ERROR);
|
|
}
|
|
}
|
|
return ['package' => (array) $v];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, true>
|
|
*/
|
|
public function option_q(string $k, mixed $v): array
|
|
{
|
|
return $this->option_quit($k, $v);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, true>
|
|
*/
|
|
public function option_quit(string $k, mixed $v): array
|
|
{
|
|
return ['quit' => true];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, bool|array<string>>
|
|
*/
|
|
public function option_s(string $k, mixed $v): array
|
|
{
|
|
if ($k === "S") {
|
|
return $this->option_saveconfig($k, $v);
|
|
}
|
|
return $this->option_skip($k, $v);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, array<string>>
|
|
*/
|
|
public function option_skip(string $k, mixed $v): array
|
|
{
|
|
$skipIds = $this->config->skipIds;
|
|
foreach((array)$v as $val) {
|
|
$recursive = true;
|
|
if (strpos($val, "=") !== false) {
|
|
list($val, $recursive) = explode("=", $val);
|
|
|
|
if (!is_numeric($recursive) && defined($recursive)) {
|
|
$recursive = constant($recursive);
|
|
}
|
|
$recursive = (bool) $recursive;
|
|
}
|
|
$skipIds[$val] = $recursive;
|
|
}
|
|
return ['skipIds' => $skipIds];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, bool>
|
|
*/
|
|
public function option_saveconfig(string $k, mixed $v): array
|
|
{
|
|
if (is_array($v)) {
|
|
trigger_error(sprintf("You cannot pass %s more than once", $k), E_USER_ERROR);
|
|
}
|
|
|
|
$val = is_bool($v) ? true : self::boolval($v);
|
|
|
|
if (!is_bool($val)) {
|
|
trigger_error("yes/no || on/off || true/false || 1/0 expected", E_USER_ERROR);
|
|
}
|
|
|
|
return ['saveConfig' => $val];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, int>
|
|
*/
|
|
public function option_v(string $k, mixed $v): array
|
|
{
|
|
if ($k[0] === 'V') {
|
|
return $this->option_version($k, $v);
|
|
}
|
|
return $this->option_verbose($k, $v);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, int>
|
|
*/
|
|
public function option_verbose(string $k, mixed $v): array
|
|
{
|
|
static $verbose = 0;
|
|
|
|
foreach((array)$v as $val) {
|
|
foreach(explode("|", $val) as $const) {
|
|
if (defined($const)) {
|
|
$verbose |= (int)constant($const);
|
|
} elseif (is_numeric($const)) {
|
|
$verbose |= (int)$const;
|
|
} elseif (empty($const)) {
|
|
$verbose = max($verbose, 1);
|
|
$verbose <<= 1;
|
|
} else {
|
|
trigger_error("Unknown option passed to --$k, '$const'", E_USER_ERROR);
|
|
}
|
|
}
|
|
}
|
|
error_reporting($GLOBALS['olderrrep'] | $verbose);
|
|
return ['verbose' => $verbose];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public function option_l(string $k, mixed $v): array
|
|
{
|
|
if ($k === "L") {
|
|
return $this->option_lang($k, $v);
|
|
}
|
|
return $this->option_list($k, $v);
|
|
}
|
|
|
|
public function option_list(string $k, mixed $v): never
|
|
{
|
|
$packageList = $this->config->getSupportedPackages();
|
|
|
|
echo "Supported packages:\n";
|
|
foreach ($packageList as $package) {
|
|
$formats = $this->formatFactory::createFactory($package)->getOutputFormats();
|
|
echo "\t" . $package . "\n\t\t" . implode("\n\t\t", $formats) . "\n";
|
|
}
|
|
|
|
exit(0);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, string>
|
|
*/
|
|
public function option_lang(string $k, mixed $v): array
|
|
{
|
|
return ['language' => $v];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, bool|array<string>>
|
|
*/
|
|
public function option_c(string $k, mixed $v): array
|
|
{
|
|
if ($k === "C") {
|
|
return $this->option_css($k, $v);
|
|
}
|
|
return $this->option_color($k, $v);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, bool>
|
|
*/
|
|
public function option_color(string $k, mixed $v): array
|
|
{
|
|
if (is_array($v)) {
|
|
trigger_error(sprintf("You cannot pass %s more than once", $k), E_USER_ERROR);
|
|
}
|
|
$val = self::boolval($v);
|
|
if (!is_bool($val)) {
|
|
trigger_error("yes/no || on/off || true/false || 1/0 expected", E_USER_ERROR);
|
|
}
|
|
return ['colorOutput' => $val];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, array<string>>
|
|
*/
|
|
public function option_css(string $k, mixed $v): array
|
|
{
|
|
$styles = [];
|
|
foreach((array)$v as $val) {
|
|
if (!in_array($val, $styles)) {
|
|
$styles[] = $val;
|
|
}
|
|
}
|
|
return ['css' => $styles];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, array<string>>
|
|
*/
|
|
public function option_k(string $k, mixed $v): array
|
|
{
|
|
return $this->option_packagedir($k, $v);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, array<string>>
|
|
*/
|
|
public function option_packagedir(string $k, mixed $v): array
|
|
{
|
|
$packages = $this->config->packageDirs;
|
|
foreach((array)$v as $val) {
|
|
if ($path = realpath($val)) {
|
|
if (!in_array($path, $packages)) {
|
|
$packages[] = $path;
|
|
}
|
|
} else {
|
|
trigger_error(vsprintf('Invalid path: %s', [$val]), E_USER_WARNING);
|
|
}
|
|
}
|
|
return ['packageDirs' => $packages];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, true>
|
|
*/
|
|
public function option_x(string $k, mixed $v): array
|
|
{
|
|
return $this->option_xinclude($k, true);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, true>
|
|
*/
|
|
public function option_xinclude(string $k, mixed $v): array
|
|
{
|
|
return ['processXincludes' => true];
|
|
}
|
|
|
|
/**
|
|
* Prints out the current PhD and PHP version.
|
|
* Exits directly.
|
|
*
|
|
* @return never
|
|
*/
|
|
public function option_version(string $k, mixed $v): never
|
|
{
|
|
$this->outputHandler->printPhdInfo('PhD Version: ' . $this->config::VERSION);
|
|
$packageList = $this->config->getSupportedPackages();
|
|
foreach ($packageList as $package) {
|
|
$version = $this->formatFactory::createFactory($package)->getPackageVersion();
|
|
$this->outputHandler->printPhdInfo("\t$package: $version");
|
|
}
|
|
$this->outputHandler->printPhdInfo('PHP Version: ' . phpversion());
|
|
$this->outputHandler->printPhdInfo($this->config->copyright);
|
|
exit(0);
|
|
}
|
|
|
|
public function option_h(string $k, mixed $v): never
|
|
{
|
|
$this->option_help($k, $v);
|
|
}
|
|
|
|
public function option_help(string $k, mixed $v): never
|
|
{
|
|
echo "PhD version: " .$this->config::VERSION;
|
|
echo "\n" . $this->config->copyright . "\n
|
|
-v
|
|
--verbose <int> Adjusts the verbosity level
|
|
-f <formatname>
|
|
--format <formatname> The build format to use
|
|
-P <packagename>
|
|
--package <packagename> The package to use
|
|
-I
|
|
--noindex Do not index before rendering but load from cache
|
|
(default: " . ($this->config->noIndex ? 'true' : 'false') . ")
|
|
-M
|
|
--memoryindex Do not save indexing into a file, store it in memory.
|
|
(default: " . ($this->config->memoryIndex ? 'true' : 'false') . ")
|
|
-r
|
|
--forceindex Force re-indexing under all circumstances
|
|
(default: " . ($this->config->forceIndex ? 'true' : 'false') . ")
|
|
-t
|
|
--notoc Do not rewrite TOC before rendering but load from
|
|
cache (default: " . ($this->config->noToc ? 'true' : 'false') . ")
|
|
-d <filename>
|
|
--docbook <filename> The Docbook file to render from
|
|
-x
|
|
--xinclude Process XML Inclusions (XInclude)
|
|
(default: " . ($this->config->processXincludes ? 'true' : 'false') . ")
|
|
-p <id[=bool]>
|
|
--partial <id[=bool]> The ID to render, optionally skipping its children
|
|
chunks (default to true; render children)
|
|
-s <id[=bool]>
|
|
--skip <id[=bool]> The ID to skip, optionally skipping its children
|
|
chunks (default to true; skip children)
|
|
-l
|
|
--list Print out the supported packages and formats
|
|
-o <directory>
|
|
--output <directory> The output directory (default: " . $this->config->outputDir . ")
|
|
-F filename
|
|
--outputfilename filename Filename to use when writing standalone formats
|
|
(default: <packagename>-<formatname>.<formatext>)
|
|
-L <language>
|
|
--lang <language> The language of the source file (used by the CHM
|
|
theme). (default: " . $this->config->language . ")
|
|
-c <bool>
|
|
--color <bool> Enable color output when output is to a terminal
|
|
(default: " . ($this->config->getColorOutput() ? 'true' : 'false') . ")
|
|
-C <filename>
|
|
--css <filename> Link for an external CSS file.
|
|
-g <classname>
|
|
--highlighter <classname> Use custom source code highlighting php class
|
|
-V
|
|
--version Print the PhD version information
|
|
-h
|
|
--help This help
|
|
-e <extension>
|
|
--ext <extension> The alternative filename extension to use,
|
|
including the dot. Use 'false' for no extension.
|
|
-S <bool>
|
|
--saveconfig <bool> Save the generated config (default: " . ($this->config->saveConfig ? 'true' : 'false') . ").
|
|
|
|
-Q
|
|
--quit Don't run the build. Use with --saveconfig to
|
|
just save the config (default: " . ($this->config->quit ? 'true' : 'false') . ").
|
|
-k
|
|
--packagedir Use an external package directory.
|
|
|
|
|
|
Most options can be passed multiple times for greater effect.
|
|
";
|
|
exit(0);
|
|
}
|
|
|
|
/**
|
|
* Makes one of the following strings into a boolean:
|
|
* "on", "off", "yes", "no", "false", "true", "0", "1"
|
|
*
|
|
* Returns boolean true/false on success, null on failure
|
|
*/
|
|
private static function boolval(mixed $val): ?bool
|
|
{
|
|
if (!is_string($val)) {
|
|
return null;
|
|
}
|
|
|
|
return match ($val) {
|
|
"on", "yes", "true", "1" => true,
|
|
"off", "no", "false", "0" => false,
|
|
default => null
|
|
};
|
|
}
|
|
}
|