Changeset View
Changeset View
Standalone View
Standalone View
src/console/view/PhutilConsoleTable.php
- This file was added.
| <?php | |||||
| /** | |||||
| * Show a table in the console. Usage: | |||||
| * | |||||
| * $table = id(new PhutilConsoleTable()) | |||||
| * ->addColumn('id', array('title' => 'ID', 'align' => 'right')) | |||||
| * ->addColumn('name', array('title' => 'Username', 'align' => 'center')) | |||||
| * ->addColumn('email', array('title' => 'Email Address')) | |||||
| * | |||||
| * ->addRow(array( | |||||
| * 'id' => 12345, | |||||
| * 'name' => 'alicoln', | |||||
| * 'email' => 'abraham@lincoln.com', | |||||
| * )) | |||||
| * ->addRow(array( | |||||
| * 'id' => 99999999, | |||||
| * 'name' => 'jbloggs', | |||||
| * 'email' => 'joe@bloggs.com', | |||||
| * )) | |||||
| * | |||||
| * ->setBorders(true) | |||||
| * ->draw(); | |||||
| */ | |||||
| final class PhutilConsoleTable extends PhutilConsoleView { | |||||
| private $columns = array(); | |||||
| private $data = array(); | |||||
| private $widths = array(); | |||||
| private $borders = false; | |||||
| private $padding = 1; | |||||
| private $showHeader = true; | |||||
| const ALIGN_LEFT = 'left'; | |||||
| const ALIGN_CENTER = 'center'; | |||||
| const ALIGN_RIGHT = 'right'; | |||||
| /* -( Configuration )------------------------------------------------------ */ | |||||
| public function setBorders($borders) { | |||||
| $this->borders = $borders; | |||||
| return $this; | |||||
| } | |||||
| public function setPadding($padding) { | |||||
| $this->padding = $padding; | |||||
| return $this; | |||||
| } | |||||
| public function setShowHeader($show_header) { | |||||
| $this->showHeader = $show_header; | |||||
| return $this; | |||||
| } | |||||
| /* -( Data )--------------------------------------------------------------- */ | |||||
| public function addColumn($key, array $column) { | |||||
| PhutilTypeSpec::checkMap($column, array( | |||||
| 'title' => 'string', | |||||
| 'align' => 'optional string', | |||||
| )); | |||||
| $this->columns[$key] = $column; | |||||
| return $this; | |||||
| } | |||||
| public function addColumns(array $columns) { | |||||
| foreach ($columns as $key => $column) { | |||||
| $this->addColumn($key, $column); | |||||
| } | |||||
| return $this; | |||||
| } | |||||
| public function addRow(array $data) { | |||||
| $this->data[] = $data; | |||||
| foreach ($data as $key => $value) { | |||||
| $this->widths[$key] = max( | |||||
| idx($this->widths, $key, 0), | |||||
| phutil_utf8_console_strlen($value)); | |||||
| } | |||||
| return $this; | |||||
| } | |||||
| /* -( Drawing )------------------------------------------------------------ */ | |||||
| protected function drawView() { | |||||
| return $this->drawLines( | |||||
| array_merge( | |||||
| $this->getHeader(), | |||||
| $this->getBody(), | |||||
| $this->getFooter())); | |||||
| } | |||||
| private function getHeader() { | |||||
| $output = array(); | |||||
| if ($this->borders) { | |||||
| $output[] = $this->formatSeparator('='); | |||||
| } | |||||
| if (!$this->showHeader) { | |||||
| return $output; | |||||
| } | |||||
| $columns = array(); | |||||
| foreach ($this->columns as $key => $column) { | |||||
| $title = tsprintf('**%s**', $column['title']); | |||||
| if ($this->shouldAddSpacing($key, $column)) { | |||||
| $title = $this->alignString( | |||||
| $title, | |||||
| $this->getWidth($key), | |||||
| idx($column, 'align', self::ALIGN_LEFT)); | |||||
| } | |||||
| $columns[] = $title; | |||||
| } | |||||
| $output[] = $this->formatRow($columns); | |||||
| if ($this->borders) { | |||||
| $output[] = $this->formatSeparator('='); | |||||
| } | |||||
| return $output; | |||||
| } | |||||
| private function getBody() { | |||||
| $output = array(); | |||||
| foreach ($this->data as $data) { | |||||
| $columns = array(); | |||||
| foreach ($this->columns as $key => $column) { | |||||
| if (!$this->shouldAddSpacing($key, $column)) { | |||||
| $columns[] = idx($data, $key, ''); | |||||
| } else { | |||||
| $columns[] = $this->alignString( | |||||
| idx($data, $key, ''), | |||||
| $this->getWidth($key), | |||||
| idx($column, 'align', self::ALIGN_LEFT)); | |||||
| } | |||||
| } | |||||
| $output[] = $this->formatRow($columns); | |||||
| } | |||||
| return $output; | |||||
| } | |||||
| private function getFooter() { | |||||
| $output = array(); | |||||
| if ($this->borders) { | |||||
| $columns = array(); | |||||
| foreach ($this->getColumns() as $column) { | |||||
| $columns[] = str_repeat('=', $this->getWidth($column)); | |||||
| } | |||||
| $output[] = array( | |||||
| '+', | |||||
| $this->implode('+', $columns), | |||||
| '+', | |||||
| ); | |||||
| } | |||||
| return $output; | |||||
| } | |||||
| /* -( Internals )---------------------------------------------------------- */ | |||||
| /** | |||||
| * Returns if the specified column should have spacing added. | |||||
| * | |||||
| * @return bool | |||||
| */ | |||||
| private function shouldAddSpacing($key, $column) { | |||||
| if (!$this->borders) { | |||||
| if (last_key($this->columns) === $key) { | |||||
| if (idx($column, 'align', self::ALIGN_LEFT) === self::ALIGN_LEFT) { | |||||
| // Don't add extra spaces to this column since it's the last column, | |||||
| // left aligned, and we're not showing borders. This prevents | |||||
| // unnecessary empty lines from appearing when the extra spaces | |||||
| // wrap around the terminal. | |||||
| return false; | |||||
| } | |||||
| } | |||||
| } | |||||
| return true; | |||||
| } | |||||
| /** | |||||
| * Returns the column IDs. | |||||
| * | |||||
| * @return list<string> | |||||
| */ | |||||
| protected function getColumns() { | |||||
| return array_keys($this->columns); | |||||
| } | |||||
| /** | |||||
| * Get the width of a specific column, including padding. | |||||
| * | |||||
| * @param string | |||||
| * @return int | |||||
| */ | |||||
| protected function getWidth($key) { | |||||
| $width = max( | |||||
| idx($this->widths, $key), | |||||
| phutil_utf8_console_strlen( | |||||
| idx(idx($this->columns, $key, array()), 'title', ''))); | |||||
| return $width + 2 * $this->padding; | |||||
| } | |||||
| protected function alignString($string, $width, $align) { | |||||
| $num_padding = $width - | |||||
| (2 * $this->padding) - phutil_utf8_console_strlen($string); | |||||
| switch ($align) { | |||||
| case self::ALIGN_LEFT: | |||||
| $num_left_padding = 0; | |||||
| $num_right_padding = $num_padding; | |||||
| break; | |||||
| case self::ALIGN_CENTER: | |||||
| $num_left_padding = (int)($num_padding / 2); | |||||
| $num_right_padding = $num_padding - $num_left_padding; | |||||
| break; | |||||
| case self::ALIGN_RIGHT: | |||||
| $num_left_padding = $num_padding; | |||||
| $num_right_padding = 0; | |||||
| break; | |||||
| } | |||||
| $left_padding = str_repeat(' ', $num_left_padding); | |||||
| $right_padding = str_repeat(' ', $num_right_padding); | |||||
| return array( | |||||
| $left_padding, | |||||
| $string, | |||||
| $right_padding, | |||||
| ); | |||||
| } | |||||
| /** | |||||
| * Format cells into an entire row. | |||||
| * | |||||
| * @param list<string> | |||||
| * @return string | |||||
| */ | |||||
| protected function formatRow(array $columns) { | |||||
| $padding = str_repeat(' ', $this->padding); | |||||
| if ($this->borders) { | |||||
| $separator = $padding.'|'.$padding; | |||||
| return array( | |||||
| '|'.$padding, | |||||
| $this->implode($separator, $columns), | |||||
| $padding.'|', | |||||
| ); | |||||
| } else { | |||||
| return $this->implode($padding, $columns); | |||||
| } | |||||
| } | |||||
| protected function formatSeparator($string) { | |||||
| $columns = array(); | |||||
| if ($this->borders) { | |||||
| $separator = '+'; | |||||
| } else { | |||||
| $separator = ''; | |||||
| } | |||||
| foreach ($this->getColumns() as $column) { | |||||
| $columns[] = str_repeat($string, $this->getWidth($column)); | |||||
| } | |||||
| return array( | |||||
| $separator, | |||||
| $this->implode($separator, $columns), | |||||
| $separator, | |||||
| ); | |||||
| } | |||||
| } | |||||