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; | |||||
| } | |||||
| } | } | ||||