Index: src/__celerity_resource_map__.php =================================================================== --- src/__celerity_resource_map__.php +++ src/__celerity_resource_map__.php @@ -3433,7 +3433,7 @@ ), 'phabricator-remarkup-css' => array( - 'uri' => '/res/7e8988dd/rsrc/css/core/remarkup.css', + 'uri' => '/res/4c313572/rsrc/css/core/remarkup.css', 'type' => 'css', 'requires' => array( @@ -4273,7 +4273,7 @@ ), array( 'packages' => array( - 'a4e76ef8' => + '30de5267' => array( 'name' => 'core.pkg.css', 'symbols' => @@ -4322,7 +4322,7 @@ 41 => 'phabricator-tag-view-css', 42 => 'phui-list-view-css', ), - 'uri' => '/res/pkg/a4e76ef8/core.pkg.css', + 'uri' => '/res/pkg/30de5267/core.pkg.css', 'type' => 'css', ), '6041c6c8' => @@ -4514,15 +4514,15 @@ ), 'reverse' => array( - 'aphront-dialog-view-css' => 'a4e76ef8', - 'aphront-error-view-css' => 'a4e76ef8', - 'aphront-list-filter-view-css' => 'a4e76ef8', - 'aphront-pager-view-css' => 'a4e76ef8', - 'aphront-panel-view-css' => 'a4e76ef8', - 'aphront-table-view-css' => 'a4e76ef8', - 'aphront-tokenizer-control-css' => 'a4e76ef8', - 'aphront-tooltip-css' => 'a4e76ef8', - 'aphront-typeahead-control-css' => 'a4e76ef8', + 'aphront-dialog-view-css' => '30de5267', + 'aphront-error-view-css' => '30de5267', + 'aphront-list-filter-view-css' => '30de5267', + 'aphront-pager-view-css' => '30de5267', + 'aphront-panel-view-css' => '30de5267', + 'aphront-table-view-css' => '30de5267', + 'aphront-tokenizer-control-css' => '30de5267', + 'aphront-tooltip-css' => '30de5267', + 'aphront-typeahead-control-css' => '30de5267', 'differential-changeset-view-css' => '7cd7e387', 'differential-core-view-css' => '7cd7e387', 'differential-inline-comment-editor' => '5e9e5c4e', @@ -4536,7 +4536,7 @@ 'differential-table-of-contents-css' => '7cd7e387', 'diffusion-commit-view-css' => '270f4eb4', 'diffusion-icons-css' => '270f4eb4', - 'global-drag-and-drop-css' => 'a4e76ef8', + 'global-drag-and-drop-css' => '30de5267', 'inline-comment-summary-css' => '7cd7e387', 'javelin-aphlict' => '6041c6c8', 'javelin-behavior' => '3e3be199', @@ -4611,56 +4611,56 @@ 'javelin-util' => '3e3be199', 'javelin-vector' => '3e3be199', 'javelin-workflow' => '3e3be199', - 'lightbox-attachment-css' => 'a4e76ef8', + 'lightbox-attachment-css' => '30de5267', 'maniphest-task-summary-css' => '49898640', - 'phabricator-action-list-view-css' => 'a4e76ef8', - 'phabricator-application-launch-view-css' => 'a4e76ef8', + 'phabricator-action-list-view-css' => '30de5267', + 'phabricator-application-launch-view-css' => '30de5267', 'phabricator-busy' => '6041c6c8', 'phabricator-content-source-view-css' => '7cd7e387', - 'phabricator-core-css' => 'a4e76ef8', - 'phabricator-crumbs-view-css' => 'a4e76ef8', + 'phabricator-core-css' => '30de5267', + 'phabricator-crumbs-view-css' => '30de5267', 'phabricator-drag-and-drop-file-upload' => '5e9e5c4e', 'phabricator-dropdown-menu' => '6041c6c8', 'phabricator-file-upload' => '6041c6c8', - 'phabricator-filetree-view-css' => 'a4e76ef8', - 'phabricator-flag-css' => 'a4e76ef8', + 'phabricator-filetree-view-css' => '30de5267', + 'phabricator-flag-css' => '30de5267', 'phabricator-hovercard' => '6041c6c8', - 'phabricator-jump-nav' => 'a4e76ef8', + 'phabricator-jump-nav' => '30de5267', 'phabricator-keyboard-shortcut' => '6041c6c8', 'phabricator-keyboard-shortcut-manager' => '6041c6c8', - 'phabricator-main-menu-view' => 'a4e76ef8', + 'phabricator-main-menu-view' => '30de5267', 'phabricator-menu-item' => '6041c6c8', - 'phabricator-nav-view-css' => 'a4e76ef8', + 'phabricator-nav-view-css' => '30de5267', 'phabricator-notification' => '6041c6c8', - 'phabricator-notification-css' => 'a4e76ef8', - 'phabricator-notification-menu-css' => 'a4e76ef8', + 'phabricator-notification-css' => '30de5267', + 'phabricator-notification-menu-css' => '30de5267', 'phabricator-object-selector-css' => '7cd7e387', 'phabricator-phtize' => '6041c6c8', 'phabricator-prefab' => '6041c6c8', 'phabricator-project-tag-css' => '49898640', - 'phabricator-remarkup-css' => 'a4e76ef8', + 'phabricator-remarkup-css' => '30de5267', 'phabricator-shaped-request' => '5e9e5c4e', - 'phabricator-side-menu-view-css' => 'a4e76ef8', - 'phabricator-standard-page-view' => 'a4e76ef8', - 'phabricator-tag-view-css' => 'a4e76ef8', + 'phabricator-side-menu-view-css' => '30de5267', + 'phabricator-standard-page-view' => '30de5267', + 'phabricator-tag-view-css' => '30de5267', 'phabricator-textareautils' => '6041c6c8', 'phabricator-tooltip' => '6041c6c8', - 'phabricator-transaction-view-css' => 'a4e76ef8', - 'phabricator-zindex-css' => 'a4e76ef8', - 'phui-button-css' => 'a4e76ef8', - 'phui-form-css' => 'a4e76ef8', - 'phui-form-view-css' => 'a4e76ef8', - 'phui-header-view-css' => 'a4e76ef8', - 'phui-icon-view-css' => 'a4e76ef8', - 'phui-list-view-css' => 'a4e76ef8', - 'phui-object-item-list-view-css' => 'a4e76ef8', - 'phui-property-list-view-css' => 'a4e76ef8', - 'phui-spacing-css' => 'a4e76ef8', - 'sprite-apps-large-css' => 'a4e76ef8', - 'sprite-gradient-css' => 'a4e76ef8', - 'sprite-icons-css' => 'a4e76ef8', - 'sprite-menu-css' => 'a4e76ef8', - 'sprite-status-css' => 'a4e76ef8', - 'syntax-highlighting-css' => 'a4e76ef8', + 'phabricator-transaction-view-css' => '30de5267', + 'phabricator-zindex-css' => '30de5267', + 'phui-button-css' => '30de5267', + 'phui-form-css' => '30de5267', + 'phui-form-view-css' => '30de5267', + 'phui-header-view-css' => '30de5267', + 'phui-icon-view-css' => '30de5267', + 'phui-list-view-css' => '30de5267', + 'phui-object-item-list-view-css' => '30de5267', + 'phui-property-list-view-css' => '30de5267', + 'phui-spacing-css' => '30de5267', + 'sprite-apps-large-css' => '30de5267', + 'sprite-gradient-css' => '30de5267', + 'sprite-icons-css' => '30de5267', + 'sprite-menu-css' => '30de5267', + 'sprite-status-css' => '30de5267', + 'syntax-highlighting-css' => '30de5267', ), )); Index: src/__phutil_library_map__.php =================================================================== --- src/__phutil_library_map__.php +++ src/__phutil_library_map__.php @@ -1536,6 +1536,9 @@ 'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php', 'PhabricatorRefreshCSRFController' => 'applications/auth/controller/PhabricatorRefreshCSRFController.php', 'PhabricatorRegistrationProfile' => 'applications/people/storage/PhabricatorRegistrationProfile.php', + 'PhabricatorRemarkupBlockInterpreterCowsay' => 'infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterCowsay.php', + 'PhabricatorRemarkupBlockInterpreterFiglet' => 'infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterFiglet.php', + 'PhabricatorRemarkupBlockInterpreterGraphviz' => 'infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterGraphviz.php', 'PhabricatorRemarkupControl' => 'view/form/control/PhabricatorRemarkupControl.php', 'PhabricatorRemarkupRuleEmbedFile' => 'applications/files/remarkup/PhabricatorRemarkupRuleEmbedFile.php', 'PhabricatorRemarkupRuleImageMacro' => 'applications/macro/remarkup/PhabricatorRemarkupRuleImageMacro.php', @@ -3747,6 +3750,9 @@ 'PhabricatorRedirectController' => 'PhabricatorController', 'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController', 'PhabricatorRegistrationProfile' => 'Phobject', + 'PhabricatorRemarkupBlockInterpreterCowsay' => 'PhutilRemarkupBlockInterpreter', + 'PhabricatorRemarkupBlockInterpreterFiglet' => 'PhutilRemarkupBlockInterpreter', + 'PhabricatorRemarkupBlockInterpreterGraphviz' => 'PhutilRemarkupBlockInterpreter', 'PhabricatorRemarkupControl' => 'AphrontFormTextAreaControl', 'PhabricatorRemarkupRuleEmbedFile' => 'PhabricatorRemarkupRuleObject', 'PhabricatorRemarkupRuleImageMacro' => 'PhutilRemarkupRule', Index: src/infrastructure/markup/PhabricatorMarkupEngine.php =================================================================== --- src/infrastructure/markup/PhabricatorMarkupEngine.php +++ src/infrastructure/markup/PhabricatorMarkupEngine.php @@ -460,6 +460,7 @@ $blocks[] = new PhutilRemarkupEngineRemarkupNoteBlockRule(); $blocks[] = new PhutilRemarkupEngineRemarkupTableBlockRule(); $blocks[] = new PhutilRemarkupEngineRemarkupSimpleTableBlockRule(); + $blocks[] = new PhutilRemarkupEngineRemarkupInterpreterRule(); $custom_block_rule_classes = $options['custom-block']; if ($custom_block_rule_classes) { Index: src/infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterCowsay.php =================================================================== --- /dev/null +++ src/infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterCowsay.php @@ -0,0 +1,55 @@ +markupError( + pht('Unable to locate the `cowsay` binary. Install cowsay.')); + } + + $bin = idx($argv, 'think') ? 'cowthink' : 'cowsay'; + $eyes = idx($argv, 'eyes', 'oo'); + $tongue = idx($argv, 'tongue', ' '); + $cow = idx($argv, 'cow', 'default'); + + // NOTE: Strip this aggressively to prevent nonsense like + // `cow=/etc/passwd`. We could build a whiltelist with `cowsay -l`. + $cow = preg_replace('/[^a-z.-]+/', '', $cow); + + $future = new ExecFuture( + '%s -e %s -T %s -f %s ', + $bin, + $eyes, + $tongue, + $cow); + + $future->write($content); + + list($err, $stdout, $stderr) = $future->resolve(); + + if ($err) { + return $this->markupError( + pht( + 'Execution of `cowsay` failed:', $stderr)); + } + + + if ($this->getEngine()->isTextMode()) { + return $stdout; + } + + return phutil_tag( + 'div', + array( + 'class' => 'PhabricatorMonospaced remarkup-cowsay', + ), + $stdout); + } + +} Index: src/infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterFiglet.php =================================================================== --- /dev/null +++ src/infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterFiglet.php @@ -0,0 +1,40 @@ +markupError( + pht('Unable to locate the `figlet` binary. Install figlet.')); + } + + $future = id(new ExecFuture('figlet')) + ->write(trim($content, "\n")); + + list($err, $stdout, $stderr) = $future->resolve(); + + if ($err) { + return $this->markupError( + pht( + 'Execution of `figlet` failed:', $stderr)); + } + + + if ($this->getEngine()->isTextMode()) { + return $stdout; + } + + return phutil_tag( + 'div', + array( + 'class' => 'PhabricatorMonospaced remarkup-figlet', + ), + $stdout); + } + +} Index: src/infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterGraphviz.php =================================================================== --- /dev/null +++ src/infrastructure/markup/interpreter/PhabricatorRemarkupBlockInterpreterGraphviz.php @@ -0,0 +1,44 @@ +markupError( + pht('Unable to locate the `dot` binary. Install Graphviz.')); + } + + $future = id(new ExecFuture('dot -T%s', 'png')) + ->write(trim($content)); + + list($err, $stdout, $stderr) = $future->resolve(); + + if ($err) { + return $this->markupError( + pht( + 'Execution of `dot` failed, check your syntax: %s', $stderr)); + } + + $file = PhabricatorFile::buildFromFileDataOrHash( + $stdout, + array( + 'name' => 'graphviz.png', + )); + + if ($this->getEngine()->isTextMode()) { + return '<'.$file->getBestURI().'>'; + } + + return phutil_tag( + 'img', + array( + 'src' => $file->getBestURI(), + )); + } + +} Index: webroot/rsrc/css/core/remarkup.css =================================================================== --- webroot/rsrc/css/core/remarkup.css +++ webroot/rsrc/css/core/remarkup.css @@ -329,6 +329,20 @@ border-right: 1px solid #cccccc; } +.remarkup-interpreter-error { + padding: 8px; + border: 1px solid {$red}; + background-color: {$lightred}; +} + +.remarkup-cowsay { + white-space: pre-wrap; +} + +.remarkup-figlet { + white-space: pre-wrap; +} + .remarkup-assist { display: block; width: 14px;