diff --git a/src/applications/diviner/publisher/DivinerLivePublisher.php b/src/applications/diviner/publisher/DivinerLivePublisher.php index cc72583817..9e3ea7d1ef 100644 --- a/src/applications/diviner/publisher/DivinerLivePublisher.php +++ b/src/applications/diviner/publisher/DivinerLivePublisher.php @@ -1,156 +1,152 @@ <?php final class DivinerLivePublisher extends DivinerPublisher { private $book; private function loadBook() { if (!$this->book) { $book_name = $this->getConfig('name'); - $book = id(new DivinerLiveBook())->loadOneWhere( - 'name = %s', - $book_name); + $book = id(new DivinerLiveBook())->loadOneWhere('name = %s', $book_name); if (!$book) { $book = id(new DivinerLiveBook()) ->setName($book_name) ->setViewPolicy(PhabricatorPolicies::POLICY_USER) ->save(); } $book->setConfigurationData($this->getConfigurationData())->save(); - $this->book = $book; } + return $this->book; } private function loadSymbolForAtom(DivinerAtom $atom) { $symbol = id(new DivinerAtomQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withBookPHIDs(array($this->loadBook()->getPHID())) ->withTypes(array($atom->getType())) ->withNames(array($atom->getName())) ->withContexts(array($atom->getContext())) ->withIndexes(array($this->getAtomSimilarIndex($atom))) ->withIncludeUndocumentable(true) ->withIncludeGhosts(true) ->executeOne(); if ($symbol) { return $symbol; } return id(new DivinerLiveSymbol()) ->setBookPHID($this->loadBook()->getPHID()) ->setType($atom->getType()) ->setName($atom->getName()) ->setContext($atom->getContext()) ->setAtomIndex($this->getAtomSimilarIndex($atom)); } private function loadAtomStorageForSymbol(DivinerLiveSymbol $symbol) { $storage = id(new DivinerLiveAtom())->loadOneWhere( 'symbolPHID = %s', $symbol->getPHID()); if ($storage) { return $storage; } return id(new DivinerLiveAtom()) ->setSymbolPHID($symbol->getPHID()); } protected function loadAllPublishedHashes() { $symbols = id(new DivinerAtomQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withBookPHIDs(array($this->loadBook()->getPHID())) ->withIncludeUndocumentable(true) ->execute(); return mpull($symbols, 'getGraphHash'); } protected function deleteDocumentsByHash(array $hashes) { $atom_table = new DivinerLiveAtom(); $symbol_table = new DivinerLiveSymbol(); - $conn_w = $symbol_table->establishConnection('w'); $strings = array(); foreach ($hashes as $hash) { $strings[] = qsprintf($conn_w, '%s', $hash); } foreach (PhabricatorLiskDAO::chunkSQL($strings, ', ') as $chunk) { queryfx( $conn_w, 'UPDATE %T SET graphHash = NULL, nodeHash = NULL WHERE graphHash IN (%Q)', $symbol_table->getTableName(), $chunk); } queryfx( $conn_w, 'DELETE a FROM %T a LEFT JOIN %T s ON a.symbolPHID = s.phid WHERE s.graphHash IS NULL', $atom_table->getTableName(), $symbol_table->getTableName()); } protected function createDocumentsByHash(array $hashes) { foreach ($hashes as $hash) { $atom = $this->getAtomFromGraphHash($hash); $ref = $atom->getRef(); $symbol = $this->loadSymbolForAtom($atom); $is_documentable = $this->shouldGenerateDocumentForAtom($atom); $symbol ->setGraphHash($hash) ->setIsDocumentable((int)$is_documentable) ->setTitle($ref->getTitle()) ->setGroupName($ref->getGroup()) ->setNodeHash($atom->getHash()); if ($atom->getType() !== DivinerAtom::TYPE_FILE) { $renderer = $this->getRenderer(); $summary = $renderer->getAtomSummary($atom); $symbol->setSummary($summary); } else { $symbol->setSummary(''); } $symbol->save(); // TODO: We probably need a finer-grained sense of what "documentable" // atoms are. Neither files nor methods are currently considered // documentable, but for different reasons: files appear nowhere, while // methods just don't appear at the top level. These are probably // separate concepts. Since we need atoms in order to build method // documentation, we insert them here. This also means we insert files, // which are unnecessary and unused. Make sure this makes sense, but then // probably introduce separate "isTopLevel" and "isDocumentable" flags? // TODO: Yeah do that soon ^^^ if ($atom->getType() !== DivinerAtom::TYPE_FILE) { $storage = $this->loadAtomStorageForSymbol($symbol) ->setAtomData($atom->toDictionary()) ->setContent(null) ->save(); } } } public function findAtomByRef(DivinerAtomRef $ref) { // TODO: Actually implement this. - return null; } } diff --git a/src/applications/diviner/publisher/DivinerPublisher.php b/src/applications/diviner/publisher/DivinerPublisher.php index 0c2c0b1ad1..b98cec9965 100644 --- a/src/applications/diviner/publisher/DivinerPublisher.php +++ b/src/applications/diviner/publisher/DivinerPublisher.php @@ -1,157 +1,156 @@ <?php abstract class DivinerPublisher { private $atomCache; private $atomGraphHashToNodeHashMap; private $atomMap = array(); private $renderer; private $config; private $symbolReverseMap; private $dropCaches; - public function setDropCaches($drop_caches) { + public final function setDropCaches($drop_caches) { $this->dropCaches = $drop_caches; return $this; } - public function setRenderer(DivinerRenderer $renderer) { + public final function setRenderer(DivinerRenderer $renderer) { $renderer->setPublisher($this); $this->renderer = $renderer; return $this; } - public function getRenderer() { + public final function getRenderer() { return $this->renderer; } - public function setConfig(array $config) { + public final function setConfig(array $config) { $this->config = $config; return $this; } - public function getConfig($key, $default = null) { + public final function getConfig($key, $default = null) { return idx($this->config, $key, $default); } - public function getConfigurationData() { + public final function getConfigurationData() { return $this->config; } - public function setAtomCache(DivinerAtomCache $cache) { + public final function setAtomCache(DivinerAtomCache $cache) { $this->atomCache = $cache; $graph_map = $this->atomCache->getGraphMap(); $this->atomGraphHashToNodeHashMap = array_flip($graph_map); return $this; } - protected function getAtomFromGraphHash($graph_hash) { + protected final function getAtomFromGraphHash($graph_hash) { if (empty($this->atomGraphHashToNodeHashMap[$graph_hash])) { - throw new Exception("No such atom '{$graph_hash}'!"); + throw new Exception(pht("No such atom '%s'!", $graph_hash)); } return $this->getAtomFromNodeHash( $this->atomGraphHashToNodeHashMap[$graph_hash]); } - protected function getAtomFromNodeHash($node_hash) { + protected final function getAtomFromNodeHash($node_hash) { if (empty($this->atomMap[$node_hash])) { $dict = $this->atomCache->getAtom($node_hash); $this->atomMap[$node_hash] = DivinerAtom::newFromDictionary($dict); } return $this->atomMap[$node_hash]; } - protected function getSimilarAtoms(DivinerAtom $atom) { + protected final function getSimilarAtoms(DivinerAtom $atom) { if ($this->symbolReverseMap === null) { $rmap = array(); $smap = $this->atomCache->getSymbolMap(); foreach ($smap as $nhash => $shash) { $rmap[$shash][$nhash] = true; } $this->symbolReverseMap = $rmap; } $shash = $atom->getRef()->toHash(); if (empty($this->symbolReverseMap[$shash])) { - throw new Exception('Atom has no symbol map entry!'); + throw new Exception(pht('Atom has no symbol map entry!')); } $hashes = $this->symbolReverseMap[$shash]; $atoms = array(); foreach ($hashes as $hash => $ignored) { $atoms[] = $this->getAtomFromNodeHash($hash); } $atoms = msort($atoms, 'getSortKey'); return $atoms; } /** * If a book contains multiple definitions of some atom, like some function - * "f()", we assign them an arbitrary (but fairly stable) order and publish - * them as "function/f/1/", "function/f/2/", etc., or similar. + * `f()`, we assign them an arbitrary (but fairly stable) order and publish + * them as `function/f/1/`, `function/f/2/`, etc., or similar. */ - protected function getAtomSimilarIndex(DivinerAtom $atom) { + protected final function getAtomSimilarIndex(DivinerAtom $atom) { $atoms = $this->getSimilarAtoms($atom); if (count($atoms) == 1) { return 0; } $index = 1; foreach ($atoms as $similar_atom) { if ($atom === $similar_atom) { return $index; } $index++; } - throw new Exception('Expected to find atom while disambiguating!'); + throw new Exception(pht('Expected to find atom while disambiguating!')); } - abstract protected function loadAllPublishedHashes(); abstract protected function deleteDocumentsByHash(array $hashes); abstract protected function createDocumentsByHash(array $hashes); abstract public function findAtomByRef(DivinerAtomRef $ref); - final public function publishAtoms(array $hashes) { + public final function publishAtoms(array $hashes) { $existing = $this->loadAllPublishedHashes(); if ($this->dropCaches) { $deleted = $existing; $created = $hashes; } else { $existing_map = array_fill_keys($existing, true); $hashes_map = array_fill_keys($hashes, true); $deleted = array_diff_key($existing_map, $hashes_map); $created = array_diff_key($hashes_map, $existing_map); $deleted = array_keys($deleted); $created = array_keys($created); } echo pht('Deleting %d documents.', count($deleted))."\n"; $this->deleteDocumentsByHash($deleted); echo pht('Creating %d documents.', count($created))."\n"; $this->createDocumentsByHash($created); } - protected function shouldGenerateDocumentForAtom(DivinerAtom $atom) { + protected final function shouldGenerateDocumentForAtom(DivinerAtom $atom) { switch ($atom->getType()) { case DivinerAtom::TYPE_METHOD: case DivinerAtom::TYPE_FILE: return false; case DivinerAtom::TYPE_ARTICLE: default: break; } return true; } } diff --git a/src/applications/diviner/publisher/DivinerStaticPublisher.php b/src/applications/diviner/publisher/DivinerStaticPublisher.php index b45af14349..36d51fcacb 100644 --- a/src/applications/diviner/publisher/DivinerStaticPublisher.php +++ b/src/applications/diviner/publisher/DivinerStaticPublisher.php @@ -1,209 +1,208 @@ <?php final class DivinerStaticPublisher extends DivinerPublisher { private $publishCache; private $atomNameMap; private function getPublishCache() { if (!$this->publishCache) { $dir = implode( DIRECTORY_SEPARATOR, array( $this->getConfig('root'), '.divinercache', $this->getConfig('name'), 'static', )); $this->publishCache = new DivinerPublishCache($dir); } return $this->publishCache; } protected function loadAllPublishedHashes() { return array_keys($this->getPublishCache()->getPathMap()); } protected function deleteDocumentsByHash(array $hashes) { $root = $this->getConfig('root'); $cache = $this->getPublishCache(); foreach ($hashes as $hash) { $paths = $cache->getAtomPathsFromCache($hash); foreach ($paths as $path) { $abs = $root.DIRECTORY_SEPARATOR.$path; Filesystem::remove($abs); // If the parent directory is now empty, clean it up. $dir = dirname($abs); while (true) { if (!Filesystem::isDescendant($dir, $root)) { // Directory is outside of the root. break; } if (Filesystem::listDirectory($dir)) { // Directory is not empty. break; } Filesystem::remove($dir); $dir = dirname($dir); } } $cache->removeAtomPathsFromCache($hash); $cache->deleteAtomFromIndex($hash); } } protected function createDocumentsByHash(array $hashes) { $indexes = array(); - $cache = $this->getPublishCache(); foreach ($hashes as $hash) { $atom = $this->getAtomFromGraphHash($hash); $paths = array(); if ($this->shouldGenerateDocumentForAtom($atom)) { $content = $this->getRenderer()->renderAtom($atom); $this->writeDocument($atom, $content); $paths[] = $this->getAtomRelativePath($atom); if ($this->getAtomSimilarIndex($atom) !== null) { $index = dirname($this->getAtomRelativePath($atom)).'index.html'; $indexes[$index] = $atom; $paths[] = $index; } $this->addAtomToIndex($hash, $atom); } $cache->addAtomPathsToCache($hash, $paths); } foreach ($indexes as $index => $atoms) { // TODO: Publish disambiguation pages. } $this->publishIndex(); - $cache->writePathMap(); $cache->writeIndex(); } private function publishIndex() { $index = $this->getPublishCache()->getIndex(); $refs = array(); + foreach ($index as $hash => $dictionary) { $refs[$hash] = DivinerAtomRef::newFromDictionary($dictionary); } $content = $this->getRenderer()->renderAtomIndex($refs); $path = implode( DIRECTORY_SEPARATOR, array( $this->getConfig('root'), 'docs', $this->getConfig('name'), 'index.html', )); Filesystem::writeFile($path, $content); } public function findAtomByRef(DivinerAtomRef $ref) { if ($ref->getBook() != $this->getConfig('name')) { return null; } if ($this->atomNameMap === null) { $name_map = array(); foreach ($this->getPublishCache()->getIndex() as $hash => $dict) { $name_map[$dict['name']][$hash] = $dict; } $this->atomNameMap = $name_map; } $name = $ref->getName(); if (empty($this->atomNameMap[$name])) { return null; } $candidates = $this->atomNameMap[$name]; foreach ($candidates as $key => $dict) { $candidates[$key] = DivinerAtomRef::newFromDictionary($dict); if ($ref->getType()) { if ($candidates[$key]->getType() != $ref->getType()) { unset($candidates[$key]); } } if ($ref->getContext()) { if ($candidates[$key]->getContext() != $ref->getContext()) { unset($candidates[$key]); } } } // If we have exactly one uniquely identifiable atom, return it. if (count($candidates) == 1) { return $this->getAtomFromNodeHash(last_key($candidates)); } return null; } private function addAtomToIndex($hash, DivinerAtom $atom) { $ref = $atom->getRef(); $ref->setIndex($this->getAtomSimilarIndex($atom)); $ref->setSummary($this->getRenderer()->renderAtomSummary($atom)); $this->getPublishCache()->addAtomToIndex($hash, $ref->toDictionary()); } private function writeDocument(DivinerAtom $atom, $content) { $root = $this->getConfig('root'); $path = $root.DIRECTORY_SEPARATOR.$this->getAtomRelativePath($atom); if (!Filesystem::pathExists($path)) { Filesystem::createDirectory($path, $umask = 0755, $recursive = true); } Filesystem::writeFile($path.'index.html', $content); return $this; } private function getAtomRelativePath(DivinerAtom $atom) { $ref = $atom->getRef(); $book = $ref->getBook(); $type = $ref->getType(); $context = $ref->getContext(); $name = $ref->getName(); $path = array( 'docs', $book, $type, ); if ($context !== null) { $path[] = $context; } $path[] = $name; $index = $this->getAtomSimilarIndex($atom); if ($index !== null) { $path[] = '@'.$index; } $path[] = null; return implode(DIRECTORY_SEPARATOR, $path); } }