Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15406015
D12966.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
5 KB
Referenced Files
None
Subscribers
None
D12966.id.diff
View Options
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
@@ -322,11 +322,13 @@
'PhutilSyntaxHighlighterException' => 'markup/syntax/highlighter/PhutilSyntaxHighlighterException.php',
'PhutilSystem' => 'utils/PhutilSystem.php',
'PhutilSystemTestCase' => 'utils/__tests__/PhutilSystemTestCase.php',
+ 'PhutilTerminalString' => 'xsprintf/PhutilTerminalString.php',
'PhutilTestPhobject' => 'object/__tests__/PhutilTestPhobject.php',
'PhutilTortureTestDaemon' => 'daemon/torture/PhutilTortureTestDaemon.php',
'PhutilTranslation' => 'internationalization/PhutilTranslation.php',
'PhutilTranslator' => 'internationalization/PhutilTranslator.php',
'PhutilTranslatorTestCase' => 'internationalization/__tests__/PhutilTranslatorTestCase.php',
+ 'PhutilTsprintfTestCase' => 'xsprintf/__tests__/PhutilTsprintfTestCase.php',
'PhutilTwitchAuthAdapter' => 'auth/PhutilTwitchAuthAdapter.php',
'PhutilTwitchFuture' => 'future/twitch/PhutilTwitchFuture.php',
'PhutilTwitterAuthAdapter' => 'auth/PhutilTwitterAuthAdapter.php',
@@ -464,6 +466,7 @@
'queryfx' => 'xsprintf/queryfx.php',
'queryfx_all' => 'xsprintf/queryfx.php',
'queryfx_one' => 'xsprintf/queryfx.php',
+ 'tsprintf' => 'xsprintf/tsprintf.php',
'urisprintf' => 'xsprintf/urisprintf.php',
'vcsprintf' => 'xsprintf/csprintf.php',
'vjsprintf' => 'xsprintf/jsprintf.php',
@@ -478,6 +481,7 @@
'xsprintf_ldap' => 'xsprintf/ldapsprintf.php',
'xsprintf_mercurial' => 'xsprintf/hgsprintf.php',
'xsprintf_query' => 'xsprintf/qsprintf.php',
+ 'xsprintf_terminal' => 'xsprintf/tsprintf.php',
'xsprintf_uri' => 'xsprintf/urisprintf.php',
),
'xmap' => array(
@@ -720,10 +724,12 @@
'PhutilSyntaxHighlighterException' => 'Exception',
'PhutilSystem' => 'Phobject',
'PhutilSystemTestCase' => 'PhutilTestCase',
+ 'PhutilTerminalString' => 'Phobject',
'PhutilTestPhobject' => 'Phobject',
'PhutilTortureTestDaemon' => 'PhutilDaemon',
'PhutilTranslation' => 'Phobject',
'PhutilTranslatorTestCase' => 'PhutilTestCase',
+ 'PhutilTsprintfTestCase' => 'PhutilTestCase',
'PhutilTwitchAuthAdapter' => 'PhutilOAuthAuthAdapter',
'PhutilTwitchFuture' => 'FutureProxy',
'PhutilTwitterAuthAdapter' => 'PhutilOAuth1AuthAdapter',
diff --git a/src/xsprintf/PhutilTerminalString.php b/src/xsprintf/PhutilTerminalString.php
new file mode 100644
--- /dev/null
+++ b/src/xsprintf/PhutilTerminalString.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * String escaped for terminal output. See @{function:tsprintf}.
+ */
+final class PhutilTerminalString extends Phobject {
+
+ private $string;
+
+ public function __construct($string) {
+ $this->string = $string;
+ }
+
+ public function __toString() {
+ return $this->string;
+ }
+
+ public static function escapeStringValue($value, $allow_whitespace) {
+ if ($value instanceof PhutilTerminalString) {
+ return (string)$value;
+ }
+
+ $value = (string)$value;
+
+ static $escape_map;
+ if ($escape_map === null) {
+ $escape_map = array(
+ chr(0x00) => '<NUL>',
+ chr(0x07) => '<BEL>',
+ chr(0x08) => '<BS>',
+ chr(0x09) => '<TAB>',
+ chr(0x0A) => '<LF>',
+ chr(0x0D) => '<CR>',
+ chr(0x1B) => '<ESC>',
+ chr(0x7F) => '<DEL>',
+ );
+
+ for ($ii = 0; $ii < 32; $ii++) {
+ $c = chr($ii);
+ if (empty($escape_map[$c])) {
+ $escape_map[$c] = sprintf('<0x%02X>', $ii);
+ }
+ }
+ }
+
+ $map = $escape_map;
+ if ($allow_whitespace) {
+ unset($map["\r"]);
+ unset($map["\n"]);
+ unset($map["\t"]);
+ }
+
+ $value = str_replace(array_keys($map), array_values($map), $value);
+
+ // In this mode, we additionally escape any <CR> which is not immediately
+ // followed by <LF>.
+ if ($allow_whitespace) {
+ $value = preg_replace('/\r(?!\n)/', '<CR>', $value);
+ }
+
+ return $value;
+ }
+}
diff --git a/src/xsprintf/__tests__/PhutilTsprintfTestCase.php b/src/xsprintf/__tests__/PhutilTsprintfTestCase.php
new file mode 100644
--- /dev/null
+++ b/src/xsprintf/__tests__/PhutilTsprintfTestCase.php
@@ -0,0 +1,24 @@
+<?php
+
+final class PhutilTsprintfTestCase extends PhutilTestCase {
+
+ public function testTsprintf() {
+ $this->assertEqual(
+ '<NUL>',
+ (string)tsprintf('%s', "\0"));
+
+ $this->assertEqual(
+ '<ESC>[31mred<ESC>[39m',
+ (string)tsprintf('%s', "\x1B[31mred\x1B[39m"));
+
+ $block = "1\r\n2\r3\n4";
+
+ $this->assertEqual(
+ '1<CR><LF>2<CR>3<LF>4',
+ (string)tsprintf('%s', $block));
+ $this->assertEqual(
+ "1\r\n2<CR>3\n4",
+ (string)tsprintf('%B', $block));
+ }
+
+}
diff --git a/src/xsprintf/tsprintf.php b/src/xsprintf/tsprintf.php
new file mode 100644
--- /dev/null
+++ b/src/xsprintf/tsprintf.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * Format text for terminal output. This function behaves like `sprintf`,
+ * except that all the normal conversions (like "%s") will be properly escaped,
+ * and additional conversions are supported:
+ *
+ * %B (Block)
+ * Escapes text, but preserves tabs and newlines.
+ *
+ * %R (Raw String)
+ * Inserts raw, unescaped text. DANGEROUS!
+ *
+ * Particularly, this will escape terminal control characters.
+ */
+function tsprintf($pattern /* , ... */) {
+ $args = func_get_args();
+ $string = xsprintf('xsprintf_terminal', null, $args);
+ return new PhutilTerminalString($string);
+}
+
+/**
+ * Callback for terminal encoding, see @{function:tsprintf} for use.
+ */
+function xsprintf_terminal($userdata, &$pattern, &$pos, &$value, &$length) {
+ $type = $pattern[$pos];
+
+ switch ($type) {
+ case 's':
+ $value = PhutilTerminalString::escapeStringValue($value, false);
+ $type = 's';
+ break;
+ case 'B':
+ $value = PhutilTerminalString::escapeStringValue($value, true);
+ $type = 's';
+ break;
+ case 'R':
+ $type = 's';
+ break;
+ }
+
+ $pattern[$pos] = $type;
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Mar 19, 12:04 PM (1 w, 2 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7709468
Default Alt Text
D12966.id.diff (5 KB)
Attached To
Mode
D12966: Introduce tsprintf(), for escaping terminal output
Attached
Detach File
Event Timeline
Log In to Comment