[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/vendor/splitbrain/php-cli/src/ -> TableFormatter.php (source)

   1  <?php
   2  
   3  namespace splitbrain\phpcli;
   4  
   5  /**
   6   * Class TableFormatter
   7   *
   8   * Output text in multiple columns
   9   *
  10   * @author Andreas Gohr <andi@splitbrain.org>
  11   * @license MIT
  12   */
  13  class TableFormatter
  14  {
  15      /** @var string border between columns */
  16      protected $border = ' ';
  17  
  18      /** @var int the terminal width */
  19      protected $max = 74;
  20  
  21      /** @var Colors for coloring output */
  22      protected $colors;
  23  
  24      /**
  25       * TableFormatter constructor.
  26       *
  27       * @param Colors|null $colors
  28       */
  29      public function __construct(Colors $colors = null)
  30      {
  31          // try to get terminal width
  32          $width = $this->getTerminalWidth();
  33          if ($width) {
  34              $this->max = $width - 1;
  35          }
  36  
  37          if ($colors) {
  38              $this->colors = $colors;
  39          } else {
  40              $this->colors = new Colors();
  41          }
  42      }
  43  
  44      /**
  45       * The currently set border (defaults to ' ')
  46       *
  47       * @return string
  48       */
  49      public function getBorder()
  50      {
  51          return $this->border;
  52      }
  53  
  54      /**
  55       * Set the border. The border is set between each column. Its width is
  56       * added to the column widths.
  57       *
  58       * @param string $border
  59       */
  60      public function setBorder($border)
  61      {
  62          $this->border = $border;
  63      }
  64  
  65      /**
  66       * Width of the terminal in characters
  67       *
  68       * initially autodetected
  69       *
  70       * @return int
  71       */
  72      public function getMaxWidth()
  73      {
  74          return $this->max;
  75      }
  76  
  77      /**
  78       * Set the width of the terminal to assume (in characters)
  79       *
  80       * @param int $max
  81       */
  82      public function setMaxWidth($max)
  83      {
  84          $this->max = $max;
  85      }
  86  
  87      /**
  88       * Tries to figure out the width of the terminal
  89       *
  90       * @return int terminal width, 0 if unknown
  91       */
  92      protected function getTerminalWidth()
  93      {
  94          // from environment
  95          if (isset($_SERVER['COLUMNS'])) return (int)$_SERVER['COLUMNS'];
  96  
  97          // via tput
  98          $process = proc_open('tput cols', array(
  99              1 => array('pipe', 'w'),
 100              2 => array('pipe', 'w'),
 101          ), $pipes);
 102          $width = (int)stream_get_contents($pipes[1]);
 103          proc_close($process);
 104  
 105          return $width;
 106      }
 107  
 108      /**
 109       * Takes an array with dynamic column width and calculates the correct width
 110       *
 111       * Column width can be given as fixed char widths, percentages and a single * width can be given
 112       * for taking the remaining available space. When mixing percentages and fixed widths, percentages
 113       * refer to the remaining space after allocating the fixed width
 114       *
 115       * @param array $columns
 116       * @return int[]
 117       * @throws Exception
 118       */
 119      protected function calculateColLengths($columns)
 120      {
 121          $idx = 0;
 122          $border = $this->strlen($this->border);
 123          $fixed = (count($columns) - 1) * $border; // borders are used already
 124          $fluid = -1;
 125  
 126          // first pass for format check and fixed columns
 127          foreach ($columns as $idx => $col) {
 128              // handle fixed columns
 129              if ((string)intval($col) === (string)$col) {
 130                  $fixed += $col;
 131                  continue;
 132              }
 133              // check if other colums are using proper units
 134              if (substr($col, -1) == '%') {
 135                  continue;
 136              }
 137              if ($col == '*') {
 138                  // only one fluid
 139                  if ($fluid < 0) {
 140                      $fluid = $idx;
 141                      continue;
 142                  } else {
 143                      throw new Exception('Only one fluid column allowed!');
 144                  }
 145              }
 146              throw new Exception("unknown column format $col");
 147          }
 148  
 149          $alloc = $fixed;
 150          $remain = $this->max - $alloc;
 151  
 152          // second pass to handle percentages
 153          foreach ($columns as $idx => $col) {
 154              if (substr($col, -1) != '%') {
 155                  continue;
 156              }
 157              $perc = floatval($col);
 158  
 159              $real = (int)floor(($perc * $remain) / 100);
 160  
 161              $columns[$idx] = $real;
 162              $alloc += $real;
 163          }
 164  
 165          $remain = $this->max - $alloc;
 166          if ($remain < 0) {
 167              throw new Exception("Wanted column widths exceed available space");
 168          }
 169  
 170          // assign remaining space
 171          if ($fluid < 0) {
 172              $columns[$idx] += ($remain); // add to last column
 173          } else {
 174              $columns[$fluid] = $remain;
 175          }
 176  
 177          return $columns;
 178      }
 179  
 180      /**
 181       * Displays text in multiple word wrapped columns
 182       *
 183       * @param int[] $columns list of column widths (in characters, percent or '*')
 184       * @param string[] $texts list of texts for each column
 185       * @param array $colors A list of color names to use for each column. use empty string for default
 186       * @return string
 187       * @throws Exception
 188       */
 189      public function format($columns, $texts, $colors = array())
 190      {
 191          $columns = $this->calculateColLengths($columns);
 192  
 193          $wrapped = array();
 194          $maxlen = 0;
 195  
 196          foreach ($columns as $col => $width) {
 197              $wrapped[$col] = explode("\n", $this->wordwrap($texts[$col], $width, "\n", true));
 198              $len = count($wrapped[$col]);
 199              if ($len > $maxlen) {
 200                  $maxlen = $len;
 201              }
 202  
 203          }
 204  
 205          $last = count($columns) - 1;
 206          $out = '';
 207          for ($i = 0; $i < $maxlen; $i++) {
 208              foreach ($columns as $col => $width) {
 209                  if (isset($wrapped[$col][$i])) {
 210                      $val = $wrapped[$col][$i];
 211                  } else {
 212                      $val = '';
 213                  }
 214                  $chunk = $this->pad($val, $width);
 215                  if (isset($colors[$col]) && $colors[$col]) {
 216                      $chunk = $this->colors->wrap($chunk, $colors[$col]);
 217                  }
 218                  $out .= $chunk;
 219  
 220                  // border
 221                  if ($col != $last) {
 222                      $out .= $this->border;
 223                  }
 224              }
 225              $out .= "\n";
 226          }
 227          return $out;
 228  
 229      }
 230  
 231      /**
 232       * Pad the given string to the correct length
 233       *
 234       * @param string $string
 235       * @param int $len
 236       * @return string
 237       */
 238      protected function pad($string, $len)
 239      {
 240          $strlen = $this->strlen($string);
 241          if ($strlen > $len) return $string;
 242  
 243          $pad = $len - $strlen;
 244          return $string . str_pad('', $pad, ' ');
 245      }
 246  
 247      /**
 248       * Measures char length in UTF-8 when possible
 249       *
 250       * @param $string
 251       * @return int
 252       */
 253      protected function strlen($string)
 254      {
 255          // don't count color codes
 256          $string = preg_replace("/\33\\[\\d+(;\\d+)?m/", '', $string);
 257  
 258          if (function_exists('mb_strlen')) {
 259              return mb_strlen($string, 'utf-8');
 260          }
 261  
 262          return strlen($string);
 263      }
 264  
 265      /**
 266       * @param string $string
 267       * @param int $start
 268       * @param int|null $length
 269       * @return string
 270       */
 271      protected function substr($string, $start = 0, $length = null)
 272      {
 273          if (function_exists('mb_substr')) {
 274              return mb_substr($string, $start, $length);
 275          } else {
 276              // mb_substr() treats $length differently than substr()
 277              if ($length) {
 278                  return substr($string, $start, $length);
 279              } else {
 280                  return substr($string, $start);
 281              }
 282          }
 283      }
 284  
 285      /**
 286       * @param string $str
 287       * @param int $width
 288       * @param string $break
 289       * @param bool $cut
 290       * @return string
 291       * @link http://stackoverflow.com/a/4988494
 292       */
 293      protected function wordwrap($str, $width = 75, $break = "\n", $cut = false)
 294      {
 295          $lines = explode($break, $str);
 296          foreach ($lines as &$line) {
 297              $line = rtrim($line);
 298              if ($this->strlen($line) <= $width) {
 299                  continue;
 300              }
 301              $words = explode(' ', $line);
 302              $line = '';
 303              $actual = '';
 304              foreach ($words as $word) {
 305                  if ($this->strlen($actual . $word) <= $width) {
 306                      $actual .= $word . ' ';
 307                  } else {
 308                      if ($actual != '') {
 309                          $line .= rtrim($actual) . $break;
 310                      }
 311                      $actual = $word;
 312                      if ($cut) {
 313                          while ($this->strlen($actual) > $width) {
 314                              $line .= $this->substr($actual, 0, $width) . $break;
 315                              $actual = $this->substr($actual, $width);
 316                          }
 317                      }
 318                      $actual .= ' ';
 319                  }
 320              }
 321              $line .= trim($actual);
 322          }
 323          return implode($break, $lines);
 324      }
 325  }