diff --git a/build/gen_stub.php b/build/gen_stub.php index eaf4af0a6ac..2ee52a81443 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1060,7 +1060,7 @@ class FunctionName implements FunctionOrMethodName { } public function getMethodSynopsisFilename(): string { - return implode('_', $this->name->getParts()); + return 'functions/' . implode('/', str_replace('_', '-', $this->name->getParts())); } public function getNameForAttributes(): string { @@ -1105,8 +1105,11 @@ class MethodName implements FunctionOrMethodName { return "arginfo_class_{$this->getDeclarationClassName()}_{$this->methodName}"; } - public function getMethodSynopsisFilename(): string { - return $this->getDeclarationClassName() . "_{$this->methodName}"; + public function getMethodSynopsisFilename(): string + { + $parts = [...$this->className->getParts(), ltrim($this->methodName, '_')]; + /* File paths are in lowercase */ + return strtolower(implode('/', $parts)); } public function getNameForAttributes(): string { @@ -1480,25 +1483,447 @@ class FuncInfo { return $flags; } + private function generateRefSect1(DOMDocument $doc, string $role): DOMElement { + $refSec = $doc->createElement('refsect1'); + $refSec->setAttribute('role', $role); + $refSec->append( + "\n ", + $doc->createEntityReference('reftitle.' . $role), + "\n " + ); + return $refSec; + } + /** * @param array $funcMap * @param array $aliasMap * @throws Exception */ public function getMethodSynopsisDocument(array $funcMap, array $aliasMap): ?string { + $REFSEC1_SEPERATOR = "\n\n "; - $doc = new DOMDocument(); + $doc = new DOMDocument("1.0", "utf-8"); $doc->formatOutput = true; + + $refentry = $doc->createElement('refentry'); + $doc->appendChild($refentry); + + if ($this->isMethod()) { + assert($this->name instanceof MethodName); + /* Namespaces are seperated by '-', '_' must be converted to '-' too. + * Trim away the __ for magic methods */ + $id = strtolower( + str_replace('\\', '-', $this->name->className->__toString()) + . '.' + . str_replace('_', '-', ltrim($this->name->methodName, '_')) + ); + } else { + $id = 'function.' . strtolower(str_replace('_', '-', $this->name->__toString())); + } + $refentry->setAttribute("xml:id", $id); + /* We create an attribute for xmlns, as libxml otherwise force it to be the first one */ + //$refentry->setAttribute("xmlns", "http://docbook.org/ns/docbook"); + $namespace = $doc->createAttribute('xmlns'); + $namespace->value = "http://docbook.org/ns/docbook"; + $refentry->setAttributeNode($namespace); + $refentry->setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); + $refentry->appendChild(new DOMText("\n ")); + + /* Creation of */ + $refnamediv = $doc->createElement('refnamediv'); + $refnamediv->appendChild(new DOMText("\n ")); + $refname = $doc->createElement('refname', $this->name->__toString()); + $refnamediv->appendChild($refname); + $refnamediv->appendChild(new DOMText("\n ")); + $refpurpose = $doc->createElement('refpurpose', 'Description'); + $refnamediv->appendChild($refpurpose); + + $refnamediv->appendChild(new DOMText("\n ")); + $refentry->append($refnamediv, $REFSEC1_SEPERATOR); + + /* Creation of */ + $descriptionRefSec = $this->generateRefSect1($doc, 'description'); + $methodSynopsis = $this->getMethodSynopsisElement($funcMap, $aliasMap, $doc); if (!$methodSynopsis) { return null; } + $descriptionRefSec->appendChild($methodSynopsis); + $descriptionRefSec->appendChild(new DOMText("\n ")); + $undocumentedEntity = $doc->createEntityReference('warn.undocumented.func'); + $descriptionRefSec->appendChild($undocumentedEntity); + $descriptionRefSec->appendChild(new DOMText("\n ")); + $returnDescriptionPara = $doc->createElement('para'); + $returnDescriptionPara->appendChild(new DOMText("\n Description.\n ")); + $descriptionRefSec->appendChild($returnDescriptionPara); - $doc->appendChild($methodSynopsis); + $descriptionRefSec->appendChild(new DOMText("\n ")); + $refentry->append($descriptionRefSec, $REFSEC1_SEPERATOR); + /* Creation of */ + $parametersRefSec = $this->getParameterSection($doc); + $refentry->append($parametersRefSec, $REFSEC1_SEPERATOR); + + /* Creation of */ + if (!$this->name->isConstructor() && !$this->name->isDestructor()) { + $returnRefSec = $this->getReturnValueSection($doc); + $refentry->append($returnRefSec, $REFSEC1_SEPERATOR); + } + + /* Creation of */ + $errorsRefSec = $this->generateRefSect1($doc, 'errors'); + $errorsDescriptionParaConstantTag = $doc->createElement('constant'); + $errorsDescriptionParaConstantTag->append('E_*'); + $errorsDescriptionParaExceptionTag = $doc->createElement('exceptionname'); + $errorsDescriptionParaExceptionTag->append('Exception'); + $errorsDescriptionPara = $doc->createElement('para'); + $errorsDescriptionPara->append( + "\n When does this function issue ", + $errorsDescriptionParaConstantTag, + " level errors,\n and/or throw ", + $errorsDescriptionParaExceptionTag, + "s.\n " + ); + $errorsRefSec->appendChild($errorsDescriptionPara); + $errorsRefSec->appendChild(new DOMText("\n ")); + + $refentry->append($errorsRefSec, $REFSEC1_SEPERATOR); + + /* Creation of */ + $changelogRefSec = $this->getChangelogSection($doc); + $refentry->append($changelogRefSec, $REFSEC1_SEPERATOR); + + $exampleRefSec = $this->getExampleSection($doc, $id); + $refentry->append($exampleRefSec, $REFSEC1_SEPERATOR); + + /* Creation of */ + $notesRefSec = $this->generateRefSect1($doc, 'notes'); + + $noteTagSimara = $doc->createElement('simpara'); + $noteTagSimara->append( + "\n Any notes that don't fit anywhere else should go here.\n " + ); + $noteTag = $doc->createElement('note'); + $noteTag->append("\n ", $noteTagSimara, "\n "); + $notesRefSec->append($noteTag, "\n "); + + $refentry->append($notesRefSec, $REFSEC1_SEPERATOR); + + /* Creation of */ + $seeAlsoRefSec = $this->generateRefSect1($doc, 'seealso'); + + $seeAlsoMemberClassMethod = $doc->createElement('member'); + $seeAlsoMemberClassMethodTag = $doc->createElement('methodname'); + $seeAlsoMemberClassMethodTag->appendChild(new DOMText("ClassName::otherMethodName")); + $seeAlsoMemberClassMethod->appendChild($seeAlsoMemberClassMethodTag); + + $seeAlsoMemberFunction = $doc->createElement('member'); + $seeAlsoMemberFunctionTag = $doc->createElement('function'); + $seeAlsoMemberFunctionTag->appendChild(new DOMText("some_function")); + $seeAlsoMemberFunction->appendChild($seeAlsoMemberFunctionTag); + + $seeAlsoMemberLink = $doc->createElement('member'); + $seeAlsoMemberLinkTag = $doc->createElement('link'); + $seeAlsoMemberLinkTag->setAttribute('linkend', 'some.id.chunk.to.link'); + $seeAlsoMemberLinkTag->appendChild(new DOMText('something appendix')); + $seeAlsoMemberLink->appendChild($seeAlsoMemberLinkTag); + + $seeAlsoList = $doc->createElement('simplelist'); + $seeAlsoList->append( + "\n ", + $seeAlsoMemberClassMethod, + "\n ", + $seeAlsoMemberFunction, + "\n ", + $seeAlsoMemberLink, + "\n " + ); + + $seeAlsoRefSec->appendChild($seeAlsoList); + $seeAlsoRefSec->appendChild(new DOMText("\n ")); + + $refentry->appendChild($seeAlsoRefSec); + + $refentry->appendChild(new DOMText("\n\n")); + + $doc->appendChild(new DOMComment( + <<saveXML(); } + private function getParameterSection(DOMDocument $doc): DOMElement { + $parametersRefSec = $this->generateRefSect1($doc, 'parameters'); + if (empty($this->args)) { + $noParamEntity = $doc->createEntityReference('no.function.parameters'); + $parametersRefSec->appendChild($noParamEntity); + return $parametersRefSec; + } else { + $parametersPara = $doc->createElement('para'); + $parametersRefSec->appendChild($parametersPara); + + $parametersPara->appendChild(new DOMText("\n ")); + $parametersList = $doc->createElement('variablelist'); + $parametersPara->appendChild($parametersList); + + /* + + name + + + Description. + + + + */ + foreach ($this->args as $arg) { + $parameter = $doc->createElement('parameter', $arg->name); + $parameterTerm = $doc->createElement('term'); + $parameterTerm->appendChild($parameter); + + $listItemPara = $doc->createElement('para'); + $listItemPara->append( + "\n ", + "Description.", + "\n ", + ); + + $parameterEntryListItem = $doc->createElement('listitem'); + $parameterEntryListItem->append( + "\n ", + $listItemPara, + "\n ", + ); + + $parameterEntry = $doc->createElement('varlistentry'); + $parameterEntry->append( + "\n ", + $parameterTerm, + "\n ", + $parameterEntryListItem, + "\n ", + ); + + $parametersList->appendChild(new DOMText("\n ")); + $parametersList->appendChild($parameterEntry); + } + $parametersList->appendChild(new DOMText("\n ")); + } + $parametersPara->appendChild(new DOMText("\n ")); + $parametersRefSec->appendChild(new DOMText("\n ")); + return $parametersRefSec; + } + + private function getReturnValueSection(DOMDocument $doc): DOMElement { + $returnRefSec = $this->generateRefSect1($doc, 'returnvalues'); + + $returnDescriptionPara = $doc->createElement('para'); + $returnDescriptionPara->appendChild(new DOMText("\n ")); + + $returnType = $this->return->getMethodSynopsisType(); + if ($returnType === null) { + $returnDescriptionPara->appendChild(new DOMText("Description.")); + } else if (count($returnType->types) === 1) { + $type = $returnType->types[0]; + $name = $type->name; + + switch ($name) { + case 'void': + $descriptionNode = $doc->createEntityReference('return.void'); + break; + case 'true': + $descriptionNode = $doc->createEntityReference('return.true.always'); + break; + case 'bool': + $descriptionNode = $doc->createEntityReference('return.success'); + break; + default: + $descriptionNode = new DOMText("Description."); + break; + } + $returnDescriptionPara->appendChild($descriptionNode); + } else { + $returnDescriptionPara->appendChild(new DOMText("Description.")); + } + $returnDescriptionPara->appendChild(new DOMText("\n ")); + $returnRefSec->appendChild($returnDescriptionPara); + $returnRefSec->appendChild(new DOMText("\n ")); + return $returnRefSec; + } + + /** + * @param array $headers [count($headers) === $columns] + * @param array> $rows [count($rows[$i]) === $columns] + */ + private function generateDocbookInformalTable( + DOMDocument $doc, + int $indent, + int $columns, + array $headers, + array $rows + ): DOMElement { + $strIndent = str_repeat(' ', $indent); + + $headerRow = $doc->createElement('row'); + foreach ($headers as $header) { + $headerEntry = $doc->createElement('entry'); + $headerEntry->appendChild($header); + + $headerRow->append("\n$strIndent ", $headerEntry); + } + $headerRow->append("\n$strIndent "); + + $thead = $doc->createElement('thead'); + $thead->append( + "\n$strIndent ", + $headerRow, + "\n$strIndent ", + ); + + $tbody = $doc->createElement('tbody'); + foreach ($rows as $row) { + $bodyRow = $doc->createElement('row'); + foreach ($row as $cell) { + $entry = $doc->createElement('entry'); + $entry->appendChild($cell); + + $bodyRow->appendChild(new DOMText("\n$strIndent ")); + $bodyRow->appendChild($entry); + } + $bodyRow->appendChild(new DOMText("\n$strIndent ")); + + $tbody->append( + "\n$strIndent ", + $bodyRow, + "\n$strIndent ", + ); + } + + $tgroup = $doc->createElement('tgroup'); + $tgroup->setAttribute('cols', (string) $columns); + $tgroup->append( + "\n$strIndent ", + $thead, + "\n$strIndent ", + $tbody, + "\n$strIndent ", + ); + + $table = $doc->createElement('informaltable'); + $table->append( + "\n$strIndent ", + $tgroup, + "\n$strIndent", + ); + + return $table; + } + + private function getChangelogSection(DOMDocument $doc): DOMElement { + $refSec = $this->generateRefSect1($doc, 'changelog'); + $headers = [ + $doc->createEntityReference('Version'), + $doc->createEntityReference('Description'), + ]; + $rows = [[ + new DOMText('8.X.0'), + new DOMText("\n Description\n "), + ]]; + $table = $this->generateDocbookInformalTable( + $doc, + /* indent: */ 2, + /* columns: */ 2, + /* headers: */ $headers, + /* rows: */ $rows + ); + $refSec->appendChild($table); + + $refSec->appendChild(new DOMText("\n ")); + return $refSec; + } + + private function getExampleSection(DOMDocument $doc, string $id): DOMElement { + $refSec = $this->generateRefSect1($doc, 'examples'); + + $example = $doc->createElement('example'); + $fnName = $this->name->__toString(); + $example->setAttribute('xml:id', $id . '.example.basic'); + + $title = $doc->createElement('title'); + $fn = $doc->createElement($this->isMethod() ? 'methodname' : 'function'); + $fn->append($fnName); + $title->append($fn, ' example'); + + $example->append("\n ", $title); + + $para = $doc->createElement('para'); + $para->append("\n ", "Description.", "\n "); + $example->append("\n ", $para); + + $prog = $doc->createElement('programlisting'); + $prog->setAttribute('role', 'php'); + $code = new DOMCdataSection( + << + +CODE_EXAMPLE + ); + $prog->append("\n"); + $prog->appendChild($code); + $prog->append("\n "); + + $example->append("\n ", $prog); + $example->append("\n ", $doc->createEntityReference('example.outputs')); + + $output = new DOMCdataSection( + <<createElement('screen'); + $screen->append("\n"); + $screen->appendChild($output); + $screen->append("\n "); + + $example->append( + "\n ", + $screen, + "\n ", + ); + + $refSec->append( + $example, + "\n ", + ); + return $refSec; + } + /** * @param array $funcMap * @param array $aliasMap @@ -5155,6 +5580,9 @@ if ($replacePredefinedConstants && $locationCount < 2) { if ($replaceClassSynopses && $locationCount < 2) { die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --replace-classsynopses ./ ../doc-en/\n"); } +if ($generateMethodSynopses && $locationCount < 2) { + die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --generate-methodsynopses ./ ../doc-en/\n"); +} if ($replaceMethodSynopses && $locationCount < 2) { die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --replace-methodsynopses ./ ../doc-en/\n"); } @@ -5162,7 +5590,7 @@ if ($verifyManual && $locationCount < 2) { die("At least one source stub path and a target manual directory has to be provided:\n./build/gen_stub.php --verify-manual ./ ../doc-en/\n"); } $manualTarget = null; -if ($replacePredefinedConstants || $replaceClassSynopses || $replaceMethodSynopses || $verifyManual) { +if ($replacePredefinedConstants || $replaceClassSynopses || $generateMethodSynopses || $replaceMethodSynopses || $verifyManual) { $manualTarget = array_pop($locations); } if ($locations === []) { @@ -5361,16 +5789,14 @@ if ($replaceClassSynopses || $verifyManual) { } if ($generateMethodSynopses) { - $methodSynopsesDirectory = getcwd() . "/methodsynopses"; - $methodSynopses = generateMethodSynopses($funcMap, $aliasMap); - if (!empty($methodSynopses)) { - if (!file_exists($methodSynopsesDirectory)) { - mkdir($methodSynopsesDirectory); - } + if (!file_exists($manualTarget)) { + mkdir($manualTarget); + } - foreach ($methodSynopses as $filename => $content) { - if (file_put_contents("$methodSynopsesDirectory/$filename", $content)) { + foreach ($methodSynopses as $filename => $content) { + if (!file_exists("$manualTarget/$filename")) { + if (file_put_contents("$manualTarget/$filename", $content)) { echo "Saved $filename\n"; } }