* @license BSD */ //namespace Domframework; /** Allow to manage a linux Console to have a minimal but working text interface * When using this class, you must use the $console::echo method and not * display directely on screen * Like readline, but all in PHP * Manage the historical of the provided commands * Allow the user to use arrow keys and the shortcuts Ctrl+arrow, Ctrl+L, * Ctrl+U, Ctrl+W * To not allow the stop of the program by Ctrl+C, you can add * exec ("stty intr ^J"); * To update the window size when the terminal is resized, use after console * instanciation : * declare(ticks = 1); * pcntl_signal (SIGWINCH, function () use ($console) { * $console->updateTerminalSize (); * }); */ class console { // PROPERTIES // {{{ /** Set the debug on if a filename is provided, or do not debug if false is * provided */ //private $debug = "/tmp/debug"; private $debug = false; /** Save the initial stty value */ private $initSttyState; /** Line Content */ private $lineContent = ""; /** If true, display each char the user has pressed (echo mode) */ private $echoMode = true; /** List of non printable chars in decimal. The non printable chars are not * displayed but are correctely captured */ private $nonWriteableChar = array (1, 2, 3, 4, 6, 8, 9, 16, 18, 20, 21, 22, 23, 24, 25, 27, 127); /** The history list in an array */ private $history = array (); /** The history max size in entries */ private $historyMaxSize = 1000; /** Set the completion keys. Not set by default */ private $completionKeys = false; /** Set the function called when the completion char is called */ private $completionFunction = array (); /** Set the width of the terminal in chars */ private $termWidth; /** Set the height of the terminal in chars */ private $termHeight; /** Store the last cursor position in the last readline */ private $cursorPos = 1; // }}} /** The constructor init the console. * Check if we have the rights to execute, if wa have in cli... */ public function __construct () // {{{ { if (! function_exists ("exec")) throw $this->ConsoleException ("No exec support in PHP"); $this->initSttyState = exec ("stty -g"); // Set the terminal to return the value each time a key is pressed. // Do not display anything, so we don't see the characters when the user is // deleting. // 'intr ^J' allow to redefine the interruption from Ctrl+C to Ctrl+J. It // allow to manage the Ctrl+C key to clean the entry exec ("stty -echo -icanon min 1 time 0"); $this->updateTerminalSize (); } // }}} /** Update the terminal size */ public function updateTerminalSize () // {{{ { $this->termWidth = 80; $this->termHeight = 25; $termSize = exec ("stty size", $null, $rc); if ($rc === 0) { list ($termHeight, $termWidth) = explode (" ", $termSize); if (is_null ($termWidth) || is_bool ($termWidth)) $this->termWidth = 80; else $this->termWidth = intval ($termWidth); if (is_null ($termHeight) || is_bool ($termHeight)) $this->termHeight = 25; else $this->termHeight = intval ($termHeight); } } // }}} /** The destructor return the terminal to initial state */ public function __destruct () // {{{ { exec ("stty $this->initSttyState"); $this->colorReset (); $this->textUnderline (false); $this->textBold (false); } // }}} /** Each time a key is pressed by the user, display the value on screen (echo) */ public function setEcho () // {{{ { $this->echoMode = true; } // }}} /** Each time a key is pressed by the user, DO NOT display the value on screen * (echo disabled) */ public function unsetEcho () // {{{ { $this->echoMode = false; } // }}} /** Display a text on screen. Must be used before "readline" method because * the provided message will not be deleted by readline process. * @param string $message The message to display */ public function echo ($message) // {{{ { echo $message; $this->lineContent .= $message; } // }}} /** Wait one valid character from the user. * The non printable chars are not displayed, nor returned * The ESC Sequences are skipped * @return the pressed char */ public function getc () // {{{ { $char = $this->getKey (); while (in_array (ord ($char), $this->nonWriteableChar)) $char = $this->getKey (); return $char; } // }}} /** Wait one key pressed by the user. If the key pressed is an ESC sequence, * return this sequence * The non printable chars are not displayed, but are correctely returned * The UTF8 chars are return as multiple length chars * @return the pressed char */ public function getKey () // {{{ { $char = fgetc (STDIN); if ($char === chr (27)) { $sequence = $char; $char2 = fgetc (STDIN); if (ord ($char2) === 27) { // Sequence of ESC ESC } elseif (ord ($char2) === 79) { // Start an ESC SS3 sequence // Like F2 Key $sequence .= $char2; $char = fgetc (STDIN); $sequence .= $char; } elseif (ord ($char2) === 91) { // Start an ESC CSI sequence. Do not display it, just return it. // The ESC squences are used to communicate the cursor keys, associated // with the Ctrl key, by example // ESC [ is followed by any number (including none) of "parameter bytes" // in the range 0x30–0x3F (ASCII 0–9:;<=>?), then by any number of // "intermediate bytes" in the range 0x20–0x2F (ASCII space and // !"#$%&'()*+,-./), then finally by a single "final byte" in the range // 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~).[14]:5.4 $sequence .= $char2; $char = fgetc (STDIN); while (ord ($char) < 64 || ord ($char) > 126) { $sequence .= $char; $char = fgetc (STDIN); } $sequence .= $char; } else { $this->consoleException ("Invalid ESC seq : ".ord($char2)); } return $sequence; } // UTF Sequence : // - char1 < 128 : One char (like ascii) // - char1 >= 194 && char1 <= 223 : Two chars // - char1 >= 224 && char1 <= 239 : Three chars // - char1 >= 240 && char1 <= 244 : Four chars if (ord ($char) < 128) { // One Char like ascii } elseif (ord ($char) >= 194 && ord ($char) <= 223) { // Two chars $char .= fgetc (STDIN); } elseif (ord ($char) >= 224 && ord ($char) <= 239) { // Three chars $char .= fgetc (STDIN).fgetc (STDIN); } elseif (ord ($char) >= 240 && ord ($char) <= 244) { // Four chars $char .= fgetc (STDIN).fgetc (STDIN).fgetc (STDIN); } if ($this->echoMode && ! in_array (ord ($char), $this->nonWriteableChar)) echo $char; return $char; } // }}} /** Get the line of characters pressed by the user and return the result. * Stop when the user valid by \n. * Manage correctely the backspace, the Ctrl+W to remove word... * @param string $propo Preset the text for the user * @param boolean|string $stopperChar The chars to stop the analysis and * return the result * @return The typed string */ public function readline ($propo = "", $stopperChar = false) // {{{ { // Gets can not delete chars before the call. Keep the prompt (if exists) if (! is_string ($propo)) $this->consoleException ("Invalid proposition provided to readline"); $prompt = $this->lineContent; $minLength = mb_strlen ($this->lineContent) + 1; // Manage the history and a temporary buffer if the user has already type // something before calling the history $historyPos = count ($this->history); $string = $propo; echo $string; $this->lineContent = $prompt.$string; // The cursor position from last char of line. $cursorPos = mb_strlen ($this->lineContent) + 1; while (1) { $char = $this->getKey (); if ($stopperChar === false && $char === "\n") { // End of process without stopperChars $this->lineContent = ""; break; } if ($stopperChar !== false && in_array ($char, $this->mb_str_split ($stopperChar))) { // End of process with stopperChars $this->lineContent = ""; break; } if ($this->completionKeys !== false && in_array ($char, $this->mb_str_split ($this->completionKeys))) // Manage autocompletion // {{{ { $this->debug ("Autocompletion starting"); // Take the last part of the string without space or double quotes $pos = strrpos ($string, " "); if ($pos === false) { // No space : put all in end $start = ""; $end = $string; } elseif ($pos === mb_strlen ($string)) { // Last char is a space : put all in start $start = $string; $end = ""; } else { // Last char is not a space, end is the last word and start is the // begin to before last word $start = mb_substr ($string, 0, $pos + 1); $end = mb_substr ($string, $pos + 1); } $this->debug ("Autocompletion : start='$start', end='$end'"); $completeArr = call_user_func ($this->completionFunction, self::tokenize ($start)); if (! is_array ($completeArr)) throw new \Exception ("Autocompletion : return is not an array"); $isAssoc = is_array ($completeArr) && array_diff_key ($completeArr, array_keys (array_keys ($completeArr))); // Remove from completeArr the proposed values which doesn't match with // $end (invalid proposals) foreach ($completeArr as $key => $val) { if ($isAssoc) $val = $key; if (mb_substr ($val, 0, mb_strlen ($end)) !== $end) unset ($completeArr[$key]); } if (count ($completeArr) === 1 && ($isAssoc && key ($completeArr) !== "" || ! $isAssoc && reset ($completeArr) !== "")) { // One entry : add a space to put on the next $this->debug ("Autocompletion : One entry not empty : ". "add it + ending space"); if ($isAssoc) $string = $start.key ($completeArr)." "; else $string = $start.reset ($completeArr)." "; } elseif (count ($completeArr)) { // Multiple entries : display them to allow the user to choose $this->debug ("Autocompletion : Multiple entries : display choices"); echo "\n"; // In associative array, the key is the possible answer to // autocompletion, and the value is the helper message // Get the largest key length to make a beautiful alignment // Get the smaller key length to found a affined answer $maxlen = 0; foreach ($completeArr as $key => $val) { $maxlen = max ($maxlen, mb_strlen ($key)); } $maxlen = $maxlen + 5; if ($isAssoc) ksort ($completeArr, SORT_NATURAL); else sort ($completeArr, SORT_NATURAL); foreach ($completeArr as $key => $val) { if ($isAssoc) printf ("%-${maxlen}s %s\n", $key, $val); elseif (trim ($val) === "") // TODO : Define the string to display for ending string echo "
\n"; else echo "$val\n"; } if ($isAssoc) $addChars = $this->shortestIdenticalValues ( array_keys ($completeArr)); else $addChars = $this->shortestIdenticalValues ($completeArr); if ($addChars === "") $addChars = $end; $string = $start.$addChars; } else { $this->debug ("Autocompletion : Zero entry : do not change"); $string = $start.$end; } if (is_array ($completeArr) && count ($completeArr)) { // If there were multiple suggestions displayed, rewrite the line $cursorPos = mb_strlen ($prompt.$string) + 1; $this->rewriteLine ($prompt.$string); $this->moveCursor ($cursorPos); } $this->debug ("Autocompletion : end '$prompt.$string'"); } // }}} elseif (ord ($char) === 3) // Abort (Ctrl+C) // {{{ { $this->debug ("Abort Ctrl+C : ".ord ($char)); $this->lineContent = ""; $string = ""; $this->clearLine (); echo "$prompt\n"; break; } // }}} elseif (ord ($char) === 4) // Logout (Ctrl+D) // {{{ { $this->debug ("Logout Ctrl+D : ".ord ($char)); $string = "exit\n"; $this->rewriteLine ($prompt.$string); return $string; } // }}} elseif (ord($char) === 12) // Refresh page (Ctrl+L) // {{{ { $this->debug ("Refresh Ctrl+L : ".ord ($char)); echo "\033[2J\033[;H\033c"; $cursorPos = mb_strlen ($prompt.$string) + 1; $this->rewriteLine ($prompt.$string); $this->moveCursor ($cursorPos); } // }}} elseif (ord($char) === 21) // Empty line from prompt to cursor (Ctrl+U) // {{{ { $this->debug ("Empty line from prompt to cursor Ctrl+U : ".ord ($char)); $string = mb_substr ($string, $cursorPos - $minLength); $cursorPos = $minLength; $this->rewriteLine ($prompt.$string); $this->moveCursor ($cursorPos); } // }}} elseif (ord($char) === 23) // Remove the last word (Ctrl+W) // {{{ { $this->debug ("Remove the last word Ctrl+W : ".ord ($char)); $tmp = mb_substr ($string, 0, $cursorPos - $minLength); $end = mb_substr ($string, $cursorPos - $minLength); $tmp = rtrim ($tmp); $pos = mb_strrpos ($tmp, " "); if ($pos !== false) $pos++; $string = mb_substr ($string, 0, $pos).$end; $cursorPos = $minLength + $pos; $this->rewriteLine ($prompt.$string); $this->moveCursor ($cursorPos); } // }}} elseif (ord($char) === 127) // Remove the previous char (Backspace) // {{{ { $this->debug ("Remove the previous char (Backspace) : ".ord ($char)); if ($cursorPos <= $minLength) continue; $strArr = $this->mb_str_split ($string); $cursorPos--; unset ($strArr[$cursorPos - $minLength]); $string = implode ($strArr); $this->rewriteLine ($prompt.$string); $this->moveCursor ($cursorPos); } // }}} elseif (ord ($char{0}) === 27) { // ESC SEQUENCE $sequence = ""; foreach (str_split ($char) as $key) $sequence .= ord ($key)." "; $this->debug ("ESC SEQUENCE : $sequence"); if ($char === chr (27).chr (91).chr (49).chr (59).chr (53).chr (67)) // Cursor right + Ctrl : cursor jump by word // {{{ { $this->debug ("Cursor right + Ctrl"); $tmp = mb_substr ($string, $cursorPos - $minLength); $tmp = ltrim ($tmp); $pos = strpos ($tmp, " "); if ($pos !== false) $cursorPos += $pos +1 ; else $cursorPos = mb_strlen ($prompt.$string) + 1; $this->moveCursor ($cursorPos); } // }}} elseif ($char === chr (27).chr (91).chr (49).chr (59).chr (53).chr (68)) // Cursor left + Ctrl : cursor jump by word // {{{ { $this->debug ("Cursor left + Ctrl"); $tmp = mb_substr ($string, 0, $cursorPos - $minLength); $tmp = rtrim ($tmp); $pos = strrpos ($tmp, " "); if ($pos !== false) $pos++; $cursorPos = $minLength + $pos; $this->moveCursor ($cursorPos); } // }}} elseif ($char === chr (27).chr (91).chr (65)) // Cursor up : display the previous history if defined // {{{ { $this->debug ("Cursor up"); if (! isset ($historyTmp)) { $historyTmp = $string; $historyTmpPos = $cursorPos; } if ($historyPos > 0) { $historyPos--; $slice = array_slice ($this->history, $historyPos, 1); $string = reset ($slice); $cursorPos = mb_strlen ($prompt.$string) + 1; $this->rewriteLine ($prompt.$string); $this->moveCursor ($cursorPos); } } // }}} elseif ($char === chr (27).chr (91).chr (66)) // Cursor down : display the next history if defined // {{{ { $this->debug ("Cursor down"); if ($historyPos < count ($this->history) - 1) { $historyPos++; $slice = array_slice ($this->history, $historyPos, 1); $string = reset ($slice); $cursorPos = mb_strlen ($prompt.$string) + 1; } elseif (isset ($historyTmp)) { $string = $historyTmp; $cursorPos = $historyTmpPos; unset ($historyTmp); } $this->rewriteLine ($prompt.$string); $this->moveCursor ($cursorPos); } // }}} elseif ($char === chr (27).chr (91).chr (67)) // Cursor right // {{{ { $this->debug ("Cursor right"); if ($cursorPos <= mb_strlen ($this->lineContent)) { $cursorPos++; $this->moveCursor ($cursorPos); } } // }}} elseif ($char === chr (27).chr (91).chr (68)) // Cursor left // {{{ { $this->debug ("Cursor left"); if ($cursorPos > $minLength) { $cursorPos--; $this->moveCursor ($cursorPos); } } // }}} elseif ($char === chr (27).chr (91).chr (70)) // End key // {{{ { $this->debug ("End key"); $cursorPos = $minLength + mb_strlen ($string); $this->moveCursor ($cursorPos); } // }}} elseif ($char === chr (27).chr (91).chr (72)) // Home key // {{{ { $this->debug ("Home key"); $cursorPos = $minLength; $this->moveCursor ($cursorPos); } // }}} elseif ($char === chr (27).chr (91).chr (51).chr (126)) // Remove the char under the cursor (Delete) // {{{ { $this->debug ("Delete key"); if ($cursorPos > mb_strlen ($prompt.$string)) continue; $strArr = $this->mb_str_split ($string); unset ($strArr[$cursorPos - $minLength]); $string = implode ($strArr); $this->rewriteLine ($prompt.$string); $this->moveCursor ($cursorPos); } // }}} } elseif (in_array (ord ($char), $this->nonWriteableChar)) // Non writeable char : skip it // {{{ { $this->debug ("Non writeable char : ".ord ($char)); } // }}} else // Normal char : Add it to the string // {{{ { $this->debug ("Normal char : ".ord ($char)); $strArr = $this->mb_str_split ($string); $firstArr = array_slice ($strArr, 0, $cursorPos - $minLength); $lastArr = array_slice ($strArr, $cursorPos - $minLength); $insertArr = array ($char); $strArr = array_merge ($firstArr, $insertArr, $lastArr); $string = implode ($strArr); $cursorPos++; $this->rewriteLine ($prompt.$string); $this->moveCursor ($cursorPos); } // }}} } $this->debug ("End of readline '$string'"); return $string; } // }}} /** Rewrite the line with the provided $text. * Delete all the old data * @param string $text The new text to use on line */ private function rewriteLine ($text) // {{{ { $this->debug ("Call rewriteLine ($text)"); if ($this->echoMode) { $this->clearLine (); $this->lineContent = $text; echo $this->lineContent; $this->cursorPos = mb_strlen ($this->lineContent); } } // }}} /** Move the cursor on position $position. The first column is $cursorPos=1 * @param integer $cursorPos The new position on line */ private function moveCursor ($cursorPos) // {{{ { $this->debug ("Call moveCursor ($cursorPos)"); if ($cursorPos < 1) $this->consoleException ("MoveCursor lesser than one : $cursorPos"); if ($this->echoMode) { $oldLength = mb_strlen ($this->lineContent); // 1. Calculate on which line the cursor is positionned $cursorLine = 1 + floor ((-1+$this->cursorPos) / $this->termWidth); // 2. Return the cursor to the first line for ($i = $cursorLine ; $i > 1 ; $i--) echo chr (27).chr (91).chr (49).chr (65)."\r"; // 3. Down the cursor to the wanted line $wantedLine = ceil ($cursorPos / $this->termWidth); if ($wantedLine > 1) { for ($i = 1 ; $i < $wantedLine ; $i++) echo "\r".chr (27).chr (91).chr (49).chr (66)."\r"; } // 4. Move the cursor on the last line $needMovePos = -1 + $cursorPos - ($wantedLine - 1) * $this->termWidth; echo "\r".str_repeat (chr (27).chr (91).chr (67), $needMovePos); $this->cursorPos = $cursorPos; } } // }}} /** Clear the existing line. */ public function clearLine () // {{{ { $this->debug ("Call clearLine"); $oldLength = mb_strlen ($this->lineContent); // 1. Calculate on which line the cursor is positionned $cursorLine = 1 + floor ((-1+$this->cursorPos) / $this->termWidth); $lastLines = 1 + floor ((1+$oldLength) / $this->termWidth); $this->debug ("==> clearLine : oldLength=$oldLength, ". "cursorLine=$cursorLine, lastLines=$lastLines"); for ($i = $cursorLine ; $i < $lastLines ; $i++) { $this->debug ("==> clearLine : go Down (i=$i<$lastLines)"); echo "\033[1B"; } // 3. Remove the lines from lastLines to line 1 if ($lastLines > 1) { for ($i = $lastLines ; $i > 1 ; $i--) { $this->debug ("==> clearLine : Remove line up (i=$i<$lastLines)"); echo "\r\033[K\033[1A\r"; } } // 4. Clean the line 1 $this->debug ("==> clearLine : Remove line 1"); echo "\r\033[K"; $this->lineContent = ""; $this->cursorPos = 1; } // }}} /** Clear all the screen and remove the scroll of the screen */ public function clearScreen () // {{{ { echo "\033[2J\033[;H\033c"; } // }}} /** Get the terminal Height */ public function getTermHeight () // {{{ { return $this->termHeight; } // }}} /** Get the terminal Width */ public function getTermWidth () // {{{ { return $this->termWidth; } // }}} /** Call a specific function when a completion key is pressed * The function must get the partial text as first parameter, and must return * an array with the possibilities * If only one possibility is returned, the console will be immediately * updated. * @param string|bool $completionKeys The list of the completion keys. False * unset the method * @param callable $completionFunction The function called when one of the * completion keys is pressed. */ public function completeFunction ($completionKeys, $completionFunction) // {{{ { if (! is_string ($completionKeys) && ! is_boolean ($completionKeys)) $this->consoleException ("Can not set the completionKeys : not a string"); if ($completionKeys === true) $this->consoleException ("Can not set the completionKeys : not false"); if (! is_callable ($completionFunction)) $this->consoleException ("Can not set the completionFunction : ". "not a callable function"); $this->completionKeys = $completionKeys; $this->completionFunction = $completionFunction; } // }}} /** Get the actual history in memory */ public function getHistory () // {{{ { return $this->history; } // }}} /** Clear the history * This method do NOT write the empty history on disk */ public function clearHistory () // {{{ { $this->history = array (); return $this; } // }}} /** Write the history to disk. * @param string $historyFile The history file where the history is stored */ public function writeHistory ($historyFile) // {{{ { if (file_exists ($historyFile)) { if (! is_writeable ($historyFile)) $this->consoleException ("History file '$historyFile' ". "is not writeable"); $history = file_get_contents ($historyFile); if ($history === false) $this->consoleException ("History file '$historyFile' can not be read"); $historyArr = explode ("\n", $history); if (! isset ($historyArr[0]) || $historyArr[0] !== "__HISTORY__") $this->consoleException ("History file '$historyFile' ". "is not an history file : do not touch\n"); } elseif (! file_exists (dirname ($historyFile))) $this->consoleException ("History file '$historyFile' ". "can not be created: parent directory doesn't exists"); elseif (! is_dir (dirname ($historyFile))) $this->consoleException ("History file '$historyFile' ". "can not be created: parent directory is not a directory"); file_put_contents ($historyFile, "__HISTORY__\n"); $history = ""; foreach ($this->history as $time => $command) $history .= "$time $command\n"; file_put_contents ($historyFile, $history, FILE_APPEND|LOCK_EX); return $this; } // }}} /** Read the history from the disk * If the file doesn't exists, return an empty array * @param string $historyFile The history file where the history is stored * @return the read history with timestamp as key and command as value */ public function readHistory ($historyFile) // {{{ { if (! file_exists ($historyFile)) { $this->history = array (); return array (); } if (! is_readable ($historyFile)) $this->consoleException ("History file '$historyFile' can not be read"); $history = file_get_contents ($historyFile); if ($history === false) $this->consoleException ("History file '$historyFile' can not be read"); $historyArr = explode ("\n", $history); if (! isset ($historyArr[0]) || $historyArr[0] !== "__HISTORY__") $this->consoleException ("History file '$historyFile' ". "is not an history file : do not touch\n"); array_shift ($historyArr); foreach ($historyArr as $line) { @list ($time, $command) = @explode (" ", $line, 2); if ($time === null || $command === null || ! ctype_digit ($time)) continue; $this->history[$time] = $command; } return $this->history; } // }}} /** Add a new entry in history. * The new line can not be empty : it is not stored, but without error * This method do NOT write the history on disk : you must use writeHistory * @param string The new entry to add in history */ public function addHistory ($line) // {{{ { if (! is_string ($line)) $this->consoleException ("Can not add line to history : ". "it is not a string"); if (trim ($line) === "") return $this; $this->history[time()] = $line; $this->history = array_slice ($this->history, -$this->historyMaxSize, null, true); return $this; } // }}} /** Get/Set the maximum number of entries in the history * If null, get the defined maximum number * @param integer|null $historyMaxSize The maximum number of entries */ public function historyMaxSize ($historyMaxSize = null) // {{{ { if ($historyMaxSize === null) return $this->historyMaxSize; if (intval ($historyMaxSize) < 1) $this->consoleException ("Can not set the historyMaxSize : ". "negative value provided"); $this->historyMaxSize = intval ($historyMaxSize); } // }}} /** Error management * @param string $message The message to throw in the exception */ public function consoleException ($message) // {{{ { throw new \Exception ($message, 500); } // }}} /** Set the text color * @param integer $colorNum The color number to use */ public function colorText ($colorNum) // {{{ { if (! is_int ($colorNum)) $this->consoleException ("ColorNum provided to colorText is not an ". "integer"); echo "\033[38;5;${colorNum}m"; } // }}} /** Set the background text color * @param integer $colorNum The color number to use */ public function colorBackgroundText ($colorNum) // {{{ { if (! is_int ($colorNum)) $this->consoleException ("ColorNum provided to colorBackgroundText not ". "an integer"); echo "\033[48;5;${colorNum}m"; } // }}} /** Reset the colors */ public function colorReset () // {{{ { echo "\033[0m"; } // }}} /** Underline the text * @param boolean $underline True to underline, false to remove the underline */ public function textUnderline ($underline) // {{{ { if ($underline === false) $underline = 2; else $underline = ""; echo "\033[${underline}4m"; } // }}} /** Bold the text * @param boolean $bold True to bold, false to remove the bold */ public function textBold ($bold) // {{{ { if ($bold === false) $bold = 0; else $bold = 1; echo "\033[${bold}m"; } // }}} /** Tokenize the provided line and aggragate if there is single or double * quotes. * Trim the spaces * @param string $line The line to tokenize * @return array The tokens */ static public function tokenize ($line) // {{{ { $tokens = array (); $token = strtok (trim ($line),' '); while ($token) { // find double quoted tokens if ($token{0}=='"') { $token .= ' '.strtok('"').'"'; } // find single quoted tokens if ($token{0}=="'") { $token .= ' '.strtok("'")."'"; } $tokens[] = $token; $token = strtok(' '); } return $tokens; } // }}} /** This function return an array with each char, but supports UTF-8 * @param string $string The string to explode * @param integer $split_length The number of chars in each split * @return array */ private function mb_str_split ($string, $split_length = 1) // {{{ { $res = array(); for ($i = 0; $i < mb_strlen ($string); $i += $split_length) $res[] = mb_substr ($string, $i, $split_length); return $res; } // }}} /** This function debug the data * @param mixed $data The data to store */ private function debug ($data) // {{{ { if ($this->debug === false) return; if (is_array ($data) || is_bool ($data)) $data = var_export ($data, true); file_put_contents ($this->debug, date ("H:i:s")." $data\n", FILE_APPEND); } // }}} /** Look in the array which first chars of each possibilites are identical. * @param array $completeArr The values to examine * @return string the identical chars */ private function shortestIdenticalValues ($completeArr) // {{{ { if (! is_array ($completeArr)) return ""; $minlen = 99999; foreach ($completeArr as $val) { $minlen = min ($minlen, mb_strlen ($val)); } $identicalString = ""; $sameCharLength = 1 ; while ($sameCharLength <= $minlen) { $tmp = ""; foreach ($completeArr as $val) { $part = mb_substr ($val, 0, $sameCharLength); if ($tmp == "") $tmp = $part; if ($tmp !== $part) { break 2; } } $identicalString = $tmp; $sameCharLength++; } return $identicalString; } // }}} }