@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.

Allow Diviner groups to be configured in `.book` files

Summary:
Ref T988. Currently, every class/function needs to be annotated with `@group`, but 99% of this data can be inferred from file structure, at least in this project. Allow group specifications like:

"paste" : {
"name" : "Paste",
"include" : "(^src/applications/paste/)"
}

..to automatically put everything defined there in the "paste" group. A list of regexps is also supported. Depends on D6855.

Test Plan: Regenerated documentation with `bin/diviner generate --book src/docs/book/phabricator.book --clean`, observed all Paste stuff go in the paste group.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T988

Differential Revision: https://secure.phabricator.com/D6856

+80 -31
+1 -3
src/applications/diviner/atom/DivinerAtom.php
··· 295 295 } 296 296 297 297 public function getRef() { 298 - $group = null; 299 298 $title = null; 300 299 if ($this->docblockMeta) { 301 - $group = $this->getDocblockMetaValue('group'); 302 300 $title = $this->getDocblockMetaValue('title'); 303 301 } 304 302 ··· 308 306 ->setType($this->getType()) 309 307 ->setName($this->getName()) 310 308 ->setTitle($title) 311 - ->setGroup($group); 309 + ->setGroup($this->getProperty('group')); 312 310 } 313 311 314 312 public static function newFromDictionary(array $dictionary) {
+27 -2
src/applications/diviner/atomizer/DivinerAtomizer.php
··· 7 7 8 8 private $book; 9 9 private $fileName; 10 + private $atomContext; 10 11 11 12 /** 12 13 * If you make a significant change to an atomizer, you can bump this ··· 16 17 return 1; 17 18 } 18 19 19 - final public function atomize($file_name, $file_data) { 20 + final public function atomize($file_name, $file_data, array $context) { 20 21 $this->fileName = $file_name; 21 - return $this->executeAtomize($file_name, $file_data); 22 + $this->atomContext = $context; 23 + $atoms = $this->executeAtomize($file_name, $file_data); 24 + 25 + // Promote the "@group" special to a property. If there's no "@group" on 26 + // an atom but the file it's in matches a group pattern, associate it with 27 + // the right group. 28 + foreach ($atoms as $atom) { 29 + $group = null; 30 + try { 31 + $group = $atom->getDocblockMetaValue('group'); 32 + } catch (Exception $ex) { 33 + // There's no docblock metadata. 34 + } 35 + 36 + // If there's no group, but the file matches a group, use that group. 37 + if ($group === null && isset($context['group'])) { 38 + $group = $context['group']; 39 + } 40 + 41 + if ($group !== null) { 42 + $atom->setProperty('group', $group); 43 + } 44 + } 45 + 46 + return $atoms; 22 47 } 23 48 24 49 abstract protected function executeAtomize($file_name, $file_data);
+2
src/applications/diviner/atomizer/DivinerPHPAtomizer.php
··· 89 89 foreach ($attributes as $attribute) { 90 90 $attr = strtolower($attribute->getConcreteString()); 91 91 switch ($attr) { 92 + case 'final': 93 + case 'abstract': 92 94 case 'static': 93 95 $matom->setProperty($attr, true); 94 96 break;
+1 -1
src/applications/diviner/controller/DivinerAtomController.php
··· 81 81 82 82 $properties = id(new PhabricatorPropertyListView()); 83 83 84 - $group = $atom->getDocblockMetaValue('group'); 84 + $group = $atom->getProperty('group'); 85 85 if ($group) { 86 86 $group_name = $book->getGroupName($group); 87 87 } else {
+21 -2
src/applications/diviner/workflow/DivinerAtomizeWorkflow.php
··· 63 63 $configure->setBook($this->getConfig('name')); 64 64 } 65 65 66 + $group_rules = array(); 67 + foreach ($this->getConfig('groups', array()) as $group => $spec) { 68 + $include = (array)idx($spec, 'include', array()); 69 + foreach ($include as $pattern) { 70 + $group_rules[$pattern] = $group; 71 + } 72 + } 73 + 66 74 $all_atoms = array(); 75 + $context = array( 76 + 'group' => null, 77 + ); 67 78 foreach ($files as $file) { 68 79 $abs_path = Filesystem::resolvePath($file, $this->getConfig('root')); 69 80 $data = Filesystem::readFile($abs_path); ··· 75 86 $console->writeLog("Atomizing %s...\n", $file); 76 87 } 77 88 78 - $file_atoms = $file_atomizer->atomize($file, $data); 89 + $context['group'] = null; 90 + foreach ($group_rules as $rule => $group) { 91 + if (preg_match($rule, $file)) { 92 + $context['group'] = $group; 93 + break; 94 + } 95 + } 96 + 97 + $file_atoms = $file_atomizer->atomize($file, $data, $context); 79 98 $all_atoms[] = $file_atoms; 80 99 81 100 if (count($file_atoms) !== 1) { ··· 83 102 } 84 103 $file_atom = head($file_atoms); 85 104 86 - $atoms = $atomizer->atomize($file, $data); 105 + $atoms = $atomizer->atomize($file, $data, $context); 87 106 88 107 foreach ($atoms as $atom) { 89 108 if (!$atom->getParentHash()) {
+1 -16
src/applications/diviner/workflow/DivinerGenerateWorkflow.php
··· 205 205 '/\\.php$/' => 'DivinerPHPAtomizer', 206 206 )); 207 207 208 - foreach ($rules as $rule => $atomizer) { 209 - if (@preg_match($rule, '') === false) { 210 - throw new Exception( 211 - "Rule '{$rule}' is not a valid regular expression!"); 212 - } 213 - } 214 - 215 208 return $rules; 216 209 } 217 210 218 211 private function getExclude() { 219 - $exclude = $this->getConfig('exclude', array()); 220 - 221 - foreach ($exclude as $rule) { 222 - if (@preg_match($rule, '') === false) { 223 - throw new Exception( 224 - "Exclude rule '{$rule}' is not a valid regular expression!"); 225 - } 226 - } 227 - 212 + $exclude = (array)$this->getConfig('exclude', array()); 228 213 return $exclude; 229 214 } 230 215
+23 -7
src/applications/diviner/workflow/DivinerWorkflow.php
··· 35 35 "Book configuration '{$book_path}' is not in JSON format."); 36 36 } 37 37 38 + PhutilTypeSpec::checkMap( 39 + $book, 40 + array( 41 + 'name' => 'string', 42 + 'title' => 'optional string', 43 + 'short' => 'optional string', 44 + 'root' => 'optional string', 45 + 'uri.source' => 'optional string', 46 + 'rules' => 'optional map<regex, string>', 47 + 'exclude' => 'optional regex|list<regex>', 48 + 'groups' => 'optional map<string, map<string, wild>>', 49 + )); 50 + 38 51 // If the book specifies a "root", resolve it; otherwise, use the directory 39 52 // the book configuration file lives in. 40 53 $full_path = dirname(Filesystem::resolvePath($book_path)); ··· 43 56 } 44 57 $book['root'] = Filesystem::resolvePath($book['root'], $full_path); 45 58 46 - // Make sure we have a valid book name. 47 - if (!isset($book['name'])) { 48 - throw new PhutilArgumentUsageException( 49 - "Book configuration '{$book_path}' is missing required ". 50 - "property 'name'."); 51 - } 52 - 53 59 if (!preg_match('/^[a-z][a-z-]*$/', $book['name'])) { 54 60 $name = $book['name']; 55 61 throw new PhutilArgumentUsageException( 56 62 "Book configuration '{$book_path}' has name '{$name}', but book names ". 57 63 "must include only lowercase letters and hyphens."); 64 + } 65 + 66 + foreach (idx($book, 'groups', array()) as $group) { 67 + PhutilTypeSpec::checkmap( 68 + $group, 69 + array( 70 + 'name' => 'string', 71 + 'include' => 'optional regex|list<regex>', 72 + )); 73 + 58 74 } 59 75 60 76 $this->bookConfigPath = $book_path;
+4
src/docs/book/phabricator.book
··· 17 17 "(^src/docs/user/)" 18 18 ], 19 19 "groups" : { 20 + "paste" : { 21 + "name" : "Paste", 22 + "include" : "(^src/applications/paste/)" 23 + } 20 24 } 21 25 }