From 8da07dcf638199fcb84ad0564073eae067c92b0b Mon Sep 17 00:00:00 2001 From: Jamie Curnow Date: Wed, 15 Apr 2015 10:46:57 +1000 Subject: [PATCH] Initial Commit --- .gitignore | 3 + README.md | 94 ++++++ composer.json | 22 ++ src/jc21/CliTable.php | 544 +++++++++++++++++++++++++++++++ src/jc21/CliTableManipulator.php | 302 +++++++++++++++++ tests/data.php | 46 +++ tests/test1.php | 23 ++ tests/test2.php | 41 +++ 8 files changed, 1075 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 composer.json create mode 100644 src/jc21/CliTable.php create mode 100644 src/jc21/CliTableManipulator.php create mode 100644 tests/data.php create mode 100644 tests/test1.php create mode 100644 tests/test2.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2991df0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +.idea +vendor diff --git a/README.md b/README.md new file mode 100644 index 0000000..efdf390 --- /dev/null +++ b/README.md @@ -0,0 +1,94 @@ +CLI Table Output for PHP +================================================ + +- Nice table output of data rows +- Columns adjust to data size +- Data manipulators for fields, formats raw data to a nice display output +- Colors! When specifying a color, choose from these strings: blue, red, green, + yellow, black, magenta, cyan, white, grey + +### Installing via Composer + +```bash +# Install Composer +curl -sS https://getcomposer.org/installer | php +``` + +Next, run the Composer command to install the latest stable version of Guzzle: + +```bash +composer.phar require jc21/clitable +``` + +After installing, you need to require Composer's autoloader: + +```php +require 'vendor/autoload.php'; +``` + +### Using + +See the tests folder for some examples, but basically here's how to use it: + +```php +use jc21\CliTable; + +// $data used below is an array of rows with fields. See tests/data.php for an example. + +$table = new CliTable; +$table->setTableColor('blue'); +$table->setHeaderColor('cyan'); +$table->addField('First Name', 'firstName', false, 'white'); +$table->addField('Last Name', 'lastName', false, 'white'); +$table->addField('DOB', 'dobTime', new CliTableManipulator('datelong')); +$table->addField('Admin', 'isAdmin', new CliTableManipulator('yesno'), 'yellow'); +$table->addField('Last Seen', 'lastSeenTime', new CliTableManipulator('nicetime'), 'red'); +$table->addField('Expires', 'expires', new CliTableManipulator('duetime'), 'green'); +$table->injectData($data); +$table->display(); +``` + +### Manipulators + +These are the manipulators provided in the package: + +- dollar: Formats 12300.23 to $12,300.23 +- date: Formats unix timestamp 1372132121 to AU date 25-06-2013 (because I'm an Aussie mate) +- datelong: Formats unix timestamp 1372132121 to 25th June 2013 +- time: Formats unix timestamp 1372132121 to 1:48 pm +- datetime: Formats unix timestamp 1372132121 to 25th June 2013, 1:48 pm +- nicetime: Formats unix timestamp 1372132121 to one of the following, depending if it falls on Today or Yesterday or earlier: + - Today, 1:48 pm + - Yesterday, 1:48 pm + - 25th June 2013, 1:48 pm +- duetime: Formats unix timestamp to x years x days x hours x minutes x seconds +- nicenumber: Formats 123456 to 123,456 +- month: Formats unix timestamp 1372132121 to June +- year: Formats unix timestamp 1372132121 to 2013 +- monthyear: Formats unix timestamp 1372132121 to June 2013 +- percent: Formats 54 to 54% +- yesno: Formats a boolean value to Yes or No +- text: Strips HTML from the value before returning + +If you want to create your own manipulators: + +```php +class MyManipulator extends CliTableManipulator { + public function chucknorris($value) + { + return 'Chuck norris said: ' . $value; + } +} + +$table = new CliTable; +$table->setTableColor('blue'); +$table->setHeaderColor('cyan'); +$table->addField('First Name', 'firstName', false, 'white'); +$table->addField('Last Name', 'lastName', false, 'white'); +$table->addField('DOB', 'dobTime', new CliTableManipulator('datelong')); +$table->addField('Admin', 'isAdmin', new MyManipulator('chucknorris'), 'yellow'); +$table->addField('Last Seen', 'lastSeenTime', new CliTableManipulator('nicetime'), 'red'); +$table->addField('Expires', 'expires', new CliTableManipulator('duetime'), 'green'); +$table->injectData($data); +$table->display(); +``` \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..978474a --- /dev/null +++ b/composer.json @@ -0,0 +1,22 @@ +{ + "name": "jc21/clitable", + "description": "CLI Table output for PHP scripts", + "keywords": ["cli", "table", "command", "line"], + "homepage": "https://github.com/jc21/clitable", + "license": "BSD", + "authors": [ + { + "name": "Jamie Curnow", + "email": "jc@jc21.com" + } + ], + "minimum-stability": "dev", + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-0": { + "jc21": "src" + } + } +} diff --git a/src/jc21/CliTable.php b/src/jc21/CliTable.php new file mode 100644 index 0000000..ad8874c --- /dev/null +++ b/src/jc21/CliTable.php @@ -0,0 +1,544 @@ + | +// +---------------------------------------------------------------+ +// + +namespace jc21; + +class CliTable { + + /** + * Table Data + * + * @var object + * @access protected + * + **/ + protected $injectedData = null; + + /** + * Table Item name + * + * @var string + * @access protected + * + **/ + protected $itemName = 'Row'; + + /** + * Table fields + * + * @var array + * @access protected + * + **/ + protected $fields = array(); + + /** + * Show column headers? + * + * @var bool + * @access protected + * + **/ + protected $showHeaders = true; + + /** + * Use colors? + * + * @var bool + * @access protected + * + **/ + protected $useColors = true; + + /** + * Table Border Color + * + * @var string + * @access protected + * + **/ + protected $tableColor = 'reset'; + + /** + * Header Color + * + * @var string + * @access protected + * + **/ + protected $headerColor = 'reset'; + + /** + * Colors, will be populated after instantiation + * + * @var array + * @access protected + * + **/ + protected $colors = array(); + + /** + * Border Characters + * + * @var array + * @access protected + * + **/ + protected $chars = array( + 'top' => '═', + 'top-mid' => '╤', + 'top-left' => '╔', + 'top-right' => '╗', + 'bottom' => '═', + 'bottom-mid' => '╧', + 'bottom-left' => '╚', + 'bottom-right' => '╝', + 'left' => '║', + 'left-mid' => '╟', + 'mid' => '─', + 'mid-mid' => '┼', + 'right' => '║', + 'right-mid' => '╢', + 'middle' => '│ ', + ); + + + /** + * Constructor + * + * @access public + * @param string $itemName + * @param bool $useColors + */ + public function __construct($itemName = 'Row', $useColors = true) { + $this->setItemName($itemName); + $this->setUseColors($useColors); + $this->defineColors(); + } + + + /** + * setUseColors + * + * @access public + * @param bool $bool + * @return void + */ + public function setUseColors($bool) { + $this->useColors = (bool) $bool; + } + + + /** + * getUseColors + * + * @access public + * @return bool + */ + public function getUseColors() { + return $this->useColors; + } + + + /** + * setTableColor + * + * @access public + * @param string $color + * @return void + */ + public function setTableColor($color) { + $this->tableColor = $color; + } + + + /** + * getTableColor + * + * @access public + * @return string + */ + public function getTableColor() { + return $this->tableColor; + } + + + /** + * setChars + * + * @access public + * @param array $chars + * @return void + */ + public function setChars($chars) { + $this->chars = $chars; + } + + + /** + * setHeaderColor + * + * @access public + * @param string $color + * @return void + */ + public function setHeaderColor($color) { + $this->headerColor = $color; + } + + + /** + * getHeaderColor + * + * @access public + * @return string + */ + public function getHeaderColor() { + return $this->headerColor; + } + + + /** + * setItemName + * + * @access public + * @param string $name + * @return void + */ + public function setItemName($name) { + $this->itemName = $name; + } + + + /** + * getItemName + * + * @access public + * @return string + */ + public function getItemName() { + return $this->itemName; + } + + + /** + * injectData + * + * @access public + * @param array $data + * @return void + */ + public function injectData($data) { + $this->injectedData = $data; + } + + + /** + * setShowHeaders + * + * @access public + * @param bool $bool + * @return void + */ + public function setShowHeaders($bool) { + $this->showHeaders = $bool; + } + + + /** + * getShowHeaders + * + * @access public + * @return bool + */ + public function getShowHeaders() { + return $this->showHeaders; + } + + + /** + * getPluralItemName + * + * @access protected + * @return string + */ + protected function getPluralItemName() { + if (count($this->injectedData) == 1) { + return $this->getItemName(); + } else { + $lastChar = strtolower(substr($this->getItemName(), strlen($this->getItemName()) -1, 1)); + if ($lastChar == 's') { + return $this->getItemName() . 'es'; + } else if ($lastChar == 'y') { + return substr($this->getItemName(), 0, strlen($this->getItemName()) - 1) . 'ies'; + } else { + return $this->getItemName().'s'; + } + } + } + + + /** + * addField + * + * @access public + * @param string $fieldName + * @param string $fieldKey + * @param bool|object $manipulator + * @param string $color + * @return void + */ + public function addField($fieldName, $fieldKey, $manipulator = false, $color = 'reset') { + $this->fields[$fieldKey] = array( + 'name' => $fieldName, + 'key' => $fieldKey, + 'manipulator' => $manipulator, + 'color' => $color, + ); + } + + + /** + * get + * + * @access public + * @return string + */ + public function get() { + $rowCount = 0; + $columnLengths = array(); + $headerData = array(); + $cellData = array(); + + // Headers + if ($this->getShowHeaders()) { + foreach ($this->fields as $field) { + $headerData[$field['key']] = trim($field['name']); + + // Column Lengths + if (!isset($columnLengths[$field['key']])) { + $columnLengths[$field['key']] = 0; + } + $columnLengths[$field['key']] = max($columnLengths[$field['key']], strlen(trim($field['name']))); + } + } + + // Data + if ($this->injectedData !== null) { + if (count($this->injectedData)) { + foreach ($this->injectedData as $row) { + // Row + $cellData[$rowCount] = array(); + foreach ($this->fields as $field) { + $key = $field['key']; + $value = $row[$key]; + if ($field['manipulator'] instanceof CliTableManipulator) { + $value = trim($field['manipulator']->manipulate($value, $row, $field['name'])); + } + + $cellData[$rowCount][$key] = $value; + + // Column Lengths + if (!isset($columnLengths[$key])) { + $columnLengths[$key] = 0; + } + $columnLengths[$key] = max($columnLengths[$key], strlen($value)); + } + $rowCount++; + } + } else { + return 'There are no '.$this->getPluralItemName() . PHP_EOL; + } + } else { + return 'There is no injected data for the table!' . PHP_EOL; + } + + $response = ''; + + // Now draw the table! + $response .= $this->getTableTop($columnLengths); + if ($this->getShowHeaders()) { + $response .= $this->getFormattedRow($headerData, $columnLengths, true); + $response .= $this->getTableSeperator($columnLengths); + } + + foreach ($cellData as $row) { + $response .= $this->getFormattedRow($row, $columnLengths); + } + + $response .= $this->getTableBottom($columnLengths); + + return $response; + } + + + /** + * getFormattedRow + * + * @access protected + * @param array $rowData + * @param array $columnLengths + * @param bool $header + * @return string + */ + protected function getFormattedRow($rowData, $columnLengths, $header = false) { + $response = $this->getChar('left'); + + foreach ($rowData as $key => $field) { + if ($header) { + $color = $this->getHeaderColor(); + } else { + $color = $this->fields[$key]['color']; + } + + $fieldLength = strlen($field) + 1; + $field = ' '.($this->getUseColors() ? $this->getColorFromName($color) : '').$field; + $response .= $field; + + for ($x = $fieldLength; $x < ($columnLengths[$key] + 2); $x++) { + $response .= ' '; + } + $response .= $this->getChar('middle'); + } + + $response = substr($response, 0, strlen($response) - 3) . $this->getChar('right') . PHP_EOL; + return $response; + } + + + /** + * getTableTop + * + * @access protected + * @param array $columnLengths + * @return string + */ + protected function getTableTop($columnLengths) { + $response = $this->getChar('top-left'); + foreach ($columnLengths as $length) { + $response .= $this->getChar('top', $length + 2); + $response .= $this->getChar('top-mid'); + } + $response = substr($response, 0, strlen($response) - 3) . $this->getChar('top-right') . PHP_EOL; + return $response; + } + + + /** + * getTableBottom + * + * @access protected + * @param array $columnLengths + * @return string + */ + protected function getTableBottom($columnLengths) { + $response = $this->getChar('bottom-left'); + foreach ($columnLengths as $length) { + $response .= $this->getChar('bottom', $length + 2); + $response .= $this->getChar('bottom-mid'); + } + $response = substr($response, 0, strlen($response) - 3) . $this->getChar('bottom-right') . PHP_EOL; + return $response; + } + + + /** + * getTableSeperator + * + * @access protected + * @param array $columnLengths + * @return string + */ + protected function getTableSeperator($columnLengths) { + $response = $this->getChar('left-mid'); + foreach ($columnLengths as $length) { + $response .= $this->getChar('mid', $length + 2); + $response .= $this->getChar('mid-mid'); + } + $response = substr($response, 0, strlen($response) - 3) . $this->getChar('right-mid') . PHP_EOL; + return $response; + } + + + /** + * getChar + * + * @access protected + * @param string $type + * @param int $length + * @return string + */ + protected function getChar($type, $length = 1) { + $response = ''; + if (isset($this->chars[$type])) { + if ($this->getUseColors()) { + $response .= $this->getColorFromName($this->getTableColor()); + } + $char = trim($this->chars[$type]); + for ($x = 0; $x < $length; $x++) { + $response .= $char; + } + } + return $response; + } + + + /** + * defineColors + * + * @access protected + * @return void + */ + protected function defineColors() + { + $this->colors = array( + 'blue' => chr(27).'[1;34m', + 'red' => chr(27).'[1;31m', + 'green' => chr(27).'[1;32m', + 'yellow' => chr(27).'[1;33m', + 'black' => chr(27).'[1;30m', + 'magenta' => chr(27).'[1;35m', + 'cyan' => chr(27).'[1;36m', + 'white' => chr(27).'[1;37m', + 'grey' => chr(27).'[0;37m', + 'reset' => chr(27).'[0m', + ); + } + + + /** + * getColorFromName + * + * @access protected + * @param string $colorName + * @return string + */ + protected function getColorFromName($colorName) + { + if (isset($this->colors[$colorName])) { + return $this->colors[$colorName]; + } + return $this->colors['reset']; + } + + + /** + * display + * + * @access public + * @return void + */ + public function display() { + print $this->get(); + } + +} diff --git a/src/jc21/CliTableManipulator.php b/src/jc21/CliTableManipulator.php new file mode 100644 index 0000000..6d60dc1 --- /dev/null +++ b/src/jc21/CliTableManipulator.php @@ -0,0 +1,302 @@ + | +// +---------------------------------------------------------------+ +// + +namespace jc21; + +class CliTableManipulator { + + /** + * Stores the type of manipulation to perform + * + * @var string + * @access protected + * + **/ + protected $type = ''; + + + /** + * Constructor + * + * @access public + * @param string $type + */ + public function __construct($type) { + $this->type = $type; + } + + + /** + * manipulate + * This is used by the Table class to manipulate the data passed in and returns the formatted data. + * + * @access public + * @param mixed $value + * @param array $row + * @param string $fieldName + * @return string + */ + public function manipulate($value, $row = array(), $fieldName = '') { + $type = $this->type; + if ($type && is_callable(array($this, $type))) { + return $this->$type($value, $row, $fieldName); + } else { + error_log('Invalid Data Manipulator type: "' . $type . '"'); + return $value . ' (Invalid Type: "' . $type . '")'; + } + } + + + /** + * dollar + * Changes 12300.23 to $12,300.23 + * + * @access protected + * @param mixed $value + * @return string + */ + protected function dollar($value) { + return '$' . number_format($value, 2); + } + + + /** + * date + * Changes 1372132121 to 25-06-2013 + * + * @access protected + * @param mixed $value + * @return string + */ + protected function date($value) { + if (!$value) { + return 'Not Recorded'; + } + return date('d-m-Y', $value); + } + + + /** + * datelong + * Changes 1372132121 to 25th June 2013 + * + * @access protected + * @param mixed $value + * @return string + */ + protected function datelong($value) { + if (!$value) { + return 'Not Recorded'; + } + return date('jS F Y', $value); + } + + + /** + * time + * Changes 1372132121 to 1:48 pm + * + * @access protected + * @param mixed $value + * @return string + */ + protected function time($value) { + if (!$value) { + return 'Not Recorded'; + } + return date('g:i a', $value); + } + + + /** + * datetime + * Changes 1372132121 to 25th June 2013, 1:48 pm + * + * @access protected + * @param mixed $value + * @return string + */ + protected function datetime($value) { + if (!$value) { + return 'Not Recorded'; + } + return date('jS F Y, g:i a', $value); + } + + + /** + * nicetime + * Changes 1372132121 to 25th June 2013, 1:48 pm + * Changes 1372132121 to Today, 1:48 pm + * Changes 1372132121 to Yesterday, 1:48 pm + * + * @access protected + * @param mixed $value + * @return string + */ + protected function nicetime($value) { + if (!$value) { + return ''; + } else if ($value > mktime(0, 0, 0, date('m'), date('d'), date('Y'))) { + return 'Today ' . date('g:i a', $value); + } else if ($value > mktime(0, 0, 0, date('m'), date('d') - 1, date('Y'))) { + return 'Yesterday ' . date('g:i a', $value); + } else { + return date('jS F Y, g:i a', $value); + } + } + + + /** + * duetime + * + * @access protected + * @param mixed $value + * @return string + */ + protected function duetime($value) { + if (!$value) { + return ''; + } else { + $isPast = false; + if ($value > time()) { + $seconds = $value - time(); + } else { + $isPast = true; + $seconds = time() - $value; + } + + $text = $seconds . ' second' . ($seconds == 1 ? '' : 's'); + if ($seconds >= 60) { + $minutes = floor($seconds / 60); + $seconds -= ($minutes * 60); + $text = $minutes . ' minute' . ($minutes == 1 ? '' : 's'); + if ($minutes >= 60) { + $hours = floor($minutes / 60); + $minutes -= ($hours * 60); + $text = $hours . ' hours, ' . $minutes . ' minute' . ($hours == 1 ? '' : 's'); + if ($hours >= 24) { + $days = floor($hours / 24); + $hours -= ($days * 24); + $text = $days . ' day' . ($days == 1 ? '' : 's'); + if ($days >= 365) { + $years = floor($days / 365); + $days -= ($years * 365); + $text = $years . ' year' . ($years == 1 ? '' : 's'); + } + } + } + } + + return $text . ($isPast ? ' ago' : ''); + } + } + + + /** + * nicenumber + * + * @access protected + * @param int $value + * @return string + */ + protected function nicenumber($value) { + return number_format($value, 0); + } + + + /** + * month + * Changes 1372132121 to June + * + * @access protected + * @param mixed $value + * @return string + */ + protected function month($value) { + if (!$value) { + return 'Not Recorded'; + } + return date('F', $value); + } + + + /** + * year + * Changes 1372132121 to 2013 + * + * @access protected + * @param mixed $value + * @return string + */ + protected function year($value) { + if (!$value) { + return 'Not Recorded'; + } + return date('Y', $value); + } + + + /** + * monthyear + * Changes 1372132121 to June 2013 + * + * @access protected + * @param mixed $value + * @return string + */ + protected function monthyear($value) { + if (!$value) { + return 'Not Recorded'; + } + return date('F Y', $value); + } + + + /** + * percent + * Changes 50.2 to 50% + * + * @access protected + * @param mixed $value + * @return string + */ + protected function percent($value) { + return intval($value) . '%'; + } + + + /** + * yesno + * Changes 0/false and 1/true to No and Yes respectively + * + * @access protected + * @param mixed $value + * @return string + */ + protected function yesno($value) { + return ($value ? 'Yes' : 'No'); + } + + + /** + * text + * Strips input of any html + * + * @access protected + * @param mixed $value + * @return string + */ + protected function text($value) { + return strip_tags($value); + } +} diff --git a/tests/data.php b/tests/data.php new file mode 100644 index 0000000..5bd538d --- /dev/null +++ b/tests/data.php @@ -0,0 +1,46 @@ + 'Jamie', + 'lastName' => 'Curnow', + 'dobTime' => 390492000, + 'isAdmin' => true, + 'lastSeenTime' => time() - rand(500, 1500), + 'expires' => time() + rand(1500, 5000), + ), + array( + 'firstName' => 'Kyle', + 'lastName' => 'Shinnings', + 'dobTime' => 383061600, + 'isAdmin' => true, + 'lastSeenTime' => time() - rand(500, 1500), + 'expires' => time() + rand(1500, 5000), + ), + array( + 'firstName' => 'Samantha', + 'lastName' => 'Collerson', + 'dobTime' => 339256800, + 'isAdmin' => false, + 'lastSeenTime' => time() - rand(500, 1500), + 'expires' => time() + rand(1500, 5000), + ), + array( + 'firstName' => 'Michelle', + 'lastName' => 'Kringle', + 'dobTime' => 92844000, + 'isAdmin' => false, + 'lastSeenTime' => time() - rand(500, 1500), + 'expires' => time() + rand(1500, 5000), + ), + array( + 'firstName' => 'Timothy', + 'lastName' => 'Samuels', + 'dobTime' => 487778400, + 'isAdmin' => false, + 'lastSeenTime' => time() - rand(500, 1500), + 'expires' => time() + rand(1500, 5000), + ), +); + diff --git a/tests/test1.php b/tests/test1.php new file mode 100644 index 0000000..87cbb4b --- /dev/null +++ b/tests/test1.php @@ -0,0 +1,23 @@ +setTableColor('blue'); +$table->setHeaderColor('cyan'); +$table->addField('First Name', 'firstName', false, 'white'); +$table->addField('Last Name', 'lastName', false, 'white'); +$table->addField('DOB', 'dobTime', new CliTableManipulator('datelong')); +$table->addField('Admin', 'isAdmin', new CliTableManipulator('yesno'), 'yellow'); +$table->addField('Last Seen', 'lastSeenTime', new CliTableManipulator('nicetime'), 'red'); +$table->addField('Expires', 'expires', new CliTableManipulator('duetime'), 'green'); +$table->injectData($data); +$table->display(); + diff --git a/tests/test2.php b/tests/test2.php new file mode 100644 index 0000000..c4dc0a4 --- /dev/null +++ b/tests/test2.php @@ -0,0 +1,41 @@ +setChars(array( + 'top' => '-', + 'top-mid' => '+', + 'top-left' => '+', + 'top-right' => '+', + 'bottom' => '-', + 'bottom-mid' => '+', + 'bottom-left' => '+', + 'bottom-right' => '+', + 'left' => '|', + 'left-mid' => '+', + 'mid' => '-', + 'mid-mid' => '+', + 'right' => '|', + 'right-mid' => '+', + 'middle' => '| ', +)); + +$table->setTableColor('green'); +$table->setHeaderColor('yellow'); +$table->addField('First Name', 'firstName', false, 'cyan'); +$table->addField('Last Name', 'lastName', false, 'cyan'); +$table->addField('DOB', 'dobTime', new CliTableManipulator('datelong')); +$table->addField('Admin', 'isAdmin', new CliTableManipulator('yesno'), 'yellow'); +$table->addField('Last Seen', 'lastSeenTime', new CliTableManipulator('nicetime'), 'red'); +$table->addField('Expires', 'expires', new CliTableManipulator('duetime'), 'white'); +$table->injectData($data); +$table->display(); +