--- /dev/null
+<?php
+
+/*
+ * This file is part of Psy Shell.
+ *
+ * (c) 2012-2017 Justin Hileman
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Psy\Util;
+
+/**
+ * String utility methods.
+ *
+ * @author ju1ius
+ */
+class Str
+{
+ const UNVIS_RX = <<<'EOS'
+/
+ \\(?:
+ ((?:040)|s)
+ | (240)
+ | (?: M-(.) )
+ | (?: M\^(.) )
+ | (?: \^(.) )
+ )
+/xS
+EOS;
+
+ /**
+ * Decodes a string encoded by libsd's strvis.
+ *
+ * From `man 3 vis`:
+ *
+ * Use an ‘M’ to represent meta characters (characters with the 8th bit set),
+ * and use a caret ‘^’ to represent control characters (see iscntrl(3)).
+ * The following formats are used:
+ *
+ * \040 Represents ASCII space.
+ *
+ * \240 Represents Meta-space (  in HTML).
+ *
+ * \M-C Represents character ‘C’ with the 8th bit set.
+ * Spans characters ‘\241’ through ‘\376’.
+ *
+ * \M^C Represents control character ‘C’ with the 8th bit set.
+ * Spans characters ‘\200’ through ‘\237’, and ‘\377’ (as ‘\M^?’).
+ *
+ * \^C Represents the control character ‘C’.
+ * Spans characters ‘\000’ through ‘\037’, and ‘\177’ (as ‘\^?’).
+ *
+ * The other formats are supported by PHP's stripcslashes,
+ * except for the \s sequence (ASCII space).
+ *
+ * @param string $input The string to decode
+ *
+ * @return string
+ */
+ public static function unvis($input)
+ {
+ $output = preg_replace_callback(self::UNVIS_RX, 'self::unvisReplace', $input);
+ // other escapes & octal are handled by stripcslashes
+ return stripcslashes($output);
+ }
+
+ /**
+ * Callback for Str::unvis.
+ *
+ * @param array $match The matches passed by preg_replace_callback
+ *
+ * @return string
+ */
+ protected static function unvisReplace($match)
+ {
+ // \040, \s
+ if (!empty($match[1])) {
+ return "\x20";
+ }
+ // \240
+ if (!empty($match[2])) {
+ return "\xa0";
+ }
+ // \M-(.)
+ if (isset($match[3]) && $match[3] !== '') {
+ $chr = $match[3];
+ // unvis S_META1
+ $cp = 0200;
+ $cp |= ord($chr);
+
+ return chr($cp);
+ }
+ // \M^(.)
+ if (isset($match[4]) && $match[4] !== '') {
+ $chr = $match[4];
+ // unvis S_META | S_CTRL
+ $cp = 0200;
+ $cp |= ($chr === '?') ? 0177 : ord($chr) & 037;
+
+ return chr($cp);
+ }
+ // \^(.)
+ if (isset($match[5]) && $match[5] !== '') {
+ $chr = $match[5];
+ // unvis S_CTRL
+ $cp = 0;
+ $cp |= ($chr === '?') ? 0177 : ord($chr) & 037;
+
+ return chr($cp);
+ }
+ }
+}