Differential D20539 Diff 48970 src/applications/diffusion/remarkup/DiffusionSourceLinkRemarkupRule.php
Changeset View
Changeset View
Standalone View
Standalone View
src/applications/diffusion/remarkup/DiffusionSourceLinkRemarkupRule.php
- This file was added.
<?php | |||||
final class DiffusionSourceLinkRemarkupRule | |||||
extends PhutilRemarkupRule { | |||||
const KEY_SOURCELINKS = 'diffusion.links'; | |||||
public function getPriority() { | |||||
return 200.0; | |||||
} | |||||
public function apply($text) { | |||||
return preg_replace_callback( | |||||
'@{(?:src|source)\b((?:[^}\\\\]+|\\\\.)*)}@m', | |||||
array($this, 'markupSourceLink'), | |||||
$text); | |||||
} | |||||
public function markupSourceLink(array $matches) { | |||||
$engine = $this->getEngine(); | |||||
$text_mode = $engine->isTextMode(); | |||||
$mail_mode = $engine->isHTMLMailMode(); | |||||
if (!$this->isFlatText($matches[0]) || $text_mode || $mail_mode) { | |||||
// We could do better than this in text mode and mail mode, but focus | |||||
// on web mode first. | |||||
return $matches[0]; | |||||
} | |||||
$metadata_key = self::KEY_SOURCELINKS; | |||||
$metadata = $engine->getTextMetadata($metadata_key, array()); | |||||
$token = $engine->storeText($matches[0]); | |||||
$metadata[] = array( | |||||
'token' => $token, | |||||
'raw' => $matches[0], | |||||
'input' => $matches[1], | |||||
); | |||||
$engine->setTextMetadata($metadata_key, $metadata); | |||||
return $token; | |||||
} | |||||
public function didMarkupText() { | |||||
$engine = $this->getEngine(); | |||||
$metadata_key = self::KEY_SOURCELINKS; | |||||
$metadata = $engine->getTextMetadata($metadata_key, array()); | |||||
if (!$metadata) { | |||||
return; | |||||
} | |||||
$viewer = $engine->getConfig('viewer'); | |||||
if (!$viewer) { | |||||
return; | |||||
} | |||||
$defaults = array( | |||||
'repository' => null, | |||||
'line' => null, | |||||
'commit' => null, | |||||
'ref' => null, | |||||
); | |||||
$tags = array(); | |||||
foreach ($metadata as $ref) { | |||||
$token = $ref['token']; | |||||
$raw = $ref['raw']; | |||||
$input = $ref['input']; | |||||
$pattern = | |||||
'(^'. | |||||
'[\s,]*'. | |||||
'(?:"(?P<quotedpath>(?:[^\\\\"]+|\\.)+)"|(?P<rawpath>[^\s,]+))'. | |||||
'[\s,]*'. | |||||
'(?P<options>.*)'. | |||||
'\z)'; | |||||
$matches = null; | |||||
if (!preg_match($pattern, $input, $matches)) { | |||||
$hint_text = pht( | |||||
'Missing path, expected "{src path ...}" in: %s', | |||||
$raw); | |||||
$hint = $this->newSyntaxHint($hint_text); | |||||
$engine->overwriteStoredText($token, $hint); | |||||
continue; | |||||
} | |||||
$path = idx($matches, 'rawpath'); | |||||
if (!strlen($path)) { | |||||
$path = idx($matches, 'quotedpath'); | |||||
$path = stripcslashes($path); | |||||
} | |||||
$parts = explode(':', $path, 2); | |||||
if (count($parts) == 2) { | |||||
$repository = nonempty($parts[0], null); | |||||
$path = $parts[1]; | |||||
} else { | |||||
$repository = null; | |||||
$path = $parts[0]; | |||||
} | |||||
$options = $matches['options']; | |||||
$parser = new PhutilSimpleOptions(); | |||||
$options = $parser->parse($options) + $defaults; | |||||
foreach ($options as $key => $value) { | |||||
if (!array_key_exists($key, $defaults)) { | |||||
$hint_text = pht( | |||||
'Unknown option "%s" in: %s', | |||||
$key, | |||||
$raw); | |||||
$hint = $this->newSyntaxHint($hint_text); | |||||
$engine->overwriteStoredText($token, $hint); | |||||
continue 2; | |||||
} | |||||
} | |||||
if ($options['repository'] !== null) { | |||||
$repository = $options['repository']; | |||||
} | |||||
if ($repository === null) { | |||||
$hint_text = pht( | |||||
'Missing repository, expected "{src repository:path ...}" '. | |||||
'or "{src path repository=...}" in: %s', | |||||
$raw); | |||||
$hint = $this->newSyntaxHint($hint_text); | |||||
$engine->overwriteStoredText($token, $hint); | |||||
continue; | |||||
} | |||||
$tags[] = array( | |||||
'token' => $token, | |||||
'raw' => $raw, | |||||
'identifier' => $repository, | |||||
'path' => $path, | |||||
'options' => $options, | |||||
); | |||||
} | |||||
if (!$tags) { | |||||
return; | |||||
} | |||||
$query = id(new PhabricatorRepositoryQuery()) | |||||
->setViewer($viewer) | |||||
->withIdentifiers(ipull($tags, 'identifier')); | |||||
$query->execute(); | |||||
$repository_map = $query->getIdentifierMap(); | |||||
foreach ($tags as $tag) { | |||||
$token = $tag['token']; | |||||
$identifier = $tag['identifier']; | |||||
$repository = idx($repository_map, $identifier); | |||||
if (!$repository) { | |||||
// For now, just bail out here. Ideally, we should distingiush between | |||||
// restricted and invalid repositories. | |||||
continue; | |||||
} | |||||
$drequest = DiffusionRequest::newFromDictionary( | |||||
array( | |||||
'user' => $viewer, | |||||
'repository' => $repository, | |||||
)); | |||||
$options = $tag['options']; | |||||
$line = $options['line']; | |||||
$commit = $options['commit']; | |||||
$ref_name = $options['ref']; | |||||
$link_uri = $drequest->generateURI( | |||||
array( | |||||
'action' => 'browse', | |||||
'path' => $tag['path'], | |||||
'commit' => $commit, | |||||
'line' => $line, | |||||
'branch' => $ref_name, | |||||
)); | |||||
$view = id(new DiffusionSourceLinkView()) | |||||
->setRepository($repository) | |||||
->setPath($tag['path']) | |||||
->setURI($link_uri); | |||||
if ($line !== null) { | |||||
$view->setLine($line); | |||||
} | |||||
if ($commit !== null) { | |||||
$view->setCommit($commit); | |||||
} | |||||
if ($ref_name !== null) { | |||||
$view->setRefName($ref_name); | |||||
} | |||||
$engine->overwriteStoredText($token, $view); | |||||
} | |||||
} | |||||
private function newSyntaxHint($text) { | |||||
return id(new PHUITagView()) | |||||
->setType(PHUITagView::TYPE_SHADE) | |||||
->setColor('red') | |||||
->setIcon('fa-exclamation-triangle') | |||||
->setName($text); | |||||
} | |||||
} |