Changeset View
Changeset View
Standalone View
Standalone View
src/applications/files/document/PhabricatorJupyterDocumentEngine.php
Show First 20 Lines • Show All 79 Lines • ▼ Show 20 Lines | public function newBlockDiffViews( | ||||
$vcell = $vblock->getContent(); | $vcell = $vblock->getContent(); | ||||
$utype = idx($ucell, 'cell_type'); | $utype = idx($ucell, 'cell_type'); | ||||
$vtype = idx($vcell, 'cell_type'); | $vtype = idx($vcell, 'cell_type'); | ||||
if ($utype === $vtype) { | if ($utype === $vtype) { | ||||
switch ($utype) { | switch ($utype) { | ||||
case 'markdown': | case 'markdown': | ||||
$usource = idx($ucell, 'source'); | $usource = $this->readString($ucell, 'source'); | ||||
if (is_array($usource)) { | $vsource = $this->readString($vcell, 'source'); | ||||
$usource = implode('', $usource); | |||||
} | |||||
$vsource = idx($vcell, 'source'); | |||||
if (is_array($vsource)) { | |||||
$vsource = implode('', $vsource); | |||||
} | |||||
$diff = id(new PhutilProseDifferenceEngine()) | $diff = id(new PhutilProseDifferenceEngine()) | ||||
->getDiff($usource, $vsource); | ->getDiff($usource, $vsource); | ||||
$u_content = $this->newProseDiffCell($diff, array('=', '-')); | $u_content = $this->newProseDiffCell($diff, array('=', '-')); | ||||
$v_content = $this->newProseDiffCell($diff, array('=', '+')); | $v_content = $this->newProseDiffCell($diff, array('=', '+')); | ||||
$u_content = $this->newJupyterCell(null, $u_content, null); | $u_content = $this->newJupyterCell(null, $u_content, null); | ||||
$v_content = $this->newJupyterCell(null, $v_content, null); | $v_content = $this->newJupyterCell(null, $v_content, null); | ||||
$u_content = $this->newCellContainer($u_content); | $u_content = $this->newCellContainer($u_content); | ||||
$v_content = $this->newCellContainer($v_content); | $v_content = $this->newCellContainer($v_content); | ||||
return id(new PhabricatorDocumentEngineBlockDiff()) | return id(new PhabricatorDocumentEngineBlockDiff()) | ||||
->setOldContent($u_content) | ->setOldContent($u_content) | ||||
->addOldClass('old') | ->addOldClass('old') | ||||
->setNewContent($v_content) | ->setNewContent($v_content) | ||||
->addNewClass('new'); | ->addNewClass('new'); | ||||
case 'code/line': | case 'code/line': | ||||
$usource = idx($ucell, 'raw'); | $usource = idx($ucell, 'raw'); | ||||
$vsource = idx($vcell, 'raw'); | $vsource = idx($vcell, 'raw'); | ||||
$udisplay = idx($ucell, 'display'); | $udisplay = idx($ucell, 'display'); | ||||
$vdisplay = idx($vcell, 'display'); | $vdisplay = idx($vcell, 'display'); | ||||
$ulabel = idx($ucell, 'label'); | |||||
$vlabel = idx($vcell, 'label'); | |||||
$intraline_segments = ArcanistDiffUtils::generateIntralineDiff( | $intraline_segments = ArcanistDiffUtils::generateIntralineDiff( | ||||
$usource, | $usource, | ||||
$vsource); | $vsource); | ||||
$u_segments = array(); | $u_segments = array(); | ||||
foreach ($intraline_segments[0] as $u_segment) { | foreach ($intraline_segments[0] as $u_segment) { | ||||
$u_segments[] = $u_segment; | $u_segments[] = $u_segment; | ||||
} | } | ||||
$v_segments = array(); | $v_segments = array(); | ||||
foreach ($intraline_segments[1] as $v_segment) { | foreach ($intraline_segments[1] as $v_segment) { | ||||
$v_segments[] = $v_segment; | $v_segments[] = $v_segment; | ||||
} | } | ||||
$usource = PhabricatorDifferenceEngine::applyIntralineDiff( | $usource = PhabricatorDifferenceEngine::applyIntralineDiff( | ||||
$udisplay, | $udisplay, | ||||
$u_segments); | $u_segments); | ||||
$vsource = PhabricatorDifferenceEngine::applyIntralineDiff( | $vsource = PhabricatorDifferenceEngine::applyIntralineDiff( | ||||
$vdisplay, | $vdisplay, | ||||
$v_segments); | $v_segments); | ||||
$u_content = $this->newCodeLineCell($ucell, $usource); | list($u_label, $u_content) = $this->newCodeLineCell($ucell, $usource); | ||||
$v_content = $this->newCodeLineCell($vcell, $vsource); | list($v_label, $v_content) = $this->newCodeLineCell($vcell, $vsource); | ||||
$classes = array( | $classes = array( | ||||
'jupyter-cell-flush', | 'jupyter-cell-flush', | ||||
); | ); | ||||
$u_content = $this->newJupyterCell($ulabel, $u_content, $classes); | $u_content = $this->newJupyterCell($u_label, $u_content, $classes); | ||||
$v_content = $this->newJupyterCell($vlabel, $v_content, $classes); | $v_content = $this->newJupyterCell($v_label, $v_content, $classes); | ||||
$u_content = $this->newCellContainer($u_content); | $u_content = $this->newCellContainer($u_content); | ||||
$v_content = $this->newCellContainer($v_content); | $v_content = $this->newCellContainer($v_content); | ||||
return id(new PhabricatorDocumentEngineBlockDiff()) | return id(new PhabricatorDocumentEngineBlockDiff()) | ||||
->setOldContent($u_content) | ->setOldContent($u_content) | ||||
->addOldClass('old') | ->addOldClass('old') | ||||
->setNewContent($v_content) | ->setNewContent($v_content) | ||||
▲ Show 20 Lines • Show All 92 Lines • ▼ Show 20 Lines | foreach ($cells as $cell) { | ||||
// When the cell is a source code line, we can hash just the raw | // When the cell is a source code line, we can hash just the raw | ||||
// input rather than all the cell metadata. | // input rather than all the cell metadata. | ||||
switch (idx($cell, 'cell_type')) { | switch (idx($cell, 'cell_type')) { | ||||
case 'code/line': | case 'code/line': | ||||
$hash_input = $cell['raw']; | $hash_input = $cell['raw']; | ||||
break; | break; | ||||
case 'markdown': | case 'markdown': | ||||
$hash_input = $cell['source']; | $hash_input = $this->readString($cell, 'source'); | ||||
if (is_array($hash_input)) { | |||||
$hash_input = implode('', $cell['source']); | |||||
} | |||||
break; | break; | ||||
default: | default: | ||||
$hash_input = serialize($cell); | $hash_input = serialize($cell); | ||||
break; | break; | ||||
} | } | ||||
$hash = PhabricatorHash::digestWithNamedKey( | $hash = PhabricatorHash::digestWithNamedKey( | ||||
$hash_input, | $hash_input, | ||||
▲ Show 20 Lines • Show All 55 Lines • ▼ Show 20 Lines | private function newCells($content, $for_diff) { | ||||
if (!is_array($data)) { | if (!is_array($data)) { | ||||
throw new Exception( | throw new Exception( | ||||
pht( | pht( | ||||
'This document does not encode a valid JSON object and can not '. | 'This document does not encode a valid JSON object and can not '. | ||||
'be rendered as a Jupyter notebook.')); | 'be rendered as a Jupyter notebook.')); | ||||
} | } | ||||
$nbformat = idx($data, 'nbformat'); | $nbformat = idx($data, 'nbformat'); | ||||
if (!strlen($nbformat)) { | if (!strlen($nbformat)) { | ||||
throw new Exception( | throw new Exception( | ||||
pht( | pht( | ||||
'This document is missing an "nbformat" field. Jupyter notebooks '. | 'This document is missing an "nbformat" field. Jupyter notebooks '. | ||||
'must have this field.')); | 'must have this field.')); | ||||
} | } | ||||
Show All 25 Lines | private function newCells($content, $for_diff) { | ||||
// If we're extracting cells to build a diff view, split code cells into | // If we're extracting cells to build a diff view, split code cells into | ||||
// individual lines and individual outputs. We want users to be able to | // individual lines and individual outputs. We want users to be able to | ||||
// add inline comments to each line and each output block. | // add inline comments to each line and each output block. | ||||
$results = array(); | $results = array(); | ||||
foreach ($cells as $cell) { | foreach ($cells as $cell) { | ||||
$cell_type = idx($cell, 'cell_type'); | $cell_type = idx($cell, 'cell_type'); | ||||
if ($cell_type === 'markdown') { | if ($cell_type === 'markdown') { | ||||
$source = $cell['source']; | $source = $this->readString($cell, 'source'); | ||||
if (is_array($source)) { | |||||
$source = implode('', $source); | |||||
} | |||||
// Attempt to split contiguous blocks of markdown into smaller | // Attempt to split contiguous blocks of markdown into smaller | ||||
// pieces. | // pieces. | ||||
$chunks = preg_split( | $chunks = preg_split( | ||||
'/\n\n+/', | '/\n\n+/', | ||||
$source); | $source); | ||||
foreach ($chunks as $chunk) { | foreach ($chunks as $chunk) { | ||||
$result = $cell; | $result = $cell; | ||||
$result['source'] = array($chunk); | $result['source'] = array($chunk); | ||||
$results[] = $result; | $results[] = $result; | ||||
} | } | ||||
continue; | continue; | ||||
} | } | ||||
if ($cell_type !== 'code') { | if ($cell_type !== 'code') { | ||||
$results[] = $cell; | $results[] = $cell; | ||||
continue; | continue; | ||||
} | } | ||||
$label = $this->newCellLabel($cell); | $label = $this->newCellLabel($cell); | ||||
$lines = idx($cell, 'source'); | $lines = $this->readStringList($cell, 'source'); | ||||
if (!is_array($lines)) { | |||||
$lines = array(); | |||||
} | |||||
$content = $this->highlightLines($lines); | $content = $this->highlightLines($lines); | ||||
$count = count($lines); | $count = count($lines); | ||||
for ($ii = 0; $ii < $count; $ii++) { | for ($ii = 0; $ii < $count; $ii++) { | ||||
$is_head = ($ii === 0); | $is_head = ($ii === 0); | ||||
$is_last = ($ii === ($count - 1)); | $is_last = ($ii === ($count - 1)); | ||||
if ($is_head) { | if ($is_head) { | ||||
▲ Show 20 Lines • Show All 101 Lines • ▼ Show 20 Lines | return array( | ||||
array( | array( | ||||
'class' => 'jupyter-cell-raw PhabricatorMonospaced', | 'class' => 'jupyter-cell-raw PhabricatorMonospaced', | ||||
), | ), | ||||
$content), | $content), | ||||
); | ); | ||||
} | } | ||||
private function newMarkdownCell(array $cell) { | private function newMarkdownCell(array $cell) { | ||||
$content = idx($cell, 'source'); | $content = $this->readStringList($cell, 'source'); | ||||
if (!is_array($content)) { | |||||
$content = array(); | |||||
} | |||||
// TODO: This should ideally highlight as Markdown, but the "md" | // TODO: This should ideally highlight as Markdown, but the "md" | ||||
// highlighter in Pygments is painfully slow and not terribly useful. | // highlighter in Pygments is painfully slow and not terribly useful. | ||||
$content = $this->highlightLines($content, 'txt'); | $content = $this->highlightLines($content, 'txt'); | ||||
return array( | return array( | ||||
null, | null, | ||||
phutil_tag( | phutil_tag( | ||||
'div', | 'div', | ||||
array( | array( | ||||
'class' => 'jupyter-cell-markdown', | 'class' => 'jupyter-cell-markdown', | ||||
), | ), | ||||
$content), | $content), | ||||
); | ); | ||||
} | } | ||||
private function newCodeCell(array $cell) { | private function newCodeCell(array $cell) { | ||||
$label = $this->newCellLabel($cell); | $label = $this->newCellLabel($cell); | ||||
$content = idx($cell, 'source'); | $content = $this->readStringList($cell, 'source'); | ||||
if (!is_array($content)) { | |||||
$content = array(); | |||||
} | |||||
$content = $this->highlightLines($content); | $content = $this->highlightLines($content); | ||||
$outputs = array(); | $outputs = array(); | ||||
$output_list = idx($cell, 'outputs'); | $output_list = idx($cell, 'outputs'); | ||||
if (is_array($output_list)) { | if (is_array($output_list)) { | ||||
foreach ($output_list as $output) { | foreach ($output_list as $output) { | ||||
$outputs[] = $this->newOutput($output); | $outputs[] = $this->newOutput($output); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 90 Lines • ▼ Show 20 Lines | switch ($output_type) { | ||||
'image/gif', | 'image/gif', | ||||
); | ); | ||||
foreach ($image_formats as $image_format) { | foreach ($image_formats as $image_format) { | ||||
if (!isset($data[$image_format])) { | if (!isset($data[$image_format])) { | ||||
continue; | continue; | ||||
} | } | ||||
$raw_data = $data[$image_format]; | $raw_data = $this->readString($data, $image_format); | ||||
if (!is_array($raw_data)) { | |||||
$raw_data = array($raw_data); | |||||
} | |||||
$raw_data = implode('', $raw_data); | |||||
$content = phutil_tag( | $content = phutil_tag( | ||||
'img', | 'img', | ||||
array( | array( | ||||
'src' => 'data:'.$image_format.';base64,'.$raw_data, | 'src' => 'data:'.$image_format.';base64,'.$raw_data, | ||||
)); | )); | ||||
break 2; | break 2; | ||||
Show All 14 Lines | switch ($output_type) { | ||||
if (isset($data['text/plain'])) { | if (isset($data['text/plain'])) { | ||||
$content = $data['text/plain']; | $content = $data['text/plain']; | ||||
break; | break; | ||||
} | } | ||||
break; | break; | ||||
case 'stream': | case 'stream': | ||||
default: | default: | ||||
$content = idx($output, 'text'); | $content = $this->readString($output, 'text'); | ||||
if (!is_array($content)) { | |||||
$content = array(); | |||||
} | |||||
$content = implode('', $content); | |||||
break; | break; | ||||
} | } | ||||
return phutil_tag( | return phutil_tag( | ||||
'div', | 'div', | ||||
array( | array( | ||||
'class' => implode(' ', $classes), | 'class' => implode(' ', $classes), | ||||
), | ), | ||||
▲ Show 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | private function highlightLines(array $lines, $force_language = null) { | ||||
return $content; | return $content; | ||||
} | } | ||||
public function shouldSuggestEngine(PhabricatorDocumentRef $ref) { | public function shouldSuggestEngine(PhabricatorDocumentRef $ref) { | ||||
return true; | return true; | ||||
} | } | ||||
private function readString(array $src, $key) { | |||||
$list = $this->readStringList($src, $key); | |||||
return implode('', $list); | |||||
} | |||||
private function readStringList(array $src, $key) { | |||||
$list = idx($src, $key); | |||||
if (is_array($list)) { | |||||
$list = $list; | |||||
} else if (is_string($list)) { | |||||
$list = array($list); | |||||
} else { | |||||
$list = array(); | |||||
} | |||||
return $list; | |||||
} | |||||
} | } |