@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+601 -151
+17
resources/sql/autopatches/20150605.diviner.edges.sql
··· 1 + CREATE TABLE {$NAMESPACE}_diviner.edge ( 2 + src VARBINARY(64) NOT NULL, 3 + type INT UNSIGNED NOT NULL, 4 + dst VARBINARY(64) NOT NULL, 5 + dateCreated INT UNSIGNED NOT NULL, 6 + seq INT UNSIGNED NOT NULL, 7 + dataID INT UNSIGNED, 8 + 9 + PRIMARY KEY (src, type, dst), 10 + KEY src (src, type, dateCreated, seq), 11 + UNIQUE KEY key_dst (dst, type, src) 12 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; 13 + 14 + CREATE TABLE {$NAMESPACE}_diviner.edgedata ( 15 + id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT, 16 + data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT} 17 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+6
resources/sql/autopatches/20150605.diviner.editPolicy.sql
··· 1 + ALTER TABLE {$NAMESPACE}_diviner.diviner_livebook 2 + ADD COLUMN editPolicy VARBINARY(64) NOT NULL AFTER viewPolicy; 3 + 4 + UPDATE {$NAMESPACE}_diviner.diviner_livebook 5 + SET editPolicy = 'admin' 6 + WHERE editPolicy = '';
+19
resources/sql/autopatches/20150605.diviner.xaction.sql
··· 1 + CREATE TABLE {$NAMESPACE}_diviner.diviner_livebooktransaction ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + phid VARBINARY(64) NOT NULL, 4 + authorPHID VARBINARY(64) NOT NULL, 5 + objectPHID VARBINARY(64) NOT NULL, 6 + viewPolicy VARBINARY(64) NOT NULL, 7 + editPolicy VARBINARY(64) NOT NULL, 8 + commentPHID VARBINARY(64) DEFAULT NULL, 9 + commentVersion INT UNSIGNED NOT NULL, 10 + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, 11 + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 12 + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 13 + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 14 + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 15 + dateCreated INT UNSIGNED NOT NULL, 16 + dateModified INT UNSIGNED NOT NULL, 17 + UNIQUE KEY key_phid (phid), 18 + KEY key_object (objectPHID) 19 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+16
src/__phutil_library_map__.php
··· 649 649 'DivinerAtomizeWorkflow' => 'applications/diviner/workflow/DivinerAtomizeWorkflow.php', 650 650 'DivinerAtomizer' => 'applications/diviner/atomizer/DivinerAtomizer.php', 651 651 'DivinerBookController' => 'applications/diviner/controller/DivinerBookController.php', 652 + 'DivinerBookEditController' => 'applications/diviner/controller/DivinerBookEditController.php', 652 653 'DivinerBookItemView' => 'applications/diviner/view/DivinerBookItemView.php', 653 654 'DivinerBookPHIDType' => 'applications/diviner/phid/DivinerBookPHIDType.php', 654 655 'DivinerBookQuery' => 'applications/diviner/query/DivinerBookQuery.php', 655 656 'DivinerBookSearchIndexer' => 'applications/diviner/search/DivinerBookSearchIndexer.php', 656 657 'DivinerController' => 'applications/diviner/controller/DivinerController.php', 657 658 'DivinerDAO' => 'applications/diviner/storage/DivinerDAO.php', 659 + 'DivinerDefaultEditCapability' => 'applications/diviner/capability/DivinerDefaultEditCapability.php', 658 660 'DivinerDefaultRenderer' => 'applications/diviner/renderer/DivinerDefaultRenderer.php', 661 + 'DivinerDefaultViewCapability' => 'applications/diviner/capability/DivinerDefaultViewCapability.php', 659 662 'DivinerDiskCache' => 'applications/diviner/cache/DivinerDiskCache.php', 660 663 'DivinerFileAtomizer' => 'applications/diviner/atomizer/DivinerFileAtomizer.php', 661 664 'DivinerFindController' => 'applications/diviner/controller/DivinerFindController.php', 662 665 'DivinerGenerateWorkflow' => 'applications/diviner/workflow/DivinerGenerateWorkflow.php', 663 666 'DivinerLiveAtom' => 'applications/diviner/storage/DivinerLiveAtom.php', 664 667 'DivinerLiveBook' => 'applications/diviner/storage/DivinerLiveBook.php', 668 + 'DivinerLiveBookEditor' => 'applications/diviner/editor/DivinerLiveBookEditor.php', 669 + 'DivinerLiveBookTransaction' => 'applications/diviner/storage/DivinerLiveBookTransaction.php', 670 + 'DivinerLiveBookTransactionQuery' => 'applications/diviner/query/DivinerLiveBookTransactionQuery.php', 665 671 'DivinerLivePublisher' => 'applications/diviner/publisher/DivinerLivePublisher.php', 666 672 'DivinerLiveSymbol' => 'applications/diviner/storage/DivinerLiveSymbol.php', 667 673 'DivinerMainController' => 'applications/diviner/controller/DivinerMainController.php', ··· 671 677 'DivinerPublisher' => 'applications/diviner/publisher/DivinerPublisher.php', 672 678 'DivinerRenderer' => 'applications/diviner/renderer/DivinerRenderer.php', 673 679 'DivinerReturnTableView' => 'applications/diviner/view/DivinerReturnTableView.php', 680 + 'DivinerSchemaSpec' => 'applications/diviner/storage/DivinerSchemaSpec.php', 674 681 'DivinerSectionView' => 'applications/diviner/view/DivinerSectionView.php', 675 682 'DivinerStaticPublisher' => 'applications/diviner/publisher/DivinerStaticPublisher.php', 676 683 'DivinerSymbolRemarkupRule' => 'applications/diviner/markup/DivinerSymbolRemarkupRule.php', ··· 4010 4017 'DivinerAtomizeWorkflow' => 'DivinerWorkflow', 4011 4018 'DivinerAtomizer' => 'Phobject', 4012 4019 'DivinerBookController' => 'DivinerController', 4020 + 'DivinerBookEditController' => 'DivinerController', 4013 4021 'DivinerBookItemView' => 'AphrontTagView', 4014 4022 'DivinerBookPHIDType' => 'PhabricatorPHIDType', 4015 4023 'DivinerBookQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 4016 4024 'DivinerBookSearchIndexer' => 'PhabricatorSearchDocumentIndexer', 4017 4025 'DivinerController' => 'PhabricatorController', 4018 4026 'DivinerDAO' => 'PhabricatorLiskDAO', 4027 + 'DivinerDefaultEditCapability' => 'PhabricatorPolicyCapability', 4019 4028 'DivinerDefaultRenderer' => 'DivinerRenderer', 4029 + 'DivinerDefaultViewCapability' => 'PhabricatorPolicyCapability', 4020 4030 'DivinerDiskCache' => 'Phobject', 4021 4031 'DivinerFileAtomizer' => 'DivinerAtomizer', 4022 4032 'DivinerFindController' => 'DivinerController', ··· 4025 4035 'DivinerLiveBook' => array( 4026 4036 'DivinerDAO', 4027 4037 'PhabricatorPolicyInterface', 4038 + 'PhabricatorProjectInterface', 4028 4039 'PhabricatorDestructibleInterface', 4040 + 'PhabricatorApplicationTransactionInterface', 4029 4041 ), 4042 + 'DivinerLiveBookEditor' => 'PhabricatorApplicationTransactionEditor', 4043 + 'DivinerLiveBookTransaction' => 'PhabricatorApplicationTransaction', 4044 + 'DivinerLiveBookTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 4030 4045 'DivinerLivePublisher' => 'DivinerPublisher', 4031 4046 'DivinerLiveSymbol' => array( 4032 4047 'DivinerDAO', ··· 4041 4056 'DivinerPublisher' => 'Phobject', 4042 4057 'DivinerRenderer' => 'Phobject', 4043 4058 'DivinerReturnTableView' => 'AphrontTagView', 4059 + 'DivinerSchemaSpec' => 'PhabricatorConfigSchemaSpec', 4044 4060 'DivinerSectionView' => 'AphrontTagView', 4045 4061 'DivinerStaticPublisher' => 'DivinerPublisher', 4046 4062 'DivinerSymbolRemarkupRule' => 'PhutilRemarkupRule',
+13
src/applications/diviner/application/PhabricatorDivinerApplication.php
··· 39 39 'find/' => 'DivinerFindController', 40 40 ), 41 41 '/book/(?P<book>[^/]+)/' => 'DivinerBookController', 42 + '/book/(?P<book>[^/]+)/edit/' => 'DivinerBookEditController', 42 43 '/book/'. 43 44 '(?P<book>[^/]+)/'. 44 45 '(?P<type>[^/]+)/'. ··· 50 51 51 52 public function getApplicationGroup() { 52 53 return self::GROUP_UTILITIES; 54 + } 55 + 56 + protected function getCustomCapabilities() { 57 + return array( 58 + DivinerDefaultViewCapability::CAPABILITY => array( 59 + 'template' => DivinerBookPHIDType::TYPECONST, 60 + ), 61 + DivinerDefaultEditCapability::CAPABILITY => array( 62 + 'default' => PhabricatorPolicies::POLICY_ADMIN, 63 + 'template' => DivinerBookPHIDType::TYPECONST, 64 + ), 65 + ); 53 66 } 54 67 55 68 public function getRemarkupRules() {
+1 -1
src/applications/diviner/atomizer/DivinerArticleAtomizer.php
··· 20 20 $atom->setDocblockMetaValue('title', $title); 21 21 } 22 22 23 - // If the article has no @name, use the filename after stripping any 23 + // If the article has no `@name`, use the filename after stripping any 24 24 // extension. 25 25 $name = idx($meta, 'name'); 26 26 if (!$name) {
+11 -11
src/applications/diviner/atomizer/DivinerPHPAtomizer.php
··· 151 151 if (count($docs) < count($params)) { 152 152 $atom->addWarning( 153 153 pht( 154 - 'This call takes %d parameters, but only %d are documented.', 155 - count($params), 156 - count($docs))); 154 + 'This call takes %s parameter(s), but only %s are documented.', 155 + new PhutilNumber(count($params)), 156 + new PhutilNumber(count($docs)))); 157 157 } 158 158 } 159 159 ··· 212 212 if (preg_match('/@(return|param|task|author)/', $value, $matches)) { 213 213 $atom->addWarning( 214 214 pht( 215 - 'Atom "%s" is preceded by a comment containing "@%s", but the '. 216 - 'comment is not a documentation comment. Documentation '. 217 - 'comments must begin with "%s", followed by a newline. Did '. 215 + 'Atom "%s" is preceded by a comment containing `%s`, but '. 216 + 'the comment is not a documentation comment. Documentation '. 217 + 'comments must begin with `%s`, followed by a newline. Did '. 218 218 'you mean to use a documentation comment? (As the comment is '. 219 219 'not a documentation comment, it will be ignored.)', 220 220 $atom->getName(), 221 - $matches[1], 221 + '@'.$matches[1], 222 222 '/**')); 223 223 } 224 224 } ··· 248 248 if ($matches[1] !== $name) { 249 249 $atom->addWarning( 250 250 pht( 251 - 'Parameter "%s" is named "%s" in the documentation. The '. 252 - 'documentation may be out of date.', 251 + 'Parameter "%s" is named "%s" in the documentation. '. 252 + 'The documentation may be out of date.', 253 253 $name, 254 254 $matches[1])); 255 255 } ··· 292 292 if ($return) { 293 293 $atom->addWarning( 294 294 pht( 295 - 'Method %s has explicitly documented %s. The %s method always '. 296 - 'returns %s. Diviner documents this implicitly.', 295 + 'Method `%s` has explicitly documented `%s`. The `%s` method '. 296 + 'always returns `%s`. Diviner documents this implicitly.', 297 297 '__construct()', 298 298 '@return', 299 299 '__construct()',
+1
src/applications/diviner/cache/DivinerAtomCache.php
··· 18 18 19 19 public function delete() { 20 20 parent::delete(); 21 + 21 22 $this->fileHashMap = null; 22 23 $this->atomMap = null; 23 24 $this->atoms = array();
+2 -2
src/applications/diviner/cache/DivinerDiskCache.php
··· 26 26 * Convert a long-form hash key like `ccbbaaaaaaaaaaaaaaaaaaaaaaaaaaaaN` into 27 27 * a shortened directory form, like `cc/bb/aaaaaaaaN`. In conjunction with 28 28 * @{class:PhutilDirectoryKeyValueCache}, this gives us nice directories 29 - * inside .divinercache instead of a million hash files with huge names at 30 - * top level. 29 + * inside `.divinercache` instead of a million hash files with huge names at 30 + * the top level. 31 31 */ 32 32 protected function getHashKey($hash) { 33 33 return implode(
+1
src/applications/diviner/cache/DivinerPublishCache.php
··· 45 45 46 46 /* -( Index )-------------------------------------------------------------- */ 47 47 48 + 48 49 public function getIndex() { 49 50 if ($this->index === null) { 50 51 $this->index = $this->getCache()->getKey('index', array());
+11
src/applications/diviner/capability/DivinerDefaultEditCapability.php
··· 1 + <?php 2 + 3 + final class DivinerDefaultEditCapability extends PhabricatorPolicyCapability { 4 + 5 + const CAPABILITY = 'diviner.default.edit'; 6 + 7 + public function getCapabilityName() { 8 + return pht('Default Edit Policy'); 9 + } 10 + 11 + }
+15
src/applications/diviner/capability/DivinerDefaultViewCapability.php
··· 1 + <?php 2 + 3 + final class DivinerDefaultViewCapability extends PhabricatorPolicyCapability { 4 + 5 + const CAPABILITY = 'diviner.default.view'; 6 + 7 + public function getCapabilityName() { 8 + return pht('Default View Policy'); 9 + } 10 + 11 + public function shouldAllowPublicPolicySetting() { 12 + return true; 13 + } 14 + 15 + }
+15 -26
src/applications/diviner/controller/DivinerAtomController.php
··· 2 2 3 3 final class DivinerAtomController extends DivinerController { 4 4 5 - private $bookName; 6 - private $atomType; 7 - private $atomName; 8 - private $atomContext; 9 - private $atomIndex; 10 - 11 5 public function shouldAllowPublic() { 12 6 return true; 13 7 } 14 8 15 - public function willProcessRequest(array $data) { 16 - $this->bookName = $data['book']; 17 - $this->atomType = $data['type']; 18 - $this->atomName = $data['name']; 19 - $this->atomContext = nonempty(idx($data, 'context'), null); 20 - $this->atomIndex = nonempty(idx($data, 'index'), null); 21 - } 9 + public function handleRequest(AphrontRequest $request) { 10 + $viewer = $request->getUser(); 22 11 23 - public function processRequest() { 24 - $request = $this->getRequest(); 25 - $viewer = $request->getUser(); 12 + $book_name = $request->getURIData('book'); 13 + $atom_type = $request->getURIData('type'); 14 + $atom_name = $request->getURIData('name'); 15 + $atom_context = nonempty($request->getURIData('context'), null); 16 + $atom_index = nonempty($request->getURIData('index'), null); 26 17 27 18 require_celerity_resource('diviner-shared-css'); 28 19 29 20 $book = id(new DivinerBookQuery()) 30 21 ->setViewer($viewer) 31 - ->withNames(array($this->bookName)) 22 + ->withNames(array($book_name)) 32 23 ->executeOne(); 33 24 34 25 if (!$book) { ··· 38 29 $symbol = id(new DivinerAtomQuery()) 39 30 ->setViewer($viewer) 40 31 ->withBookPHIDs(array($book->getPHID())) 41 - ->withTypes(array($this->atomType)) 42 - ->withNames(array($this->atomName)) 43 - ->withContexts(array($this->atomContext)) 44 - ->withIndexes(array($this->atomIndex)) 32 + ->withTypes(array($atom_type)) 33 + ->withNames(array($atom_name)) 34 + ->withContexts(array($atom_context)) 35 + ->withIndexes(array($atom_index)) 45 36 ->withIsDocumentable(true) 46 37 ->needAtoms(true) 47 38 ->needExtends(true) ··· 75 66 ->setName(DivinerAtom::getAtomTypeNameString( 76 67 $atom ? $atom->getType() : $symbol->getType()))); 77 68 78 - $properties = id(new PHUIPropertyListView()); 69 + $properties = new PHUIPropertyListView(); 79 70 80 71 $group = $atom ? $atom->getProperty('group') : $symbol->getGroupName(); 81 72 if ($group) { ··· 134 125 $document->appendChild( 135 126 id(new PHUIInfoView()) 136 127 ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) 137 - ->appendChild( 138 - pht( 139 - 'This atom no longer exists.'))); 128 + ->appendChild(pht('This atom no longer exists.'))); 140 129 } 141 130 142 131 if ($atom) { ··· 304 293 pht('Implements'), 305 294 phutil_implode_html(phutil_tag('br'), $items)); 306 295 } 307 - 308 296 } 309 297 310 298 private function renderAtomTag(DivinerLiveSymbol $symbol) { ··· 471 459 private function renderFullSignature( 472 460 DivinerLiveSymbol $symbol, 473 461 $is_link = false) { 462 + 474 463 switch ($symbol->getType()) { 475 464 case DivinerAtom::TYPE_CLASS: 476 465 case DivinerAtom::TYPE_INTERFACE:
+3 -8
src/applications/diviner/controller/DivinerAtomListController.php
··· 2 2 3 3 final class DivinerAtomListController extends DivinerController { 4 4 5 - private $key; 6 - 7 5 public function shouldAllowPublic() { 8 6 return true; 9 7 } 10 8 11 - public function willProcessRequest(array $data) { 12 - $this->key = idx($data, 'key', 'all'); 13 - } 9 + public function handleRequest(AphrontRequest $request) { 10 + $query_key = $request->getURIData('key'); 14 11 15 - public function processRequest() { 16 - $request = $this->getRequest(); 17 12 $controller = id(new PhabricatorApplicationSearchController()) 18 - ->setQueryKey($this->key) 13 + ->setQueryKey($query_key) 19 14 ->setSearchEngine(new DivinerAtomSearchEngine()) 20 15 ->setNavigation($this->buildSideNavView()); 21 16
+40 -11
src/applications/diviner/controller/DivinerBookController.php
··· 2 2 3 3 final class DivinerBookController extends DivinerController { 4 4 5 - private $bookName; 6 - 7 5 public function shouldAllowPublic() { 8 6 return true; 9 7 } 10 8 11 - public function willProcessRequest(array $data) { 12 - $this->bookName = $data['book']; 13 - } 9 + public function handleRequest(AphrontRequest $request) { 10 + $viewer = $request->getViewer(); 14 11 15 - public function processRequest() { 16 - $request = $this->getRequest(); 17 - $viewer = $request->getUser(); 12 + $book_name = $request->getURIData('book'); 18 13 19 14 $book = id(new DivinerBookQuery()) 20 15 ->setViewer($viewer) 21 - ->withNames(array($this->bookName)) 16 + ->withNames(array($book_name)) 22 17 ->executeOne(); 23 18 24 19 if (!$book) { 25 20 return new Aphront404Response(); 26 21 } 27 22 23 + $actions = $this->buildActionView($viewer, $book); 24 + 28 25 $crumbs = $this->buildApplicationCrumbs(); 29 26 $crumbs->setBorder(true); 30 - 31 27 $crumbs->addTextCrumb( 32 28 $book->getShortTitle(), 33 29 '/book/'.$book->getName().'/'); 34 30 31 + $action_button = id(new PHUIButtonView()) 32 + ->setTag('a') 33 + ->setText(pht('Actions')) 34 + ->setHref('#') 35 + ->setIconFont('fa-bars') 36 + ->addClass('phui-mobile-menu') 37 + ->setDropdownMenu($actions); 38 + 35 39 $header = id(new PHUIHeaderView()) 36 40 ->setHeader($book->getTitle()) 37 41 ->setUser($viewer) 38 42 ->setPolicyObject($book) 39 - ->setEpoch($book->getDateModified()); 43 + ->setEpoch($book->getDateModified()) 44 + ->addActionLink($action_button); 40 45 41 46 $document = new PHUIDocumentView(); 42 47 $document->setHeader($header); ··· 98 103 array( 99 104 'title' => $book->getTitle(), 100 105 )); 106 + } 107 + 108 + private function buildActionView( 109 + PhabricatorUser $user, 110 + DivinerLiveBook $book) { 111 + 112 + $can_edit = PhabricatorPolicyFilter::hasCapability( 113 + $user, 114 + $book, 115 + PhabricatorPolicyCapability::CAN_EDIT); 116 + 117 + $action_view = id(new PhabricatorActionListView()) 118 + ->setUser($user) 119 + ->setObject($book) 120 + ->setObjectURI($this->getRequest()->getRequestURI()); 121 + 122 + $action_view->addAction( 123 + id(new PhabricatorActionView()) 124 + ->setName(pht('Edit Book')) 125 + ->setIcon('fa-pencil') 126 + ->setHref('/book/'.$book->getName().'/edit/') 127 + ->setDisabled(!$can_edit)); 128 + 129 + return $action_view; 101 130 } 102 131 103 132 }
+117
src/applications/diviner/controller/DivinerBookEditController.php
··· 1 + <?php 2 + 3 + final class DivinerBookEditController extends DivinerController { 4 + 5 + public function handleRequest(AphrontRequest $request) { 6 + $viewer = $request->getViewer(); 7 + 8 + $book_name = $request->getURIData('book'); 9 + 10 + $book = id(new DivinerBookQuery()) 11 + ->setViewer($viewer) 12 + ->requireCapabilities( 13 + array( 14 + PhabricatorPolicyCapability::CAN_VIEW, 15 + PhabricatorPolicyCapability::CAN_EDIT, 16 + )) 17 + ->needProjectPHIDs(true) 18 + ->withNames(array($book_name)) 19 + ->executeOne(); 20 + 21 + if (!$book) { 22 + return new Aphront404Response(); 23 + } 24 + 25 + $view_uri = '/book/'.$book->getName().'/'; 26 + 27 + if ($request->isFormPost()) { 28 + $v_projects = $request->getArr('projectPHIDs'); 29 + $v_view = $request->getStr('viewPolicy'); 30 + $v_edit = $request->getStr('editPolicy'); 31 + 32 + $xactions = array(); 33 + $xactions[] = id(new DivinerLiveBookTransaction()) 34 + ->setTransactionType(PhabricatorTransactions::TYPE_EDGE) 35 + ->setMetadataValue( 36 + 'edge:type', 37 + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) 38 + ->setNewValue( 39 + array( 40 + '=' => array_fuse($v_projects), 41 + )); 42 + $xactions[] = id(new DivinerLiveBookTransaction()) 43 + ->setTransactionType(PhabricatorTransactions::TYPE_VIEW_POLICY) 44 + ->setNewValue($v_view); 45 + $xactions[] = id(new DivinerLiveBookTransaction()) 46 + ->setTransactionType(PhabricatorTransactions::TYPE_EDIT_POLICY) 47 + ->setNewValue($v_edit); 48 + 49 + id(new DivinerLiveBookEditor()) 50 + ->setContinueOnNoEffect(true) 51 + ->setContentSourceFromRequest($request) 52 + ->setActor($viewer) 53 + ->applyTransactions($book, $xactions); 54 + 55 + return id(new AphrontRedirectResponse())->setURI($view_uri); 56 + } 57 + 58 + $crumbs = $this->buildApplicationCrumbs(); 59 + $crumbs->addTextCrumb(pht('Edit Basics')); 60 + 61 + $title = pht('Edit %s', $book->getTitle()); 62 + 63 + $policies = id(new PhabricatorPolicyQuery()) 64 + ->setViewer($viewer) 65 + ->setObject($book) 66 + ->execute(); 67 + $view_capability = PhabricatorPolicyCapability::CAN_VIEW; 68 + $edit_capability = PhabricatorPolicyCapability::CAN_EDIT; 69 + 70 + $form = id(new AphrontFormView()) 71 + ->setUser($viewer) 72 + ->appendControl( 73 + id(new AphrontFormTokenizerControl()) 74 + ->setDatasource(new PhabricatorProjectDatasource()) 75 + ->setName('projectPHIDs') 76 + ->setLabel(pht('Projects')) 77 + ->setValue($book->getProjectPHIDs())) 78 + ->appendChild( 79 + id(new AphrontFormPolicyControl()) 80 + ->setName('viewPolicy') 81 + ->setPolicyObject($book) 82 + ->setCapability($view_capability) 83 + ->setPolicies($policies) 84 + ->setCaption($book->describeAutomaticCapability($view_capability))) 85 + ->appendChild( 86 + id(new AphrontFormPolicyControl()) 87 + ->setName('editPolicy') 88 + ->setPolicyObject($book) 89 + ->setCapability($edit_capability) 90 + ->setPolicies($policies) 91 + ->setCaption($book->describeAutomaticCapability($edit_capability))) 92 + ->appendChild( 93 + id(new AphrontFormSubmitControl()) 94 + ->setValue(pht('Save')) 95 + ->addCancelButton($view_uri)); 96 + 97 + $object_box = id(new PHUIObjectBoxView()) 98 + ->setHeaderText($title) 99 + ->setForm($form); 100 + 101 + $timeline = $this->buildTransactionTimeline( 102 + $book, 103 + new DivinerLiveBookTransactionQuery()); 104 + $timeline->setShouldTerminate(true); 105 + 106 + return $this->buildApplicationPage( 107 + array( 108 + $crumbs, 109 + $object_box, 110 + $timeline, 111 + ), 112 + array( 113 + 'title' => $title, 114 + )); 115 + } 116 + 117 + }
+3 -12
src/applications/diviner/controller/DivinerController.php
··· 3 3 abstract class DivinerController extends PhabricatorController { 4 4 5 5 protected function buildSideNavView() { 6 - $menu = $this->buildMenu(); 6 + $menu = $this->buildApplicationMenu(); 7 7 return AphrontSideNavFilterView::newFromMenu($menu); 8 8 } 9 9 10 10 public function buildApplicationMenu() { 11 - return $this->buildMenu(); 12 - } 13 - 14 - private function buildMenu() { 15 11 $menu = new PHUIListView(); 16 12 17 13 id(new DivinerAtomSearchEngine()) 18 - ->setViewer($this->getRequest()->getUser()) 14 + ->setViewer($this->getRequest()->getViewer()) 19 15 ->addNavigationItems($menu); 20 16 21 17 return $menu; ··· 24 20 protected function renderAtomList(array $symbols) { 25 21 assert_instances_of($symbols, 'DivinerLiveSymbol'); 26 22 27 - $request = $this->getRequest(); 28 - $user = $request->getUser(); 29 - 30 23 $list = array(); 31 24 foreach ($symbols as $symbol) { 32 - 33 25 switch ($symbol->getType()) { 34 26 case DivinerAtom::TYPE_FUNCTION: 35 27 $title = $symbol->getTitle().'()'; ··· 43 35 ->setTitle($title) 44 36 ->setHref($symbol->getURI()) 45 37 ->setSubtitle($symbol->getSummary()) 46 - ->setType(DivinerAtom::getAtomTypeNameString( 47 - $symbol->getType())); 38 + ->setType(DivinerAtom::getAtomTypeNameString($symbol->getType())); 48 39 49 40 $list[] = $item; 50 41 }
+6 -6
src/applications/diviner/controller/DivinerFindController.php
··· 6 6 return true; 7 7 } 8 8 9 - public function processRequest() { 10 - $request = $this->getRequest(); 11 - $viewer = $request->getUser(); 9 + public function handleRequest(AphrontRequest $request) { 10 + $viewer = $request->getViewer(); 12 11 13 - $book_name = $request->getStr('book'); 12 + $book_name = $request->getStr('book'); 14 13 $query_text = $request->getStr('name'); 15 14 16 15 $book = null; ··· 19 18 ->setViewer($viewer) 20 19 ->withNames(array($book_name)) 21 20 ->executeOne(); 21 + 22 22 if (!$book) { 23 23 return new Aphront404Response(); 24 24 } ··· 70 70 ->setTitle(pht('Documentation Not Found')) 71 71 ->appendChild( 72 72 pht( 73 - 'Unable to find the specified documentation. You may have '. 74 - 'followed a bad or outdated link.')) 73 + 'Unable to find the specified documentation. '. 74 + 'You may have followed a bad or outdated link.')) 75 75 ->addCancelButton($not_found_uri, pht('Read More Documentation')); 76 76 77 77 return id(new AphrontDialogResponse())->setDialog($dialog);
+14 -19
src/applications/diviner/controller/DivinerMainController.php
··· 6 6 return true; 7 7 } 8 8 9 - public function processRequest() { 10 - $request = $this->getRequest(); 11 - $viewer = $request->getUser(); 9 + public function handleRequest(AphrontRequest $request) { 10 + $viewer = $request->getViewer(); 12 11 13 12 $books = id(new DivinerBookQuery()) 14 13 ->setViewer($viewer) ··· 31 30 ->setHeader(pht('Documentation Books')) 32 31 ->addActionLink($query_button); 33 32 34 - $document = new PHUIDocumentView(); 35 - $document->setHeader($header); 36 - $document->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS); 37 - $document->addClass('diviner-view'); 33 + $document = id(new PHUIDocumentView()) 34 + ->setHeader($header) 35 + ->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS) 36 + ->addClass('diviner-view'); 38 37 39 38 if ($books) { 40 39 $books = msort($books, 'getTitle'); ··· 54 53 ->appendChild($list); 55 54 56 55 $document->appendChild($list); 57 - 58 56 } else { 59 57 $text = pht( 60 - "(NOTE) **Looking for Phabricator documentation?** If you're looking ". 61 - "for help and information about Phabricator, you can ". 62 - "[[ https://secure.phabricator.com/diviner/ | browse the public ". 63 - "Phabricator documentation ]] on the live site.\n\n". 64 - "Diviner is the documentation generator used to build the Phabricator ". 65 - "documentation.\n\n". 58 + "(NOTE) **Looking for Phabricator documentation?** ". 59 + "If you're looking for help and information about Phabricator, ". 60 + "you can [[https://secure.phabricator.com/diviner/ | ". 61 + "browse the public Phabricator documentation]] on the live site.\n\n". 62 + "Diviner is the documentation generator used to build the ". 63 + "Phabricator documentation.\n\n". 66 64 "You haven't generated any Diviner documentation books yet, so ". 67 65 "there's nothing to show here. If you'd like to generate your own ". 68 66 "local copy of the Phabricator documentation and have it appear ". 69 67 "here, run this command:\n\n". 70 - " phabricator/ $ ./bin/diviner generate\n\n". 71 - "Right now, Diviner isn't very useful for generating documentation ". 72 - "for projects other than Phabricator. If you're interested in using ". 73 - "it in your own projects, leave feedback for us on ". 74 - "[[ https://secure.phabricator.com/T4558 | T4558 ]]."); 68 + " %s\n\n", 69 + 'phabricator/ $ ./bin/diviner generate'); 75 70 76 71 $text = PhabricatorMarkupEngine::renderOneObject( 77 72 id(new PhabricatorMarkupOneOff())->setContent($text),
+23
src/applications/diviner/editor/DivinerLiveBookEditor.php
··· 1 + <?php 2 + 3 + final class DivinerLiveBookEditor 4 + extends PhabricatorApplicationTransactionEditor { 5 + 6 + public function getEditorApplicationClass() { 7 + return 'PhabricatorDivinerApplication'; 8 + } 9 + 10 + public function getEditorObjectsDescription() { 11 + return pht('Diviner Books'); 12 + } 13 + 14 + public function getTransactionTypes() { 15 + $types = parent::getTransactionTypes(); 16 + 17 + $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; 18 + $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; 19 + 20 + return $types; 21 + } 22 + 23 + }
+4
src/applications/diviner/phid/DivinerAtomPHIDType.php
··· 12 12 return new DivinerLiveSymbol(); 13 13 } 14 14 15 + public function getPHIDTypeApplicationClass() { 16 + return 'PhabricatorDivinerApplication'; 17 + } 18 + 15 19 protected function buildQueryForObjects( 16 20 PhabricatorObjectQuery $query, 17 21 array $phids) {
+4
src/applications/diviner/phid/DivinerBookPHIDType.php
··· 12 12 return new DivinerLiveBook(); 13 13 } 14 14 15 + public function getPHIDTypeApplicationClass() { 16 + return 'PhabricatorDivinerApplication'; 17 + } 18 + 15 19 protected function buildQueryForObjects( 16 20 PhabricatorObjectQuery $query, 17 21 array $phids) {
+5 -2
src/applications/diviner/publisher/DivinerLivePublisher.php
··· 8 8 if (!$this->book) { 9 9 $book_name = $this->getConfig('name'); 10 10 11 - $book = id(new DivinerLiveBook())->loadOneWhere('name = %s', $book_name); 11 + $book = id(new DivinerLiveBook())->loadOneWhere( 12 + 'name = %s', 13 + $book_name); 14 + 12 15 if (!$book) { 13 16 $book = id(new DivinerLiveBook()) 14 17 ->setName($book_name) 15 18 ->setViewPolicy(PhabricatorPolicies::getMostOpenPolicy()) 19 + ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN) 16 20 ->save(); 17 21 } 18 22 ··· 144 148 ->setContent(null) 145 149 ->save(); 146 150 } 147 - 148 151 } 149 152 } 150 153
+12 -2
src/applications/diviner/publisher/DivinerPublisher.php
··· 133 133 $created = array_keys($created); 134 134 } 135 135 136 - echo pht('Deleting %d documents.', count($deleted))."\n"; 136 + $console = PhutilConsole::getConsole(); 137 + 138 + $console->writeOut( 139 + "%s\n", 140 + pht( 141 + 'Deleting %s document(s).', 142 + new PhutilNumber(count($deleted)))); 137 143 $this->deleteDocumentsByHash($deleted); 138 144 139 - echo pht('Creating %d documents.', count($created))."\n"; 145 + $console->writeOut( 146 + "%s\n", 147 + pht( 148 + 'Creating %s document(s).', 149 + new PhutilNumber(count($created)))); 140 150 $this->createDocumentsByHash($created); 141 151 } 142 152
+8 -9
src/applications/diviner/query/DivinerAtomQuery.php
··· 79 79 return $this; 80 80 } 81 81 82 - 83 82 /** 84 83 * Include or exclude "ghosts", which are symbols which used to exist but do 85 84 * not exist currently (for example, a function which existed in an older ··· 137 136 foreach ($atoms as $key => $atom) { 138 137 $book = idx($books, $atom->getBookPHID()); 139 138 if (!$book) { 139 + $this->didRejectResult($atom); 140 140 unset($atoms[$key]); 141 141 continue; 142 142 } ··· 158 158 // Load all of the symbols this symbol extends, recursively. Commonly, 159 159 // this means all the ancestor classes and interfaces it extends and 160 160 // implements. 161 - 162 161 if ($this->needExtends) { 163 - 164 162 // First, load all the matching symbols by name. This does 99% of the 165 163 // work in most cases, assuming things are named at all reasonably. 166 - 167 164 $names = array(); 168 165 foreach ($atoms as $atom) { 169 166 if (!$atom->getAtom()) { ··· 303 300 304 301 if ($this->titles) { 305 302 $hashes = array(); 303 + 306 304 foreach ($this->titles as $title) { 307 305 $slug = DivinerAtomRef::normalizeTitleString($title); 308 306 $hash = PhabricatorHash::digestForIndex($slug); ··· 318 316 if ($this->contexts) { 319 317 $with_null = false; 320 318 $contexts = $this->contexts; 319 + 321 320 foreach ($contexts as $key => $value) { 322 321 if ($value === null) { 323 322 unset($contexts[$key]); ··· 373 372 } 374 373 375 374 if ($this->nameContains) { 376 - // NOTE: This CONVERT() call makes queries case-insensitive, since the 377 - // column has binary collation. Eventually, this should move into 375 + // NOTE: This `CONVERT()` call makes queries case-insensitive, since 376 + // the column has binary collation. Eventually, this should move into 378 377 // fulltext. 379 - 380 378 $where[] = qsprintf( 381 379 $conn_r, 382 380 'CONVERT(name USING utf8) LIKE %~', ··· 387 385 388 386 return $this->formatWhereClause($where); 389 387 } 390 - 391 388 392 389 /** 393 390 * Walk a list of atoms and collect all the node hashes of the atoms' ··· 413 410 foreach ($child_hashes as $hash) { 414 411 $hashes[$hash] = $hash; 415 412 } 413 + 416 414 if ($recurse_up) { 417 415 $hashes += $this->getAllChildHashes($symbol->getExtends(), true); 418 416 } ··· 420 418 421 419 return $hashes; 422 420 } 423 - 424 421 425 422 /** 426 423 * Attach child atoms to existing atoms. In recursive mode, also attach child ··· 452 449 $symbol_children[] = $children[$hash]; 453 450 } 454 451 } 452 + 455 453 $symbol->attachChildren($symbol_children); 454 + 456 455 if ($recurse_up) { 457 456 $this->attachAllChildren($symbol->getExtends(), $children, true); 458 457 }
+31
src/applications/diviner/query/DivinerBookQuery.php
··· 6 6 private $phids; 7 7 private $names; 8 8 9 + private $needProjectPHIDs; 10 + 9 11 public function withIDs(array $ids) { 10 12 $this->ids = $ids; 11 13 return $this; ··· 21 23 return $this; 22 24 } 23 25 26 + public function needProjectPHIDs($need_phids) { 27 + $this->needProjectPHIDs = $need_phids; 28 + return $this; 29 + } 30 + 24 31 protected function loadPage() { 25 32 $table = new DivinerLiveBook(); 26 33 $conn_r = $table->establishConnection('r'); ··· 34 41 $this->buildLimitClause($conn_r)); 35 42 36 43 return $table->loadAllFromArray($data); 44 + } 45 + 46 + protected function didFilterPage(array $books) { 47 + assert_instances_of($books, 'DivinerLiveBook'); 48 + 49 + if ($this->needProjectPHIDs) { 50 + $edge_query = id(new PhabricatorEdgeQuery()) 51 + ->withSourcePHIDs(mpull($books, 'getPHID')) 52 + ->withEdgeTypes( 53 + array( 54 + PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, 55 + )); 56 + $edge_query->execute(); 57 + 58 + foreach ($books as $book) { 59 + $project_phids = $edge_query->getDestinationPHIDs( 60 + array( 61 + $book->getPHID(), 62 + )); 63 + $book->attachProjectPHIDs($project_phids); 64 + } 65 + } 66 + 67 + return $books; 37 68 } 38 69 39 70 protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+10
src/applications/diviner/query/DivinerLiveBookTransactionQuery.php
··· 1 + <?php 2 + 3 + final class DivinerLiveBookTransactionQuery 4 + extends PhabricatorApplicationTransactionQuery { 5 + 6 + public function getTemplateApplicationTransaction() { 7 + return new DivinerLiveBookTransaction(); 8 + } 9 + 10 + }
+2 -3
src/applications/diviner/renderer/DivinerDefaultRenderer.php
··· 202 202 } 203 203 204 204 if ($ref->getBook() != $this->getConfig('name')) { 205 - // If the ref is from a different book, we can't normalize it. Just return 206 - // it as-is if it has enough information to resolve. 205 + // If the ref is from a different book, we can't normalize it. 206 + // Just return it as-is if it has enough information to resolve. 207 207 if ($ref->getName() && $ref->getType()) { 208 208 return $ref; 209 209 } else { ··· 259 259 ), 260 260 $ref->getTitle()); 261 261 } 262 - 263 262 264 263 }
+5
src/applications/diviner/search/DivinerBookSearchIndexer.php
··· 18 18 PhabricatorSearchDocumentFieldType::FIELD_BODY, 19 19 $book->getPreface()); 20 20 21 + $this->indexTransactions( 22 + $doc, 23 + new DivinerLiveBookTransactionQuery(), 24 + array($phid)); 25 + 21 26 return $doc; 22 27 } 23 28
+50 -3
src/applications/diviner/storage/DivinerLiveBook.php
··· 3 3 final class DivinerLiveBook extends DivinerDAO 4 4 implements 5 5 PhabricatorPolicyInterface, 6 - PhabricatorDestructibleInterface { 6 + PhabricatorProjectInterface, 7 + PhabricatorDestructibleInterface, 8 + PhabricatorApplicationTransactionInterface { 7 9 8 10 protected $name; 9 11 protected $viewPolicy; 12 + protected $editPolicy; 10 13 protected $configurationData = array(); 14 + 15 + private $projectPHIDs = self::ATTACHABLE; 11 16 12 17 protected function getConfiguration() { 13 18 return array( ··· 63 68 return idx($spec, 'name', $group); 64 69 } 65 70 71 + public function attachProjectPHIDs(array $project_phids) { 72 + $this->projectPHIDs = $project_phids; 73 + return $this; 74 + } 75 + 76 + public function getProjectPHIDs() { 77 + return $this->assertAttached($this->projectPHIDs); 78 + } 79 + 80 + 66 81 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 67 82 83 + 68 84 public function getCapabilities() { 69 85 return array( 70 86 PhabricatorPolicyCapability::CAN_VIEW, 87 + PhabricatorPolicyCapability::CAN_EDIT, 71 88 ); 72 89 } 73 90 74 91 public function getPolicy($capability) { 75 - return PhabricatorPolicies::getMostOpenPolicy(); 92 + switch ($capability) { 93 + case PhabricatorPolicyCapability::CAN_VIEW: 94 + return $this->getViewPolicy(); 95 + case PhabricatorPolicyCapability::CAN_EDIT: 96 + return $this->getEditPolicy(); 97 + } 76 98 } 77 99 78 100 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 79 - return false; 101 + return false; 80 102 } 81 103 82 104 public function describeAutomaticCapability($capability) { 83 105 return null; 84 106 } 85 107 108 + 86 109 /* -( PhabricatorDestructibleInterface )----------------------------------- */ 110 + 87 111 88 112 public function destroyObjectPermanently( 89 113 PhabricatorDestructionEngine $engine) { ··· 100 124 101 125 $this->delete(); 102 126 $this->saveTransaction(); 127 + } 128 + 129 + 130 + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ 131 + 132 + 133 + public function getApplicationTransactionEditor() { 134 + return new DivinerLiveBookEditor(); 135 + } 136 + 137 + public function getApplicationTransactionObject() { 138 + return $this; 139 + } 140 + 141 + public function getApplicationTransactionTemplate() { 142 + return new DivinerLiveBookTransaction(); 143 + } 144 + 145 + public function willRenderTimeline( 146 + PhabricatorApplicationTransactionView $timeline, 147 + AphrontRequest $request) { 148 + 149 + return $timeline; 103 150 } 104 151 105 152 }
+18
src/applications/diviner/storage/DivinerLiveBookTransaction.php
··· 1 + <?php 2 + 3 + final class DivinerLiveBookTransaction 4 + extends PhabricatorApplicationTransaction { 5 + 6 + public function getApplicationName() { 7 + return 'diviner'; 8 + } 9 + 10 + public function getApplicationTransactionType() { 11 + return DivinerBookPHIDType::TYPECONST; 12 + } 13 + 14 + public function getApplicationTransactionCommentObject() { 15 + return null; 16 + } 17 + 18 + }
+11 -14
src/applications/diviner/storage/DivinerLiveSymbol.php
··· 137 137 } 138 138 139 139 public function save() { 140 - 141 140 // NOTE: The identity hash is just a sanity check because the unique tuple 142 - // on this table is way way too long to fit into a normal UNIQUE KEY. We 143 - // don't use it directly, but its existence prevents duplicate records. 141 + // on this table is way way too long to fit into a normal `UNIQUE KEY`. 142 + // We don't use it directly, but its existence prevents duplicate records. 144 143 145 144 if (!$this->identityHash) { 146 145 $this->identityHash = PhabricatorHash::digestForIndex( ··· 159 158 160 159 public function getTitle() { 161 160 $title = parent::getTitle(); 161 + 162 162 if (!strlen($title)) { 163 163 $title = $this->getName(); 164 164 } 165 + 165 166 return $title; 166 167 } 167 168 168 169 public function setTitle($value) { 169 170 $this->writeField('title', $value); 171 + 170 172 if (strlen($value)) { 171 173 $slug = DivinerAtomRef::normalizeTitleString($value); 172 174 $hash = PhabricatorHash::digestForIndex($slug); ··· 174 176 } else { 175 177 $this->titleSlugHash = null; 176 178 } 179 + 177 180 return $this; 178 181 } 179 182 ··· 199 202 200 203 201 204 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 205 + 202 206 203 207 public function getCapabilities() { 204 208 return $this->getBook()->getCapabilities(); 205 209 } 206 210 207 - 208 211 public function getPolicy($capability) { 209 212 return $this->getBook()->getPolicy($capability); 210 213 } 211 - 212 214 213 215 public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { 214 216 return $this->getBook()->hasAutomaticCapability($capability, $viewer); ··· 219 221 } 220 222 221 223 222 - /* -( Markup Interface )--------------------------------------------------- */ 224 + /* -( PhabricatorMarkupInterface )------------------------------------------ */ 223 225 224 226 225 227 public function getMarkupFieldKey($field) { 226 228 return $this->getPHID().':'.$field.':'.$this->getGraphHash(); 227 229 } 228 230 229 - 230 231 public function newMarkupEngine($field) { 231 232 return PhabricatorMarkupEngine::getEngine('diviner'); 232 233 } 233 - 234 234 235 235 public function getMarkupText($field) { 236 236 if (!$this->getAtom()) { ··· 240 240 return $this->getAtom()->getDocblockText(); 241 241 } 242 242 243 - 244 - public function didMarkupText( 245 - $field, 246 - $output, 247 - PhutilMarkupEngine $engine) { 243 + public function didMarkupText($field, $output, PhutilMarkupEngine $engine) { 248 244 return $output; 249 245 } 250 246 251 - 252 247 public function shouldUseMarkupCache($field) { 253 248 return true; 254 249 } 255 250 251 + 256 252 /* -( PhabricatorDestructibleInterface )----------------------------------- */ 253 + 257 254 258 255 public function destroyObjectPermanently( 259 256 PhabricatorDestructionEngine $engine) {
+9
src/applications/diviner/storage/DivinerSchemaSpec.php
··· 1 + <?php 2 + 3 + final class DivinerSchemaSpec extends PhabricatorConfigSchemaSpec { 4 + 5 + public function buildSchemata() { 6 + $this->buildEdgeSchemata(new DivinerLiveBook()); 7 + } 8 + 9 + }
+9 -9
src/applications/diviner/view/DivinerBookItemView.php
··· 43 43 44 44 $title = phutil_tag( 45 45 'span', 46 - array( 47 - 'class' => 'diviner-book-item-title', 48 - ), 46 + array( 47 + 'class' => 'diviner-book-item-title', 48 + ), 49 49 $this->title); 50 50 51 51 $subtitle = phutil_tag( 52 52 'span', 53 - array( 54 - 'class' => 'diviner-book-item-subtitle', 55 - ), 53 + array( 54 + 'class' => 'diviner-book-item-subtitle', 55 + ), 56 56 $this->subtitle); 57 57 58 58 $type = phutil_tag( 59 59 'span', 60 - array( 61 - 'class' => 'diviner-book-item-type', 62 - ), 60 + array( 61 + 'class' => 'diviner-book-item-type', 62 + ), 63 63 $this->type); 64 64 65 65 return array($title, $type, $subtitle);
+5 -3
src/applications/diviner/workflow/DivinerAtomizeWorkflow.php
··· 36 36 37 37 $atomizer_class = $args->getArg('atomizer'); 38 38 if (!$atomizer_class) { 39 - throw new Exception( 40 - pht('Specify an atomizer class with %s.', '--atomizer')); 39 + throw new PhutilArgumentUsageException( 40 + pht( 41 + 'Specify an atomizer class with %s.', 42 + '--atomizer')); 41 43 } 42 44 43 45 $symbols = id(new PhutilSymbolLoader()) ··· 46 48 ->setAncestorClass('DivinerAtomizer') 47 49 ->selectAndLoadSymbols(); 48 50 if (!$symbols) { 49 - throw new Exception( 51 + throw new PhutilArgumentUsageException( 50 52 pht( 51 53 "Atomizer class '%s' must be a concrete subclass of %s.", 52 54 $atomizer_class,
+38 -10
src/applications/diviner/workflow/DivinerGenerateWorkflow.php
··· 50 50 } else { 51 51 $cwd = getcwd(); 52 52 $this->log(pht('FINDING DOCUMENTATION BOOKS')); 53 + 53 54 $books = id(new FileFinder($cwd)) 54 55 ->withType('f') 55 56 ->withSuffix('book') ··· 92 93 // amount of work we can, so that regenerating documentation after minor 93 94 // changes is quick. 94 95 // 95 - // = ATOM CACHE = 96 + // = Atom Cache = 96 97 // 97 98 // In the first stage, we find all the direct changes to source code since 98 99 // the last run. This stage relies on two data structures: ··· 118 119 // its methods). The File Hash Map contains an exhaustive list of all atoms 119 120 // with type "file", but not child atoms of those top-level atoms.) 120 121 // 121 - // = GRAPH CACHE = 122 + // = Graph Cache = 122 123 // 123 124 // We now know which atoms exist, and can compare the Atom Map to some 124 125 // existing cache to figure out what has changed. However, this isn't ··· 176 177 ->setConcreteOnly(true) 177 178 ->setAncestorClass('DivinerPublisher') 178 179 ->selectAndLoadSymbols(); 180 + 179 181 if (!$symbols) { 180 - throw new Exception( 182 + throw new PhutilArgumentUsageException( 181 183 pht( 182 184 "Publisher class '%s' must be a concrete subclass of %s.", 183 185 $publisher_class, ··· 188 190 $this->publishDocumentation($args->getArg('clean'), $publisher); 189 191 } 190 192 193 + 191 194 /* -( Atom Cache )--------------------------------------------------------- */ 195 + 192 196 193 197 private function buildAtomCache() { 194 198 $this->log(pht('BUILDING ATOM CACHE')); 195 199 196 200 $file_hashes = $this->findFilesInProject(); 197 - $this->log(pht('Found %d file(s) in project.', count($file_hashes))); 201 + $this->log( 202 + pht( 203 + 'Found %s file(s) in project.', 204 + new PhutilNumber(count($file_hashes)))); 198 205 199 206 $this->deleteDeadAtoms($file_hashes); 200 207 $atomize = $this->getFilesToAtomize($file_hashes); 201 - $this->log(pht('Found %d unatomized, uncached file(s).', count($atomize))); 208 + $this->log( 209 + pht( 210 + 'Found %s unatomized, uncached file(s).', 211 + new PhutilNumber(count($atomize)))); 202 212 203 213 $file_atomizers = $this->getAtomizersForFiles($atomize); 204 - $this->log(pht('Found %d file(s) to atomize.', count($file_atomizers))); 214 + $this->log( 215 + pht( 216 + 'Found %s file(s) to atomize.', 217 + new PhutilNumber(count($file_atomizers)))); 218 + 205 219 $futures = $this->buildAtomizerFutures($file_atomizers); 206 - $this->log(pht('Atomizing %d file(s).', count($file_atomizers))); 220 + $this->log( 221 + pht( 222 + 'Atomizing %s file(s).', 223 + new PhutilNumber(count($file_atomizers)))); 207 224 208 225 if ($futures) { 209 226 $this->resolveAtomizerFutures($futures, $file_hashes); ··· 344 361 ->setTotal(count($futures)); 345 362 $futures = id(new FutureIterator($futures)) 346 363 ->limit(4); 364 + 347 365 foreach ($futures as $key => $future) { 348 366 try { 349 367 $atoms = $future->resolveJSON(); ··· 396 414 397 415 /* -( Graph Cache )-------------------------------------------------------- */ 398 416 417 + 399 418 private function buildGraphCache() { 400 419 $this->log(pht('BUILDING GRAPH CACHE')); 401 420 ··· 407 426 $dirty_nhashes = array(); 408 427 409 428 $del_atoms = array_diff_key($symbol_map, $atoms); 410 - $this->log(pht('Found %d obsolete atom(s) in graph.', count($del_atoms))); 429 + $this->log( 430 + pht( 431 + 'Found %s obsolete atom(s) in graph.', 432 + new PhutilNumber(count($del_atoms)))); 411 433 412 434 foreach ($del_atoms as $nhash => $shash) { 413 435 $atom_cache->deleteSymbol($nhash); ··· 418 440 } 419 441 420 442 $new_atoms = array_diff_key($atoms, $symbol_map); 421 - $this->log(pht('Found %d new atom(s) in graph.', count($new_atoms))); 443 + $this->log( 444 + pht( 445 + 'Found %s new atom(s) in graph.', 446 + new PhutilNumber(count($new_atoms)))); 422 447 423 448 foreach ($new_atoms as $nhash => $ignored) { 424 449 $shash = $this->computeSymbolHash($nhash); ··· 454 479 } 455 480 } 456 481 457 - $this->log(pht('Found %d affected atoms.', count($dirty_nhashes))); 482 + $this->log( 483 + pht( 484 + 'Found %s affected atoms.', 485 + new PhutilNumber(count($dirty_nhashes)))); 458 486 459 487 foreach ($dirty_nhashes as $nhash => $ignored) { 460 488 $atom_cache->addGraph($nhash, $this->computeGraphHash($nhash));
+46
src/infrastructure/internationalization/translation/PhabricatorUSEnglishTranslation.php
··· 1124 1124 '%s changed package owners, added: %4$s; removed: %6$s.', 1125 1125 ), 1126 1126 1127 + 'Found %s book(s).' => array( 1128 + 'Found %s book.', 1129 + 'Found %s books.', 1130 + ), 1131 + 'Found %s file(s) in project.' => array( 1132 + 'Found %s file in project.', 1133 + 'Found %s files in project.', 1134 + ), 1135 + 'Found %s unatomized, uncached file(s).' => array( 1136 + 'Found %s unatomized, uncached file.', 1137 + 'Found %s unatomized, uncached files.', 1138 + ), 1139 + 'Found %s file(s) to atomize.' => array( 1140 + 'Found %s file to atomize.', 1141 + 'Found %s files to atomize.', 1142 + ), 1143 + 'Atomizing %s file(s).' => array( 1144 + 'Atomizing %s file.', 1145 + 'Atomizing %s files.', 1146 + ), 1147 + 'Creating %s document(s).' => array( 1148 + 'Creating %s document.', 1149 + 'Creating %s documents.', 1150 + ), 1151 + 'Deleting %s document(s).' => array( 1152 + 'Deleting %s document.', 1153 + 'Deleting %s documents.', 1154 + ), 1155 + 'Found %s obsolete atom(s) in graph.' => array( 1156 + 'Found %s obsolete atom in graph.', 1157 + 'Found %s obsolete atoms in graph.', 1158 + ), 1159 + 'Found %s new atom(s) in graph.' => array( 1160 + 'Found %s new atom in graph.', 1161 + 'Found %s new atoms in graph.', 1162 + ), 1163 + 'This call takes %s parameter(s), but only %s are documented.' => array( 1164 + array( 1165 + 'This call takes %s parameter, but only %s is documented.', 1166 + 'This call takes %s parameter, but only %s are documented.', 1167 + ), 1168 + array( 1169 + 'This call takes %s parameters, but only %s is documented.', 1170 + 'This call takes %s parameters, but only %s are documented.', 1171 + ), 1172 + ), 1127 1173 ); 1128 1174 } 1129 1175