Page MenuHomePhabricator

D8953.diff
No OneTemporary

D8953.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -257,6 +257,7 @@
'PhutilRemarkupEngineRemarkupLiteralBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupLiteralBlockRule.php',
'PhutilRemarkupEngineRemarkupNoteBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupNoteBlockRule.php',
'PhutilRemarkupEngineRemarkupQuotesBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupQuotesBlockRule.php',
+ 'PhutilRemarkupEngineRemarkupReplyBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupReplyBlockRule.php',
'PhutilRemarkupEngineRemarkupSimpleTableBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupSimpleTableBlockRule.php',
'PhutilRemarkupEngineRemarkupTableBlockRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTableBlockRule.php',
'PhutilRemarkupEngineRemarkupTestInterpreterRule' => 'markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTestInterpreterRule.php',
@@ -628,6 +629,7 @@
'PhutilRemarkupEngineRemarkupLiteralBlockRule' => 'PhutilRemarkupEngineBlockRule',
'PhutilRemarkupEngineRemarkupNoteBlockRule' => 'PhutilRemarkupEngineBlockRule',
'PhutilRemarkupEngineRemarkupQuotesBlockRule' => 'PhutilRemarkupEngineBlockRule',
+ 'PhutilRemarkupEngineRemarkupReplyBlockRule' => 'PhutilRemarkupEngineBlockRule',
'PhutilRemarkupEngineRemarkupSimpleTableBlockRule' => 'PhutilRemarkupEngineBlockRule',
'PhutilRemarkupEngineRemarkupTableBlockRule' => 'PhutilRemarkupEngineBlockRule',
'PhutilRemarkupEngineRemarkupTestInterpreterRule' => 'PhutilRemarkupBlockInterpreter',
diff --git a/src/markup/engine/PhutilRemarkupEngine.php b/src/markup/engine/PhutilRemarkupEngine.php
--- a/src/markup/engine/PhutilRemarkupEngine.php
+++ b/src/markup/engine/PhutilRemarkupEngine.php
@@ -1,16 +1,12 @@
<?php
-/**
- * @group markup
- */
final class PhutilRemarkupEngine extends PhutilMarkupEngine {
const MODE_DEFAULT = 0;
const MODE_TEXT = 1;
- /**
- * @var PhutilRemarkupEngineBlockRule[]
- */
+ const MAX_CHILD_DEPTH = 8;
+
private $blockRules = array();
private $config = array();
private $mode;
@@ -119,14 +115,35 @@
$this->metadata = array();
$this->storage = new PhutilRemarkupBlockStorage();
+ $blocks = $this->splitTextIntoBlocks($text);
+
+ $output = array();
+ foreach ($blocks as $block) {
+ $output[] = $this->markupBlock($block);
+ }
+ $output = $this->flattenOutput($output);
+
+ $map = $this->storage->getMap();
+ unset($this->storage);
+ $metadata = $this->metadata;
+
+
+ return array(
+ 'output' => $output,
+ 'storage' => $map,
+ 'metadata' => $metadata,
+ );
+ }
+
+ private function splitTextIntoBlocks($text, $depth = 0) {
// Apply basic block and paragraph normalization to the text. NOTE: We don't
// strip trailing whitespace because it is semantic in some contexts,
// notably inlined diffs that the author intends to show as a code block.
- $text = phutil_split_lines($text, true);
+ $text = phutil_split_lines($text, true);
$block_rules = $this->blockRules;
- $blocks = array();
- $cursor = 0;
- $prev_block = array();
+ $blocks = array();
+ $cursor = 0;
+ $prev_block = array();
while (isset($text[$cursor])) {
$starting_cursor = $cursor;
@@ -139,10 +156,11 @@
}
$curr_block = array(
- "start" => $cursor,
- "num_lines" => $num_lines,
- "rule" => $block_rule,
- "is_empty" => self::isEmptyBlock($text, $cursor, $num_lines),
+ 'start' => $cursor,
+ 'num_lines' => $num_lines,
+ 'rule' => $block_rule,
+ 'is_empty' => self::isEmptyBlock($text, $cursor, $num_lines),
+ 'children' => array(),
);
if ($prev_block
@@ -164,27 +182,58 @@
}
}
- $output = array();
- foreach ($blocks as $block) {
- $output[] = $block['rule']->markupText(
- implode('', array_slice($text, $block['start'], $block['num_lines'])));
+ foreach ($blocks as $key => $block) {
+ $lines = array_slice($text, $block['start'], $block['num_lines']);
+ $blocks[$key]['text'] = implode('', $lines);
}
- $map = $this->storage->getMap();
- unset($this->storage);
- $metadata = $this->metadata;
+ // Stop splitting child blocks apart if we get too deep. This arrests
+ // any blocks which have looping child rules, and stops the stack from
+ // exploding if someone writes a hilarious comment with 5,000 levels of
+ // quoted text.
+
+ if ($depth < self::MAX_CHILD_DEPTH) {
+ foreach ($blocks as $key => $block) {
+ $rule = $block['rule'];
+ if (!$rule->supportsChildBlocks()) {
+ continue;
+ }
+
+ list($parent_text, $child_text) = $rule->extractChildText(
+ $block['text']);
+ $blocks[$key]['text'] = $parent_text;
+ $blocks[$key]['children'] = $this->splitTextIntoBlocks(
+ $child_text,
+ $depth + 1);
+ }
+ }
+
+ return $blocks;
+ }
+
+ private function markupBlock(array $block) {
+ $children = array();
+ foreach ($block['children'] as $child) {
+ $children[] = $this->markupBlock($child);
+ }
+ if ($children) {
+ $children = $this->flattenOutput($children);
+ } else {
+ $children = null;
+ }
+
+ return $block['rule']->markupText($block['text'], $children);
+ }
+
+ private function flattenOutput(array $output) {
if ($this->isTextMode()) {
$output = implode("\n\n", $output)."\n";
} else {
$output = phutil_implode_html("\n\n", $output);
}
- return array(
- 'output' => $output,
- 'storage' => $map,
- 'metadata' => $metadata,
- );
+ return $output;
}
private static function shouldMergeBlocks($text, $prev_block, $curr_block) {
diff --git a/src/markup/engine/__tests__/PhutilRemarkupEngineTestCase.php b/src/markup/engine/__tests__/PhutilRemarkupEngineTestCase.php
--- a/src/markup/engine/__tests__/PhutilRemarkupEngineTestCase.php
+++ b/src/markup/engine/__tests__/PhutilRemarkupEngineTestCase.php
@@ -90,6 +90,7 @@
$blocks = array();
$blocks[] = new PhutilRemarkupEngineRemarkupQuotesBlockRule();
+ $blocks[] = new PhutilRemarkupEngineRemarkupReplyBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupHeaderBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupHorizontalRuleBlockRule();
$blocks[] = new PhutilRemarkupEngineRemarkupCodeBlockRule();
diff --git a/src/markup/engine/__tests__/remarkup/reply-basic.txt b/src/markup/engine/__tests__/remarkup/reply-basic.txt
new file mode 100644
--- /dev/null
+++ b/src/markup/engine/__tests__/remarkup/reply-basic.txt
@@ -0,0 +1,12 @@
+>>! In comment #123, alincoln wrote:
+> Four score and twenty years ago...
+~~~~~~~~~~
+<blockquote class="remarkup-reply-block">
+<div class="remarkup-reply-head">In comment #123, alincoln wrote:</div>
+<div class="remarkup-reply-body"><p>Four score and twenty years ago...</p></div>
+</blockquote>
+~~~~~~~~~~
+In comment #123, alincoln wrote:
+
+> Four score and twenty years ago...
+
diff --git a/src/markup/engine/__tests__/remarkup/reply-nested.txt b/src/markup/engine/__tests__/remarkup/reply-nested.txt
new file mode 100644
--- /dev/null
+++ b/src/markup/engine/__tests__/remarkup/reply-nested.txt
@@ -0,0 +1,50 @@
+>>! Previously, fruit:
+>
+> - Apple
+> - Banana
+> - Cherry
+>
+>>>! More previously, vegetables:
+>>
+>> - Potato
+>> - Potato
+>> - Potato
+>
+> The end.
+
+~~~~~~~~~~
+<blockquote class="remarkup-reply-block">
+<div class="remarkup-reply-head">Previously, fruit:</div>
+<div class="remarkup-reply-body"><ul>
+<li>Apple</li>
+<li>Banana</li>
+<li>Cherry</li>
+</ul>
+
+<blockquote class="remarkup-reply-block">
+<div class="remarkup-reply-head">More previously, vegetables:</div>
+<div class="remarkup-reply-body"><ul>
+<li>Potato</li>
+<li>Potato</li>
+<li>Potato</li>
+</ul></div>
+</blockquote>
+
+<p>The end.</p></div>
+</blockquote>
+~~~~~~~~~~
+Previously, fruit:
+
+> - Apple
+> - Banana
+> - Cherry
+>
+> More previously, vegetables:
+>
+> > - Potato
+> > - Potato
+> > - Potato
+>
+>
+> The end.
+
diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineBlockRule.php
--- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineBlockRule.php
+++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineBlockRule.php
@@ -24,7 +24,7 @@
return 500.0;
}
- abstract public function markupText($text);
+ abstract public function markupText($text, $children);
/**
* This will get an array of unparsed lines and return the number of lines
@@ -84,6 +84,14 @@
return $text;
}
+ public function supportsChildBlocks() {
+ return false;
+ }
+
+ public function extractChildText($text) {
+ throw new Exception(pht('Not implemnted!'));
+ }
+
protected function renderRemarkupTable(array $out_rows) {
assert_instances_of($out_rows, 'array');
diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupCodeBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupCodeBlockRule.php
--- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupCodeBlockRule.php
+++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupCodeBlockRule.php
@@ -46,7 +46,7 @@
return $num_lines;
}
- public function markupText($text) {
+ public function markupText($text, $children) {
if (preg_match('/^```/', $text)) {
// If this is a ```-style block, trim off the backticks and any leading
// blank line.
diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupDefaultBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupDefaultBlockRule.php
--- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupDefaultBlockRule.php
+++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupDefaultBlockRule.php
@@ -14,7 +14,7 @@
return 1;
}
- public function markupText($text) {
+ public function markupText($text, $children) {
$text = trim($text);
$text = $this->applyRules($text);
diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHeaderBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHeaderBlockRule.php
--- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHeaderBlockRule.php
+++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHeaderBlockRule.php
@@ -33,7 +33,7 @@
const KEY_HEADER_TOC = 'headers.toc';
- public function markupText($text) {
+ public function markupText($text, $children) {
$text = trim($text);
$lines = phutil_split_lines($text);
diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHorizontalRuleBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHorizontalRuleBlockRule.php
--- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHorizontalRuleBlockRule.php
+++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupHorizontalRuleBlockRule.php
@@ -29,7 +29,7 @@
return $num_lines;
}
- public function markupText($text) {
+ public function markupText($text, $children) {
return phutil_tag('hr', array());
}
diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInlineBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInlineBlockRule.php
--- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInlineBlockRule.php
+++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInlineBlockRule.php
@@ -10,7 +10,7 @@
return 1;
}
- public function markupText($text) {
+ public function markupText($text, $children) {
return $this->applyRules($text);
}
diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInterpreterRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInterpreterRule.php
--- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInterpreterRule.php
+++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupInterpreterRule.php
@@ -27,7 +27,7 @@
return $num_lines;
}
- public function markupText($text) {
+ public function markupText($text, $children) {
$lines = explode("\n", $text);
$first_key = head_key($lines);
diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupListBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupListBlockRule.php
--- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupListBlockRule.php
+++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupListBlockRule.php
@@ -58,7 +58,7 @@
const CONT_BLOCK_PATTERN = '@^\s*(?:[-*#]+|[0-9]+[.)]|\[.?\])\s+@';
const STRIP_BLOCK_PATTERN = '@^\s*(?:[-*#]+|[0-9]+[.)])\s*@';
- public function markupText($text) {
+ public function markupText($text, $children) {
$items = array();
$lines = explode("\n", $text);
diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupLiteralBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupLiteralBlockRule.php
--- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupLiteralBlockRule.php
+++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupLiteralBlockRule.php
@@ -24,7 +24,7 @@
return $num_lines;
}
- public function markupText($text) {
+ public function markupText($text, $children) {
$text = preg_replace('/%%%\s*$/', '', substr($text, 3));
if ($this->getEngine()->isTextMode()) {
return $text;
diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupNoteBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupNoteBlockRule.php
--- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupNoteBlockRule.php
+++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupNoteBlockRule.php
@@ -26,7 +26,7 @@
return $num_lines;
}
- public function markupText($text) {
+ public function markupText($text, $children) {
$matches = array();
preg_match($this->getRegEx(), $text, $matches);
diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupQuotesBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupQuotesBlockRule.php
--- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupQuotesBlockRule.php
+++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupQuotesBlockRule.php
@@ -27,7 +27,7 @@
return $num_lines;
}
- public function markupText($text) {
+ public function markupText($text, $children) {
$lines = array();
foreach (explode("\n", $text) as $line) {
$lines[] = $this->applyRules(preg_replace('/^>\s*/', '', $line));
diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupReplyBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupReplyBlockRule.php
new file mode 100644
--- /dev/null
+++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupReplyBlockRule.php
@@ -0,0 +1,93 @@
+<?php
+
+final class PhutilRemarkupEngineRemarkupReplyBlockRule
+ extends PhutilRemarkupEngineBlockRule {
+
+ public function getPriority() {
+ return 400.0;
+ }
+
+ public function getMatchingLineCount(array $lines, $cursor) {
+ $pos = $cursor;
+
+ if (preg_match('/^>>!/', $lines[$pos])) {
+ do {
+ ++$pos;
+ } while (isset($lines[$pos]) && preg_match('/^>/', $lines[$pos]));
+ }
+
+ return ($pos - $cursor);
+ }
+
+ public function supportsChildBlocks() {
+ return true;
+ }
+
+ public function extractChildText($text) {
+ $text = phutil_split_lines($text, true);
+
+ $head = array();
+ $body = array();
+
+ $head = substr(reset($text), 3);
+
+ $body = array_slice($text, 1);
+
+ // Remove the carets.
+ foreach ($body as $key => $line) {
+ $body[$key] = substr($line, 1);
+ }
+
+ // Strip leading empty lines.
+ foreach ($body as $key => $line) {
+ if (strlen(trim($line))) {
+ break;
+ }
+ unset($body[$key]);
+ }
+
+ return array(trim($head), implode('', $body));
+ }
+
+ public function markupText($text, $children) {
+ $text = $this->applyRules($text);
+
+ if ($this->getEngine()->isTextMode()) {
+ $children = phutil_split_lines($children, true);
+ foreach ($children as $key => $child) {
+ if (strlen(trim($child))) {
+ $children[$key] = '> '.$child;
+ } else {
+ $children[$key] = '>'.$child;
+ }
+ }
+ $children = implode('', $children);
+
+ return $text."\n\n".$children;
+ }
+
+ return phutil_tag(
+ 'blockquote',
+ array(
+ 'class' => 'remarkup-reply-block',
+ ),
+ array(
+ "\n",
+ phutil_tag(
+ 'div',
+ array(
+ 'class' => 'remarkup-reply-head',
+ ),
+ $text),
+ "\n",
+ phutil_tag(
+ 'div',
+ array(
+ 'class' => 'remarkup-reply-body',
+ ),
+ $children),
+ "\n",
+ ));
+ }
+
+}
diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupSimpleTableBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupSimpleTableBlockRule.php
--- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupSimpleTableBlockRule.php
+++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupSimpleTableBlockRule.php
@@ -20,7 +20,7 @@
return $num_lines;
}
- public function markupText($text) {
+ public function markupText($text, $children) {
$matches = array();
$rows = array();
diff --git a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTableBlockRule.php b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTableBlockRule.php
--- a/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTableBlockRule.php
+++ b/src/markup/engine/remarkup/blockrule/PhutilRemarkupEngineRemarkupTableBlockRule.php
@@ -25,7 +25,7 @@
return $num_lines;
}
- public function markupText($text) {
+ public function markupText($text, $children) {
$matches = array();
if (!preg_match('@^<table>(.*)</table>$@si', $text, $matches)) {

File Metadata

Mime Type
text/plain
Expires
May 12 2024, 4:45 AM (4 w, 6 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6289257
Default Alt Text
D8953.diff (18 KB)

Event Timeline