mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-27 07:50:57 +01:00
Modernize Diviner
Summary: Ref T4558. This diff modernizes the #diviner application. Basically: - Add an edit controller, accessible at `/book/$BOOK/edit/`. - Add edit/view policies. - Added an action menu to the `DivinerBookController` to expose the edit interface. - Allows projects to be associated with books. - Implement edges and transactions. - Implemented `PhabricatorApplicationTransactionInterface` in `DivinerLiveBook`. Test Plan: - Generated a Diviner book with `./bin/diviner generate`. - Added projects to a book and ensured that they persisted. - Changed the view policy on a book and made sure it was effective. Reviewers: #blessed_reviewers, epriestley Reviewed By: #blessed_reviewers, epriestley Subscribers: Korvin, epriestley Maniphest Tasks: T4558 Differential Revision: https://secure.phabricator.com/D13091
This commit is contained in:
parent
c5c5523c1f
commit
6b7d7401ca
37 changed files with 601 additions and 151 deletions
17
resources/sql/autopatches/20150605.diviner.edges.sql
Normal file
17
resources/sql/autopatches/20150605.diviner.edges.sql
Normal file
|
@ -0,0 +1,17 @@
|
|||
CREATE TABLE {$NAMESPACE}_diviner.edge (
|
||||
src VARBINARY(64) NOT NULL,
|
||||
type INT UNSIGNED NOT NULL,
|
||||
dst VARBINARY(64) NOT NULL,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
seq INT UNSIGNED NOT NULL,
|
||||
dataID INT UNSIGNED,
|
||||
|
||||
PRIMARY KEY (src, type, dst),
|
||||
KEY src (src, type, dateCreated, seq),
|
||||
UNIQUE KEY key_dst (dst, type, src)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
||||
|
||||
CREATE TABLE {$NAMESPACE}_diviner.edgedata (
|
||||
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
|
||||
data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -0,0 +1,6 @@
|
|||
ALTER TABLE {$NAMESPACE}_diviner.diviner_livebook
|
||||
ADD COLUMN editPolicy VARBINARY(64) NOT NULL AFTER viewPolicy;
|
||||
|
||||
UPDATE {$NAMESPACE}_diviner.diviner_livebook
|
||||
SET editPolicy = 'admin'
|
||||
WHERE editPolicy = '';
|
19
resources/sql/autopatches/20150605.diviner.xaction.sql
Normal file
19
resources/sql/autopatches/20150605.diviner.xaction.sql
Normal file
|
@ -0,0 +1,19 @@
|
|||
CREATE TABLE {$NAMESPACE}_diviner.diviner_livebooktransaction (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARBINARY(64) NOT NULL,
|
||||
authorPHID VARBINARY(64) NOT NULL,
|
||||
objectPHID VARBINARY(64) NOT NULL,
|
||||
viewPolicy VARBINARY(64) NOT NULL,
|
||||
editPolicy VARBINARY(64) NOT NULL,
|
||||
commentPHID VARBINARY(64) DEFAULT NULL,
|
||||
commentVersion INT UNSIGNED NOT NULL,
|
||||
transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
UNIQUE KEY key_phid (phid),
|
||||
KEY key_object (objectPHID)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -649,19 +649,25 @@ phutil_register_library_map(array(
|
|||
'DivinerAtomizeWorkflow' => 'applications/diviner/workflow/DivinerAtomizeWorkflow.php',
|
||||
'DivinerAtomizer' => 'applications/diviner/atomizer/DivinerAtomizer.php',
|
||||
'DivinerBookController' => 'applications/diviner/controller/DivinerBookController.php',
|
||||
'DivinerBookEditController' => 'applications/diviner/controller/DivinerBookEditController.php',
|
||||
'DivinerBookItemView' => 'applications/diviner/view/DivinerBookItemView.php',
|
||||
'DivinerBookPHIDType' => 'applications/diviner/phid/DivinerBookPHIDType.php',
|
||||
'DivinerBookQuery' => 'applications/diviner/query/DivinerBookQuery.php',
|
||||
'DivinerBookSearchIndexer' => 'applications/diviner/search/DivinerBookSearchIndexer.php',
|
||||
'DivinerController' => 'applications/diviner/controller/DivinerController.php',
|
||||
'DivinerDAO' => 'applications/diviner/storage/DivinerDAO.php',
|
||||
'DivinerDefaultEditCapability' => 'applications/diviner/capability/DivinerDefaultEditCapability.php',
|
||||
'DivinerDefaultRenderer' => 'applications/diviner/renderer/DivinerDefaultRenderer.php',
|
||||
'DivinerDefaultViewCapability' => 'applications/diviner/capability/DivinerDefaultViewCapability.php',
|
||||
'DivinerDiskCache' => 'applications/diviner/cache/DivinerDiskCache.php',
|
||||
'DivinerFileAtomizer' => 'applications/diviner/atomizer/DivinerFileAtomizer.php',
|
||||
'DivinerFindController' => 'applications/diviner/controller/DivinerFindController.php',
|
||||
'DivinerGenerateWorkflow' => 'applications/diviner/workflow/DivinerGenerateWorkflow.php',
|
||||
'DivinerLiveAtom' => 'applications/diviner/storage/DivinerLiveAtom.php',
|
||||
'DivinerLiveBook' => 'applications/diviner/storage/DivinerLiveBook.php',
|
||||
'DivinerLiveBookEditor' => 'applications/diviner/editor/DivinerLiveBookEditor.php',
|
||||
'DivinerLiveBookTransaction' => 'applications/diviner/storage/DivinerLiveBookTransaction.php',
|
||||
'DivinerLiveBookTransactionQuery' => 'applications/diviner/query/DivinerLiveBookTransactionQuery.php',
|
||||
'DivinerLivePublisher' => 'applications/diviner/publisher/DivinerLivePublisher.php',
|
||||
'DivinerLiveSymbol' => 'applications/diviner/storage/DivinerLiveSymbol.php',
|
||||
'DivinerMainController' => 'applications/diviner/controller/DivinerMainController.php',
|
||||
|
@ -671,6 +677,7 @@ phutil_register_library_map(array(
|
|||
'DivinerPublisher' => 'applications/diviner/publisher/DivinerPublisher.php',
|
||||
'DivinerRenderer' => 'applications/diviner/renderer/DivinerRenderer.php',
|
||||
'DivinerReturnTableView' => 'applications/diviner/view/DivinerReturnTableView.php',
|
||||
'DivinerSchemaSpec' => 'applications/diviner/storage/DivinerSchemaSpec.php',
|
||||
'DivinerSectionView' => 'applications/diviner/view/DivinerSectionView.php',
|
||||
'DivinerStaticPublisher' => 'applications/diviner/publisher/DivinerStaticPublisher.php',
|
||||
'DivinerSymbolRemarkupRule' => 'applications/diviner/markup/DivinerSymbolRemarkupRule.php',
|
||||
|
@ -4010,13 +4017,16 @@ phutil_register_library_map(array(
|
|||
'DivinerAtomizeWorkflow' => 'DivinerWorkflow',
|
||||
'DivinerAtomizer' => 'Phobject',
|
||||
'DivinerBookController' => 'DivinerController',
|
||||
'DivinerBookEditController' => 'DivinerController',
|
||||
'DivinerBookItemView' => 'AphrontTagView',
|
||||
'DivinerBookPHIDType' => 'PhabricatorPHIDType',
|
||||
'DivinerBookQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'DivinerBookSearchIndexer' => 'PhabricatorSearchDocumentIndexer',
|
||||
'DivinerController' => 'PhabricatorController',
|
||||
'DivinerDAO' => 'PhabricatorLiskDAO',
|
||||
'DivinerDefaultEditCapability' => 'PhabricatorPolicyCapability',
|
||||
'DivinerDefaultRenderer' => 'DivinerRenderer',
|
||||
'DivinerDefaultViewCapability' => 'PhabricatorPolicyCapability',
|
||||
'DivinerDiskCache' => 'Phobject',
|
||||
'DivinerFileAtomizer' => 'DivinerAtomizer',
|
||||
'DivinerFindController' => 'DivinerController',
|
||||
|
@ -4025,8 +4035,13 @@ phutil_register_library_map(array(
|
|||
'DivinerLiveBook' => array(
|
||||
'DivinerDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
'PhabricatorProjectInterface',
|
||||
'PhabricatorDestructibleInterface',
|
||||
'PhabricatorApplicationTransactionInterface',
|
||||
),
|
||||
'DivinerLiveBookEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'DivinerLiveBookTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'DivinerLiveBookTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'DivinerLivePublisher' => 'DivinerPublisher',
|
||||
'DivinerLiveSymbol' => array(
|
||||
'DivinerDAO',
|
||||
|
@ -4041,6 +4056,7 @@ phutil_register_library_map(array(
|
|||
'DivinerPublisher' => 'Phobject',
|
||||
'DivinerRenderer' => 'Phobject',
|
||||
'DivinerReturnTableView' => 'AphrontTagView',
|
||||
'DivinerSchemaSpec' => 'PhabricatorConfigSchemaSpec',
|
||||
'DivinerSectionView' => 'AphrontTagView',
|
||||
'DivinerStaticPublisher' => 'DivinerPublisher',
|
||||
'DivinerSymbolRemarkupRule' => 'PhutilRemarkupRule',
|
||||
|
|
|
@ -39,6 +39,7 @@ final class PhabricatorDivinerApplication extends PhabricatorApplication {
|
|||
'find/' => 'DivinerFindController',
|
||||
),
|
||||
'/book/(?P<book>[^/]+)/' => 'DivinerBookController',
|
||||
'/book/(?P<book>[^/]+)/edit/' => 'DivinerBookEditController',
|
||||
'/book/'.
|
||||
'(?P<book>[^/]+)/'.
|
||||
'(?P<type>[^/]+)/'.
|
||||
|
@ -52,6 +53,18 @@ final class PhabricatorDivinerApplication extends PhabricatorApplication {
|
|||
return self::GROUP_UTILITIES;
|
||||
}
|
||||
|
||||
protected function getCustomCapabilities() {
|
||||
return array(
|
||||
DivinerDefaultViewCapability::CAPABILITY => array(
|
||||
'template' => DivinerBookPHIDType::TYPECONST,
|
||||
),
|
||||
DivinerDefaultEditCapability::CAPABILITY => array(
|
||||
'default' => PhabricatorPolicies::POLICY_ADMIN,
|
||||
'template' => DivinerBookPHIDType::TYPECONST,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
public function getRemarkupRules() {
|
||||
return array(
|
||||
new DivinerSymbolRemarkupRule(),
|
||||
|
|
|
@ -20,7 +20,7 @@ final class DivinerArticleAtomizer extends DivinerAtomizer {
|
|||
$atom->setDocblockMetaValue('title', $title);
|
||||
}
|
||||
|
||||
// If the article has no @name, use the filename after stripping any
|
||||
// If the article has no `@name`, use the filename after stripping any
|
||||
// extension.
|
||||
$name = idx($meta, 'name');
|
||||
if (!$name) {
|
||||
|
|
|
@ -151,9 +151,9 @@ final class DivinerPHPAtomizer extends DivinerAtomizer {
|
|||
if (count($docs) < count($params)) {
|
||||
$atom->addWarning(
|
||||
pht(
|
||||
'This call takes %d parameters, but only %d are documented.',
|
||||
count($params),
|
||||
count($docs)));
|
||||
'This call takes %s parameter(s), but only %s are documented.',
|
||||
new PhutilNumber(count($params)),
|
||||
new PhutilNumber(count($docs))));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,13 +212,13 @@ final class DivinerPHPAtomizer extends DivinerAtomizer {
|
|||
if (preg_match('/@(return|param|task|author)/', $value, $matches)) {
|
||||
$atom->addWarning(
|
||||
pht(
|
||||
'Atom "%s" is preceded by a comment containing "@%s", but the '.
|
||||
'comment is not a documentation comment. Documentation '.
|
||||
'comments must begin with "%s", followed by a newline. Did '.
|
||||
'Atom "%s" is preceded by a comment containing `%s`, but '.
|
||||
'the comment is not a documentation comment. Documentation '.
|
||||
'comments must begin with `%s`, followed by a newline. Did '.
|
||||
'you mean to use a documentation comment? (As the comment is '.
|
||||
'not a documentation comment, it will be ignored.)',
|
||||
$atom->getName(),
|
||||
$matches[1],
|
||||
'@'.$matches[1],
|
||||
'/**'));
|
||||
}
|
||||
}
|
||||
|
@ -248,8 +248,8 @@ final class DivinerPHPAtomizer extends DivinerAtomizer {
|
|||
if ($matches[1] !== $name) {
|
||||
$atom->addWarning(
|
||||
pht(
|
||||
'Parameter "%s" is named "%s" in the documentation. The '.
|
||||
'documentation may be out of date.',
|
||||
'Parameter "%s" is named "%s" in the documentation. '.
|
||||
'The documentation may be out of date.',
|
||||
$name,
|
||||
$matches[1]));
|
||||
}
|
||||
|
@ -292,8 +292,8 @@ final class DivinerPHPAtomizer extends DivinerAtomizer {
|
|||
if ($return) {
|
||||
$atom->addWarning(
|
||||
pht(
|
||||
'Method %s has explicitly documented %s. The %s method always '.
|
||||
'returns %s. Diviner documents this implicitly.',
|
||||
'Method `%s` has explicitly documented `%s`. The `%s` method '.
|
||||
'always returns `%s`. Diviner documents this implicitly.',
|
||||
'__construct()',
|
||||
'@return',
|
||||
'__construct()',
|
||||
|
|
|
@ -18,6 +18,7 @@ final class DivinerAtomCache extends DivinerDiskCache {
|
|||
|
||||
public function delete() {
|
||||
parent::delete();
|
||||
|
||||
$this->fileHashMap = null;
|
||||
$this->atomMap = null;
|
||||
$this->atoms = array();
|
||||
|
|
|
@ -26,8 +26,8 @@ abstract class DivinerDiskCache extends Phobject {
|
|||
* Convert a long-form hash key like `ccbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaN` into
|
||||
* a shortened directory form, like `cc/bb/aaaaaaaaN`. In conjunction with
|
||||
* @{class:PhutilDirectoryKeyValueCache}, this gives us nice directories
|
||||
* inside .divinercache instead of a million hash files with huge names at
|
||||
* top level.
|
||||
* inside `.divinercache` instead of a million hash files with huge names at
|
||||
* the top level.
|
||||
*/
|
||||
protected function getHashKey($hash) {
|
||||
return implode(
|
||||
|
|
|
@ -45,6 +45,7 @@ final class DivinerPublishCache extends DivinerDiskCache {
|
|||
|
||||
/* -( Index )-------------------------------------------------------------- */
|
||||
|
||||
|
||||
public function getIndex() {
|
||||
if ($this->index === null) {
|
||||
$this->index = $this->getCache()->getKey('index', array());
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
final class DivinerDefaultEditCapability extends PhabricatorPolicyCapability {
|
||||
|
||||
const CAPABILITY = 'diviner.default.edit';
|
||||
|
||||
public function getCapabilityName() {
|
||||
return pht('Default Edit Policy');
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
final class DivinerDefaultViewCapability extends PhabricatorPolicyCapability {
|
||||
|
||||
const CAPABILITY = 'diviner.default.view';
|
||||
|
||||
public function getCapabilityName() {
|
||||
return pht('Default View Policy');
|
||||
}
|
||||
|
||||
public function shouldAllowPublicPolicySetting() {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -2,33 +2,24 @@
|
|||
|
||||
final class DivinerAtomController extends DivinerController {
|
||||
|
||||
private $bookName;
|
||||
private $atomType;
|
||||
private $atomName;
|
||||
private $atomContext;
|
||||
private $atomIndex;
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->bookName = $data['book'];
|
||||
$this->atomType = $data['type'];
|
||||
$this->atomName = $data['name'];
|
||||
$this->atomContext = nonempty(idx($data, 'context'), null);
|
||||
$this->atomIndex = nonempty(idx($data, 'index'), null);
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$book_name = $request->getURIData('book');
|
||||
$atom_type = $request->getURIData('type');
|
||||
$atom_name = $request->getURIData('name');
|
||||
$atom_context = nonempty($request->getURIData('context'), null);
|
||||
$atom_index = nonempty($request->getURIData('index'), null);
|
||||
|
||||
require_celerity_resource('diviner-shared-css');
|
||||
|
||||
$book = id(new DivinerBookQuery())
|
||||
->setViewer($viewer)
|
||||
->withNames(array($this->bookName))
|
||||
->withNames(array($book_name))
|
||||
->executeOne();
|
||||
|
||||
if (!$book) {
|
||||
|
@ -38,10 +29,10 @@ final class DivinerAtomController extends DivinerController {
|
|||
$symbol = id(new DivinerAtomQuery())
|
||||
->setViewer($viewer)
|
||||
->withBookPHIDs(array($book->getPHID()))
|
||||
->withTypes(array($this->atomType))
|
||||
->withNames(array($this->atomName))
|
||||
->withContexts(array($this->atomContext))
|
||||
->withIndexes(array($this->atomIndex))
|
||||
->withTypes(array($atom_type))
|
||||
->withNames(array($atom_name))
|
||||
->withContexts(array($atom_context))
|
||||
->withIndexes(array($atom_index))
|
||||
->withIsDocumentable(true)
|
||||
->needAtoms(true)
|
||||
->needExtends(true)
|
||||
|
@ -75,7 +66,7 @@ final class DivinerAtomController extends DivinerController {
|
|||
->setName(DivinerAtom::getAtomTypeNameString(
|
||||
$atom ? $atom->getType() : $symbol->getType())));
|
||||
|
||||
$properties = id(new PHUIPropertyListView());
|
||||
$properties = new PHUIPropertyListView();
|
||||
|
||||
$group = $atom ? $atom->getProperty('group') : $symbol->getGroupName();
|
||||
if ($group) {
|
||||
|
@ -134,9 +125,7 @@ final class DivinerAtomController extends DivinerController {
|
|||
$document->appendChild(
|
||||
id(new PHUIInfoView())
|
||||
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
|
||||
->appendChild(
|
||||
pht(
|
||||
'This atom no longer exists.')));
|
||||
->appendChild(pht('This atom no longer exists.')));
|
||||
}
|
||||
|
||||
if ($atom) {
|
||||
|
@ -304,7 +293,6 @@ final class DivinerAtomController extends DivinerController {
|
|||
pht('Implements'),
|
||||
phutil_implode_html(phutil_tag('br'), $items));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function renderAtomTag(DivinerLiveSymbol $symbol) {
|
||||
|
@ -471,6 +459,7 @@ final class DivinerAtomController extends DivinerController {
|
|||
private function renderFullSignature(
|
||||
DivinerLiveSymbol $symbol,
|
||||
$is_link = false) {
|
||||
|
||||
switch ($symbol->getType()) {
|
||||
case DivinerAtom::TYPE_CLASS:
|
||||
case DivinerAtom::TYPE_INTERFACE:
|
||||
|
|
|
@ -2,20 +2,15 @@
|
|||
|
||||
final class DivinerAtomListController extends DivinerController {
|
||||
|
||||
private $key;
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->key = idx($data, 'key', 'all');
|
||||
}
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$query_key = $request->getURIData('key');
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$controller = id(new PhabricatorApplicationSearchController())
|
||||
->setQueryKey($this->key)
|
||||
->setQueryKey($query_key)
|
||||
->setSearchEngine(new DivinerAtomSearchEngine())
|
||||
->setNavigation($this->buildSideNavView());
|
||||
|
||||
|
|
|
@ -2,41 +2,46 @@
|
|||
|
||||
final class DivinerBookController extends DivinerController {
|
||||
|
||||
private $bookName;
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->bookName = $data['book'];
|
||||
}
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
$book_name = $request->getURIData('book');
|
||||
|
||||
$book = id(new DivinerBookQuery())
|
||||
->setViewer($viewer)
|
||||
->withNames(array($this->bookName))
|
||||
->withNames(array($book_name))
|
||||
->executeOne();
|
||||
|
||||
if (!$book) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$actions = $this->buildActionView($viewer, $book);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->setBorder(true);
|
||||
|
||||
$crumbs->addTextCrumb(
|
||||
$book->getShortTitle(),
|
||||
'/book/'.$book->getName().'/');
|
||||
|
||||
$action_button = id(new PHUIButtonView())
|
||||
->setTag('a')
|
||||
->setText(pht('Actions'))
|
||||
->setHref('#')
|
||||
->setIconFont('fa-bars')
|
||||
->addClass('phui-mobile-menu')
|
||||
->setDropdownMenu($actions);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader($book->getTitle())
|
||||
->setUser($viewer)
|
||||
->setPolicyObject($book)
|
||||
->setEpoch($book->getDateModified());
|
||||
->setEpoch($book->getDateModified())
|
||||
->addActionLink($action_button);
|
||||
|
||||
$document = new PHUIDocumentView();
|
||||
$document->setHeader($header);
|
||||
|
@ -100,4 +105,28 @@ final class DivinerBookController extends DivinerController {
|
|||
));
|
||||
}
|
||||
|
||||
private function buildActionView(
|
||||
PhabricatorUser $user,
|
||||
DivinerLiveBook $book) {
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$user,
|
||||
$book,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$action_view = id(new PhabricatorActionListView())
|
||||
->setUser($user)
|
||||
->setObject($book)
|
||||
->setObjectURI($this->getRequest()->getRequestURI());
|
||||
|
||||
$action_view->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Edit Book'))
|
||||
->setIcon('fa-pencil')
|
||||
->setHref('/book/'.$book->getName().'/edit/')
|
||||
->setDisabled(!$can_edit));
|
||||
|
||||
return $action_view;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
final class DivinerBookEditController extends DivinerController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
|
||||
$book_name = $request->getURIData('book');
|
||||
|
||||
$book = id(new DivinerBookQuery())
|
||||
->setViewer($viewer)
|
||||
->requireCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->needProjectPHIDs(true)
|
||||
->withNames(array($book_name))
|
||||
->executeOne();
|
||||
|
||||
if (!$book) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$view_uri = '/book/'.$book->getName().'/';
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
$v_projects = $request->getArr('projectPHIDs');
|
||||
$v_view = $request->getStr('viewPolicy');
|
||||
$v_edit = $request->getStr('editPolicy');
|
||||
|
||||
$xactions = array();
|
||||
$xactions[] = id(new DivinerLiveBookTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
|
||||
->setMetadataValue(
|
||||
'edge:type',
|
||||
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST)
|
||||
->setNewValue(
|
||||
array(
|
||||
'=' => array_fuse($v_projects),
|
||||
));
|
||||
$xactions[] = id(new DivinerLiveBookTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY)
|
||||
->setNewValue($v_view);
|
||||
$xactions[] = id(new DivinerLiveBookTransaction())
|
||||
->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY)
|
||||
->setNewValue($v_edit);
|
||||
|
||||
id(new DivinerLiveBookEditor())
|
||||
->setContinueOnNoEffect(true)
|
||||
->setContentSourceFromRequest($request)
|
||||
->setActor($viewer)
|
||||
->applyTransactions($book, $xactions);
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($view_uri);
|
||||
}
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$crumbs->addTextCrumb(pht('Edit Basics'));
|
||||
|
||||
$title = pht('Edit %s', $book->getTitle());
|
||||
|
||||
$policies = id(new PhabricatorPolicyQuery())
|
||||
->setViewer($viewer)
|
||||
->setObject($book)
|
||||
->execute();
|
||||
$view_capability = PhabricatorPolicyCapability::CAN_VIEW;
|
||||
$edit_capability = PhabricatorPolicyCapability::CAN_EDIT;
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer)
|
||||
->appendControl(
|
||||
id(new AphrontFormTokenizerControl())
|
||||
->setDatasource(new PhabricatorProjectDatasource())
|
||||
->setName('projectPHIDs')
|
||||
->setLabel(pht('Projects'))
|
||||
->setValue($book->getProjectPHIDs()))
|
||||
->appendChild(
|
||||
id(new AphrontFormPolicyControl())
|
||||
->setName('viewPolicy')
|
||||
->setPolicyObject($book)
|
||||
->setCapability($view_capability)
|
||||
->setPolicies($policies)
|
||||
->setCaption($book->describeAutomaticCapability($view_capability)))
|
||||
->appendChild(
|
||||
id(new AphrontFormPolicyControl())
|
||||
->setName('editPolicy')
|
||||
->setPolicyObject($book)
|
||||
->setCapability($edit_capability)
|
||||
->setPolicies($policies)
|
||||
->setCaption($book->describeAutomaticCapability($edit_capability)))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue(pht('Save'))
|
||||
->addCancelButton($view_uri));
|
||||
|
||||
$object_box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText($title)
|
||||
->setForm($form);
|
||||
|
||||
$timeline = $this->buildTransactionTimeline(
|
||||
$book,
|
||||
new DivinerLiveBookTransactionQuery());
|
||||
$timeline->setShouldTerminate(true);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$object_box,
|
||||
$timeline,
|
||||
),
|
||||
array(
|
||||
'title' => $title,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
|
@ -3,19 +3,15 @@
|
|||
abstract class DivinerController extends PhabricatorController {
|
||||
|
||||
protected function buildSideNavView() {
|
||||
$menu = $this->buildMenu();
|
||||
$menu = $this->buildApplicationMenu();
|
||||
return AphrontSideNavFilterView::newFromMenu($menu);
|
||||
}
|
||||
|
||||
public function buildApplicationMenu() {
|
||||
return $this->buildMenu();
|
||||
}
|
||||
|
||||
private function buildMenu() {
|
||||
$menu = new PHUIListView();
|
||||
|
||||
id(new DivinerAtomSearchEngine())
|
||||
->setViewer($this->getRequest()->getUser())
|
||||
->setViewer($this->getRequest()->getViewer())
|
||||
->addNavigationItems($menu);
|
||||
|
||||
return $menu;
|
||||
|
@ -24,12 +20,8 @@ abstract class DivinerController extends PhabricatorController {
|
|||
protected function renderAtomList(array $symbols) {
|
||||
assert_instances_of($symbols, 'DivinerLiveSymbol');
|
||||
|
||||
$request = $this->getRequest();
|
||||
$user = $request->getUser();
|
||||
|
||||
$list = array();
|
||||
foreach ($symbols as $symbol) {
|
||||
|
||||
switch ($symbol->getType()) {
|
||||
case DivinerAtom::TYPE_FUNCTION:
|
||||
$title = $symbol->getTitle().'()';
|
||||
|
@ -43,8 +35,7 @@ abstract class DivinerController extends PhabricatorController {
|
|||
->setTitle($title)
|
||||
->setHref($symbol->getURI())
|
||||
->setSubtitle($symbol->getSummary())
|
||||
->setType(DivinerAtom::getAtomTypeNameString(
|
||||
$symbol->getType()));
|
||||
->setType(DivinerAtom::getAtomTypeNameString($symbol->getType()));
|
||||
|
||||
$list[] = $item;
|
||||
}
|
||||
|
|
|
@ -6,11 +6,10 @@ final class DivinerFindController extends DivinerController {
|
|||
return true;
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
|
||||
$book_name = $request->getStr('book');
|
||||
$book_name = $request->getStr('book');
|
||||
$query_text = $request->getStr('name');
|
||||
|
||||
$book = null;
|
||||
|
@ -19,6 +18,7 @@ final class DivinerFindController extends DivinerController {
|
|||
->setViewer($viewer)
|
||||
->withNames(array($book_name))
|
||||
->executeOne();
|
||||
|
||||
if (!$book) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
@ -70,8 +70,8 @@ final class DivinerFindController extends DivinerController {
|
|||
->setTitle(pht('Documentation Not Found'))
|
||||
->appendChild(
|
||||
pht(
|
||||
'Unable to find the specified documentation. You may have '.
|
||||
'followed a bad or outdated link.'))
|
||||
'Unable to find the specified documentation. '.
|
||||
'You may have followed a bad or outdated link.'))
|
||||
->addCancelButton($not_found_uri, pht('Read More Documentation'));
|
||||
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
|
|
|
@ -6,9 +6,8 @@ final class DivinerMainController extends DivinerController {
|
|||
return true;
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$viewer = $request->getViewer();
|
||||
|
||||
$books = id(new DivinerBookQuery())
|
||||
->setViewer($viewer)
|
||||
|
@ -31,10 +30,10 @@ final class DivinerMainController extends DivinerController {
|
|||
->setHeader(pht('Documentation Books'))
|
||||
->addActionLink($query_button);
|
||||
|
||||
$document = new PHUIDocumentView();
|
||||
$document->setHeader($header);
|
||||
$document->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS);
|
||||
$document->addClass('diviner-view');
|
||||
$document = id(new PHUIDocumentView())
|
||||
->setHeader($header)
|
||||
->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS)
|
||||
->addClass('diviner-view');
|
||||
|
||||
if ($books) {
|
||||
$books = msort($books, 'getTitle');
|
||||
|
@ -54,24 +53,20 @@ final class DivinerMainController extends DivinerController {
|
|||
->appendChild($list);
|
||||
|
||||
$document->appendChild($list);
|
||||
|
||||
} else {
|
||||
$text = pht(
|
||||
"(NOTE) **Looking for Phabricator documentation?** If you're looking ".
|
||||
"for help and information about Phabricator, you can ".
|
||||
"[[ https://secure.phabricator.com/diviner/ | browse the public ".
|
||||
"Phabricator documentation ]] on the live site.\n\n".
|
||||
"Diviner is the documentation generator used to build the Phabricator ".
|
||||
"documentation.\n\n".
|
||||
"(NOTE) **Looking for Phabricator documentation?** ".
|
||||
"If you're looking for help and information about Phabricator, ".
|
||||
"you can [[https://secure.phabricator.com/diviner/ | ".
|
||||
"browse the public Phabricator documentation]] on the live site.\n\n".
|
||||
"Diviner is the documentation generator used to build the ".
|
||||
"Phabricator documentation.\n\n".
|
||||
"You haven't generated any Diviner documentation books yet, so ".
|
||||
"there's nothing to show here. If you'd like to generate your own ".
|
||||
"local copy of the Phabricator documentation and have it appear ".
|
||||
"here, run this command:\n\n".
|
||||
" phabricator/ $ ./bin/diviner generate\n\n".
|
||||
"Right now, Diviner isn't very useful for generating documentation ".
|
||||
"for projects other than Phabricator. If you're interested in using ".
|
||||
"it in your own projects, leave feedback for us on ".
|
||||
"[[ https://secure.phabricator.com/T4558 | T4558 ]].");
|
||||
" %s\n\n",
|
||||
'phabricator/ $ ./bin/diviner generate');
|
||||
|
||||
$text = PhabricatorMarkupEngine::renderOneObject(
|
||||
id(new PhabricatorMarkupOneOff())->setContent($text),
|
||||
|
|
23
src/applications/diviner/editor/DivinerLiveBookEditor.php
Normal file
23
src/applications/diviner/editor/DivinerLiveBookEditor.php
Normal file
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
final class DivinerLiveBookEditor
|
||||
extends PhabricatorApplicationTransactionEditor {
|
||||
|
||||
public function getEditorApplicationClass() {
|
||||
return 'PhabricatorDivinerApplication';
|
||||
}
|
||||
|
||||
public function getEditorObjectsDescription() {
|
||||
return pht('Diviner Books');
|
||||
}
|
||||
|
||||
public function getTransactionTypes() {
|
||||
$types = parent::getTransactionTypes();
|
||||
|
||||
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
|
||||
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
|
||||
|
||||
return $types;
|
||||
}
|
||||
|
||||
}
|
|
@ -12,6 +12,10 @@ final class DivinerAtomPHIDType extends PhabricatorPHIDType {
|
|||
return new DivinerLiveSymbol();
|
||||
}
|
||||
|
||||
public function getPHIDTypeApplicationClass() {
|
||||
return 'PhabricatorDivinerApplication';
|
||||
}
|
||||
|
||||
protected function buildQueryForObjects(
|
||||
PhabricatorObjectQuery $query,
|
||||
array $phids) {
|
||||
|
|
|
@ -12,6 +12,10 @@ final class DivinerBookPHIDType extends PhabricatorPHIDType {
|
|||
return new DivinerLiveBook();
|
||||
}
|
||||
|
||||
public function getPHIDTypeApplicationClass() {
|
||||
return 'PhabricatorDivinerApplication';
|
||||
}
|
||||
|
||||
protected function buildQueryForObjects(
|
||||
PhabricatorObjectQuery $query,
|
||||
array $phids) {
|
||||
|
|
|
@ -8,11 +8,15 @@ final class DivinerLivePublisher extends DivinerPublisher {
|
|||
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::getMostOpenPolicy())
|
||||
->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN)
|
||||
->save();
|
||||
}
|
||||
|
||||
|
@ -144,7 +148,6 @@ final class DivinerLivePublisher extends DivinerPublisher {
|
|||
->setContent(null)
|
||||
->save();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -133,10 +133,20 @@ abstract class DivinerPublisher extends Phobject {
|
|||
$created = array_keys($created);
|
||||
}
|
||||
|
||||
echo pht('Deleting %d documents.', count($deleted))."\n";
|
||||
$console = PhutilConsole::getConsole();
|
||||
|
||||
$console->writeOut(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Deleting %s document(s).',
|
||||
new PhutilNumber(count($deleted))));
|
||||
$this->deleteDocumentsByHash($deleted);
|
||||
|
||||
echo pht('Creating %d documents.', count($created))."\n";
|
||||
$console->writeOut(
|
||||
"%s\n",
|
||||
pht(
|
||||
'Creating %s document(s).',
|
||||
new PhutilNumber(count($created))));
|
||||
$this->createDocumentsByHash($created);
|
||||
}
|
||||
|
||||
|
|
|
@ -79,7 +79,6 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Include or exclude "ghosts", which are symbols which used to exist but do
|
||||
* not exist currently (for example, a function which existed in an older
|
||||
|
@ -137,6 +136,7 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
foreach ($atoms as $key => $atom) {
|
||||
$book = idx($books, $atom->getBookPHID());
|
||||
if (!$book) {
|
||||
$this->didRejectResult($atom);
|
||||
unset($atoms[$key]);
|
||||
continue;
|
||||
}
|
||||
|
@ -158,12 +158,9 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
// Load all of the symbols this symbol extends, recursively. Commonly,
|
||||
// this means all the ancestor classes and interfaces it extends and
|
||||
// implements.
|
||||
|
||||
if ($this->needExtends) {
|
||||
|
||||
// First, load all the matching symbols by name. This does 99% of the
|
||||
// work in most cases, assuming things are named at all reasonably.
|
||||
|
||||
$names = array();
|
||||
foreach ($atoms as $atom) {
|
||||
if (!$atom->getAtom()) {
|
||||
|
@ -303,6 +300,7 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
|
||||
if ($this->titles) {
|
||||
$hashes = array();
|
||||
|
||||
foreach ($this->titles as $title) {
|
||||
$slug = DivinerAtomRef::normalizeTitleString($title);
|
||||
$hash = PhabricatorHash::digestForIndex($slug);
|
||||
|
@ -318,6 +316,7 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
if ($this->contexts) {
|
||||
$with_null = false;
|
||||
$contexts = $this->contexts;
|
||||
|
||||
foreach ($contexts as $key => $value) {
|
||||
if ($value === null) {
|
||||
unset($contexts[$key]);
|
||||
|
@ -373,10 +372,9 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
}
|
||||
|
||||
if ($this->nameContains) {
|
||||
// NOTE: This CONVERT() call makes queries case-insensitive, since the
|
||||
// column has binary collation. Eventually, this should move into
|
||||
// NOTE: This `CONVERT()` call makes queries case-insensitive, since
|
||||
// the column has binary collation. Eventually, this should move into
|
||||
// fulltext.
|
||||
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'CONVERT(name USING utf8) LIKE %~',
|
||||
|
@ -388,7 +386,6 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
return $this->formatWhereClause($where);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Walk a list of atoms and collect all the node hashes of the atoms'
|
||||
* children. When recursing, also walk up the tree and collect children of
|
||||
|
@ -413,6 +410,7 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
foreach ($child_hashes as $hash) {
|
||||
$hashes[$hash] = $hash;
|
||||
}
|
||||
|
||||
if ($recurse_up) {
|
||||
$hashes += $this->getAllChildHashes($symbol->getExtends(), true);
|
||||
}
|
||||
|
@ -421,7 +419,6 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
return $hashes;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Attach child atoms to existing atoms. In recursive mode, also attach child
|
||||
* atoms to atoms that these atoms extend.
|
||||
|
@ -452,7 +449,9 @@ final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
$symbol_children[] = $children[$hash];
|
||||
}
|
||||
}
|
||||
|
||||
$symbol->attachChildren($symbol_children);
|
||||
|
||||
if ($recurse_up) {
|
||||
$this->attachAllChildren($symbol->getExtends(), $children, true);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ final class DivinerBookQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
private $phids;
|
||||
private $names;
|
||||
|
||||
private $needProjectPHIDs;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
return $this;
|
||||
|
@ -21,6 +23,11 @@ final class DivinerBookQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function needProjectPHIDs($need_phids) {
|
||||
$this->needProjectPHIDs = $need_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
$table = new DivinerLiveBook();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
|
@ -36,6 +43,30 @@ final class DivinerBookQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
return $table->loadAllFromArray($data);
|
||||
}
|
||||
|
||||
protected function didFilterPage(array $books) {
|
||||
assert_instances_of($books, 'DivinerLiveBook');
|
||||
|
||||
if ($this->needProjectPHIDs) {
|
||||
$edge_query = id(new PhabricatorEdgeQuery())
|
||||
->withSourcePHIDs(mpull($books, 'getPHID'))
|
||||
->withEdgeTypes(
|
||||
array(
|
||||
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
|
||||
));
|
||||
$edge_query->execute();
|
||||
|
||||
foreach ($books as $book) {
|
||||
$project_phids = $edge_query->getDestinationPHIDs(
|
||||
array(
|
||||
$book->getPHID(),
|
||||
));
|
||||
$book->attachProjectPHIDs($project_phids);
|
||||
}
|
||||
}
|
||||
|
||||
return $books;
|
||||
}
|
||||
|
||||
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
|
||||
$where = array();
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
final class DivinerLiveBookTransactionQuery
|
||||
extends PhabricatorApplicationTransactionQuery {
|
||||
|
||||
public function getTemplateApplicationTransaction() {
|
||||
return new DivinerLiveBookTransaction();
|
||||
}
|
||||
|
||||
}
|
|
@ -202,8 +202,8 @@ final class DivinerDefaultRenderer extends DivinerRenderer {
|
|||
}
|
||||
|
||||
if ($ref->getBook() != $this->getConfig('name')) {
|
||||
// If the ref is from a different book, we can't normalize it. Just return
|
||||
// it as-is if it has enough information to resolve.
|
||||
// If the ref is from a different book, we can't normalize it.
|
||||
// Just return it as-is if it has enough information to resolve.
|
||||
if ($ref->getName() && $ref->getType()) {
|
||||
return $ref;
|
||||
} else {
|
||||
|
@ -260,5 +260,4 @@ final class DivinerDefaultRenderer extends DivinerRenderer {
|
|||
$ref->getTitle());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,11 @@ final class DivinerBookSearchIndexer extends PhabricatorSearchDocumentIndexer {
|
|||
PhabricatorSearchDocumentFieldType::FIELD_BODY,
|
||||
$book->getPreface());
|
||||
|
||||
$this->indexTransactions(
|
||||
$doc,
|
||||
new DivinerLiveBookTransactionQuery(),
|
||||
array($phid));
|
||||
|
||||
return $doc;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,12 +3,17 @@
|
|||
final class DivinerLiveBook extends DivinerDAO
|
||||
implements
|
||||
PhabricatorPolicyInterface,
|
||||
PhabricatorDestructibleInterface {
|
||||
PhabricatorProjectInterface,
|
||||
PhabricatorDestructibleInterface,
|
||||
PhabricatorApplicationTransactionInterface {
|
||||
|
||||
protected $name;
|
||||
protected $viewPolicy;
|
||||
protected $editPolicy;
|
||||
protected $configurationData = array();
|
||||
|
||||
private $projectPHIDs = self::ATTACHABLE;
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
|
@ -63,28 +68,47 @@ final class DivinerLiveBook extends DivinerDAO
|
|||
return idx($spec, 'name', $group);
|
||||
}
|
||||
|
||||
public function attachProjectPHIDs(array $project_phids) {
|
||||
$this->projectPHIDs = $project_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getProjectPHIDs() {
|
||||
return $this->assertAttached($this->projectPHIDs);
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function getCapabilities() {
|
||||
return array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
return PhabricatorPolicies::getMostOpenPolicy();
|
||||
switch ($capability) {
|
||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
||||
return $this->getViewPolicy();
|
||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
||||
return $this->getEditPolicy();
|
||||
}
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
public function describeAutomaticCapability($capability) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
||||
|
||||
|
||||
public function destroyObjectPermanently(
|
||||
PhabricatorDestructionEngine $engine) {
|
||||
|
||||
|
@ -102,4 +126,27 @@ final class DivinerLiveBook extends DivinerDAO
|
|||
$this->saveTransaction();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorApplicationTransactionInterface )------------------------- */
|
||||
|
||||
|
||||
public function getApplicationTransactionEditor() {
|
||||
return new DivinerLiveBookEditor();
|
||||
}
|
||||
|
||||
public function getApplicationTransactionObject() {
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionTemplate() {
|
||||
return new DivinerLiveBookTransaction();
|
||||
}
|
||||
|
||||
public function willRenderTimeline(
|
||||
PhabricatorApplicationTransactionView $timeline,
|
||||
AphrontRequest $request) {
|
||||
|
||||
return $timeline;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
final class DivinerLiveBookTransaction
|
||||
extends PhabricatorApplicationTransaction {
|
||||
|
||||
public function getApplicationName() {
|
||||
return 'diviner';
|
||||
}
|
||||
|
||||
public function getApplicationTransactionType() {
|
||||
return DivinerBookPHIDType::TYPECONST;
|
||||
}
|
||||
|
||||
public function getApplicationTransactionCommentObject() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -137,10 +137,9 @@ final class DivinerLiveSymbol extends DivinerDAO
|
|||
}
|
||||
|
||||
public function save() {
|
||||
|
||||
// NOTE: The identity hash is just a sanity check because the unique tuple
|
||||
// on this table is way way too long to fit into a normal UNIQUE KEY. We
|
||||
// don't use it directly, but its existence prevents duplicate records.
|
||||
// on this table is way way too long to fit into a normal `UNIQUE KEY`.
|
||||
// We don't use it directly, but its existence prevents duplicate records.
|
||||
|
||||
if (!$this->identityHash) {
|
||||
$this->identityHash = PhabricatorHash::digestForIndex(
|
||||
|
@ -159,14 +158,17 @@ final class DivinerLiveSymbol extends DivinerDAO
|
|||
|
||||
public function getTitle() {
|
||||
$title = parent::getTitle();
|
||||
|
||||
if (!strlen($title)) {
|
||||
$title = $this->getName();
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
public function setTitle($value) {
|
||||
$this->writeField('title', $value);
|
||||
|
||||
if (strlen($value)) {
|
||||
$slug = DivinerAtomRef::normalizeTitleString($value);
|
||||
$hash = PhabricatorHash::digestForIndex($slug);
|
||||
|
@ -174,6 +176,7 @@ final class DivinerLiveSymbol extends DivinerDAO
|
|||
} else {
|
||||
$this->titleSlugHash = null;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
@ -200,16 +203,15 @@ final class DivinerLiveSymbol extends DivinerDAO
|
|||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function getCapabilities() {
|
||||
return $this->getBook()->getCapabilities();
|
||||
}
|
||||
|
||||
|
||||
public function getPolicy($capability) {
|
||||
return $this->getBook()->getPolicy($capability);
|
||||
}
|
||||
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
return $this->getBook()->hasAutomaticCapability($capability, $viewer);
|
||||
}
|
||||
|
@ -219,19 +221,17 @@ final class DivinerLiveSymbol extends DivinerDAO
|
|||
}
|
||||
|
||||
|
||||
/* -( Markup Interface )--------------------------------------------------- */
|
||||
/* -( PhabricatorMarkupInterface )------------------------------------------ */
|
||||
|
||||
|
||||
public function getMarkupFieldKey($field) {
|
||||
return $this->getPHID().':'.$field.':'.$this->getGraphHash();
|
||||
}
|
||||
|
||||
|
||||
public function newMarkupEngine($field) {
|
||||
return PhabricatorMarkupEngine::getEngine('diviner');
|
||||
}
|
||||
|
||||
|
||||
public function getMarkupText($field) {
|
||||
if (!$this->getAtom()) {
|
||||
return;
|
||||
|
@ -240,21 +240,18 @@ final class DivinerLiveSymbol extends DivinerDAO
|
|||
return $this->getAtom()->getDocblockText();
|
||||
}
|
||||
|
||||
|
||||
public function didMarkupText(
|
||||
$field,
|
||||
$output,
|
||||
PhutilMarkupEngine $engine) {
|
||||
public function didMarkupText($field, $output, PhutilMarkupEngine $engine) {
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
public function shouldUseMarkupCache($field) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorDestructibleInterface )----------------------------------- */
|
||||
|
||||
|
||||
public function destroyObjectPermanently(
|
||||
PhabricatorDestructionEngine $engine) {
|
||||
|
||||
|
|
9
src/applications/diviner/storage/DivinerSchemaSpec.php
Normal file
9
src/applications/diviner/storage/DivinerSchemaSpec.php
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
final class DivinerSchemaSpec extends PhabricatorConfigSchemaSpec {
|
||||
|
||||
public function buildSchemata() {
|
||||
$this->buildEdgeSchemata(new DivinerLiveBook());
|
||||
}
|
||||
|
||||
}
|
|
@ -43,23 +43,23 @@ final class DivinerBookItemView extends AphrontTagView {
|
|||
|
||||
$title = phutil_tag(
|
||||
'span',
|
||||
array(
|
||||
'class' => 'diviner-book-item-title',
|
||||
),
|
||||
array(
|
||||
'class' => 'diviner-book-item-title',
|
||||
),
|
||||
$this->title);
|
||||
|
||||
$subtitle = phutil_tag(
|
||||
'span',
|
||||
array(
|
||||
'class' => 'diviner-book-item-subtitle',
|
||||
),
|
||||
array(
|
||||
'class' => 'diviner-book-item-subtitle',
|
||||
),
|
||||
$this->subtitle);
|
||||
|
||||
$type = phutil_tag(
|
||||
'span',
|
||||
array(
|
||||
'class' => 'diviner-book-item-type',
|
||||
),
|
||||
array(
|
||||
'class' => 'diviner-book-item-type',
|
||||
),
|
||||
$this->type);
|
||||
|
||||
return array($title, $type, $subtitle);
|
||||
|
|
|
@ -36,8 +36,10 @@ final class DivinerAtomizeWorkflow extends DivinerWorkflow {
|
|||
|
||||
$atomizer_class = $args->getArg('atomizer');
|
||||
if (!$atomizer_class) {
|
||||
throw new Exception(
|
||||
pht('Specify an atomizer class with %s.', '--atomizer'));
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Specify an atomizer class with %s.',
|
||||
'--atomizer'));
|
||||
}
|
||||
|
||||
$symbols = id(new PhutilSymbolLoader())
|
||||
|
@ -46,7 +48,7 @@ final class DivinerAtomizeWorkflow extends DivinerWorkflow {
|
|||
->setAncestorClass('DivinerAtomizer')
|
||||
->selectAndLoadSymbols();
|
||||
if (!$symbols) {
|
||||
throw new Exception(
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
"Atomizer class '%s' must be a concrete subclass of %s.",
|
||||
$atomizer_class,
|
||||
|
|
|
@ -50,6 +50,7 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
|
|||
} else {
|
||||
$cwd = getcwd();
|
||||
$this->log(pht('FINDING DOCUMENTATION BOOKS'));
|
||||
|
||||
$books = id(new FileFinder($cwd))
|
||||
->withType('f')
|
||||
->withSuffix('book')
|
||||
|
@ -92,7 +93,7 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
|
|||
// amount of work we can, so that regenerating documentation after minor
|
||||
// changes is quick.
|
||||
//
|
||||
// = ATOM CACHE =
|
||||
// = Atom Cache =
|
||||
//
|
||||
// In the first stage, we find all the direct changes to source code since
|
||||
// the last run. This stage relies on two data structures:
|
||||
|
@ -118,7 +119,7 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
|
|||
// its methods). The File Hash Map contains an exhaustive list of all atoms
|
||||
// with type "file", but not child atoms of those top-level atoms.)
|
||||
//
|
||||
// = GRAPH CACHE =
|
||||
// = Graph Cache =
|
||||
//
|
||||
// We now know which atoms exist, and can compare the Atom Map to some
|
||||
// existing cache to figure out what has changed. However, this isn't
|
||||
|
@ -176,8 +177,9 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
|
|||
->setConcreteOnly(true)
|
||||
->setAncestorClass('DivinerPublisher')
|
||||
->selectAndLoadSymbols();
|
||||
|
||||
if (!$symbols) {
|
||||
throw new Exception(
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
"Publisher class '%s' must be a concrete subclass of %s.",
|
||||
$publisher_class,
|
||||
|
@ -188,22 +190,37 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
|
|||
$this->publishDocumentation($args->getArg('clean'), $publisher);
|
||||
}
|
||||
|
||||
|
||||
/* -( Atom Cache )--------------------------------------------------------- */
|
||||
|
||||
|
||||
private function buildAtomCache() {
|
||||
$this->log(pht('BUILDING ATOM CACHE'));
|
||||
|
||||
$file_hashes = $this->findFilesInProject();
|
||||
$this->log(pht('Found %d file(s) in project.', count($file_hashes)));
|
||||
$this->log(
|
||||
pht(
|
||||
'Found %s file(s) in project.',
|
||||
new PhutilNumber(count($file_hashes))));
|
||||
|
||||
$this->deleteDeadAtoms($file_hashes);
|
||||
$atomize = $this->getFilesToAtomize($file_hashes);
|
||||
$this->log(pht('Found %d unatomized, uncached file(s).', count($atomize)));
|
||||
$this->log(
|
||||
pht(
|
||||
'Found %s unatomized, uncached file(s).',
|
||||
new PhutilNumber(count($atomize))));
|
||||
|
||||
$file_atomizers = $this->getAtomizersForFiles($atomize);
|
||||
$this->log(pht('Found %d file(s) to atomize.', count($file_atomizers)));
|
||||
$this->log(
|
||||
pht(
|
||||
'Found %s file(s) to atomize.',
|
||||
new PhutilNumber(count($file_atomizers))));
|
||||
|
||||
$futures = $this->buildAtomizerFutures($file_atomizers);
|
||||
$this->log(pht('Atomizing %d file(s).', count($file_atomizers)));
|
||||
$this->log(
|
||||
pht(
|
||||
'Atomizing %s file(s).',
|
||||
new PhutilNumber(count($file_atomizers))));
|
||||
|
||||
if ($futures) {
|
||||
$this->resolveAtomizerFutures($futures, $file_hashes);
|
||||
|
@ -344,6 +361,7 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
|
|||
->setTotal(count($futures));
|
||||
$futures = id(new FutureIterator($futures))
|
||||
->limit(4);
|
||||
|
||||
foreach ($futures as $key => $future) {
|
||||
try {
|
||||
$atoms = $future->resolveJSON();
|
||||
|
@ -396,6 +414,7 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
|
|||
|
||||
/* -( Graph Cache )-------------------------------------------------------- */
|
||||
|
||||
|
||||
private function buildGraphCache() {
|
||||
$this->log(pht('BUILDING GRAPH CACHE'));
|
||||
|
||||
|
@ -407,7 +426,10 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
|
|||
$dirty_nhashes = array();
|
||||
|
||||
$del_atoms = array_diff_key($symbol_map, $atoms);
|
||||
$this->log(pht('Found %d obsolete atom(s) in graph.', count($del_atoms)));
|
||||
$this->log(
|
||||
pht(
|
||||
'Found %s obsolete atom(s) in graph.',
|
||||
new PhutilNumber(count($del_atoms))));
|
||||
|
||||
foreach ($del_atoms as $nhash => $shash) {
|
||||
$atom_cache->deleteSymbol($nhash);
|
||||
|
@ -418,7 +440,10 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
|
|||
}
|
||||
|
||||
$new_atoms = array_diff_key($atoms, $symbol_map);
|
||||
$this->log(pht('Found %d new atom(s) in graph.', count($new_atoms)));
|
||||
$this->log(
|
||||
pht(
|
||||
'Found %s new atom(s) in graph.',
|
||||
new PhutilNumber(count($new_atoms))));
|
||||
|
||||
foreach ($new_atoms as $nhash => $ignored) {
|
||||
$shash = $this->computeSymbolHash($nhash);
|
||||
|
@ -454,7 +479,10 @@ final class DivinerGenerateWorkflow extends DivinerWorkflow {
|
|||
}
|
||||
}
|
||||
|
||||
$this->log(pht('Found %d affected atoms.', count($dirty_nhashes)));
|
||||
$this->log(
|
||||
pht(
|
||||
'Found %s affected atoms.',
|
||||
new PhutilNumber(count($dirty_nhashes))));
|
||||
|
||||
foreach ($dirty_nhashes as $nhash => $ignored) {
|
||||
$atom_cache->addGraph($nhash, $this->computeGraphHash($nhash));
|
||||
|
|
|
@ -1124,6 +1124,52 @@ final class PhabricatorUSEnglishTranslation
|
|||
'%s changed package owners, added: %4$s; removed: %6$s.',
|
||||
),
|
||||
|
||||
'Found %s book(s).' => array(
|
||||
'Found %s book.',
|
||||
'Found %s books.',
|
||||
),
|
||||
'Found %s file(s) in project.' => array(
|
||||
'Found %s file in project.',
|
||||
'Found %s files in project.',
|
||||
),
|
||||
'Found %s unatomized, uncached file(s).' => array(
|
||||
'Found %s unatomized, uncached file.',
|
||||
'Found %s unatomized, uncached files.',
|
||||
),
|
||||
'Found %s file(s) to atomize.' => array(
|
||||
'Found %s file to atomize.',
|
||||
'Found %s files to atomize.',
|
||||
),
|
||||
'Atomizing %s file(s).' => array(
|
||||
'Atomizing %s file.',
|
||||
'Atomizing %s files.',
|
||||
),
|
||||
'Creating %s document(s).' => array(
|
||||
'Creating %s document.',
|
||||
'Creating %s documents.',
|
||||
),
|
||||
'Deleting %s document(s).' => array(
|
||||
'Deleting %s document.',
|
||||
'Deleting %s documents.',
|
||||
),
|
||||
'Found %s obsolete atom(s) in graph.' => array(
|
||||
'Found %s obsolete atom in graph.',
|
||||
'Found %s obsolete atoms in graph.',
|
||||
),
|
||||
'Found %s new atom(s) in graph.' => array(
|
||||
'Found %s new atom in graph.',
|
||||
'Found %s new atoms in graph.',
|
||||
),
|
||||
'This call takes %s parameter(s), but only %s are documented.' => array(
|
||||
array(
|
||||
'This call takes %s parameter, but only %s is documented.',
|
||||
'This call takes %s parameter, but only %s are documented.',
|
||||
),
|
||||
array(
|
||||
'This call takes %s parameters, but only %s is documented.',
|
||||
'This call takes %s parameters, but only %s are documented.',
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue