first commit
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Basic;
|
||||
|
||||
use League\CLImate\Decorator\Parser\ParserImporter;
|
||||
use League\CLImate\Settings\SettingsImporter;
|
||||
use League\CLImate\Util\UtilImporter;
|
||||
|
||||
abstract class BasicTerminalObject implements BasicTerminalObjectInterface
|
||||
{
|
||||
use SettingsImporter, ParserImporter, UtilImporter;
|
||||
|
||||
/**
|
||||
* Set the property if there is a valid value
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $value
|
||||
*/
|
||||
protected function set($key, $value)
|
||||
{
|
||||
if (strlen($value)) {
|
||||
$this->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parser for the current object
|
||||
*
|
||||
* @return \League\CLImate\Decorator\Parser\Parser
|
||||
*/
|
||||
public function getParser()
|
||||
{
|
||||
return $this->parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this object requires a new line to be added after the output
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function sameLine()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Basic;
|
||||
|
||||
use League\CLImate\Decorator\Parser\Parser;
|
||||
use League\CLImate\Util\UtilFactory;
|
||||
|
||||
interface BasicTerminalObjectInterface
|
||||
{
|
||||
public function result();
|
||||
|
||||
public function settings();
|
||||
|
||||
/**
|
||||
* @param $setting
|
||||
* @return void
|
||||
*/
|
||||
public function importSetting($setting);
|
||||
|
||||
/**
|
||||
* @return boolean
|
||||
*/
|
||||
public function sameLine();
|
||||
|
||||
/**
|
||||
* @param \League\CLImate\Decorator\Parser\Parser $parser
|
||||
*/
|
||||
public function parser(Parser $parser);
|
||||
|
||||
/**
|
||||
* @param UtilFactory $util
|
||||
*/
|
||||
public function util(UtilFactory $util);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Basic;
|
||||
|
||||
class Border extends BasicTerminalObject
|
||||
{
|
||||
/**
|
||||
* The character to repeat for the border
|
||||
*
|
||||
* @var string $char
|
||||
*/
|
||||
protected $char = '-';
|
||||
|
||||
/**
|
||||
* The length of the border
|
||||
*
|
||||
* @var integer $length
|
||||
*/
|
||||
protected $length;
|
||||
|
||||
public function __construct($char = null, $length = null)
|
||||
{
|
||||
$this->char($char)->length($length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the character to repeat for the border
|
||||
*
|
||||
* @param string $char
|
||||
*
|
||||
* @return Border
|
||||
*/
|
||||
public function char($char)
|
||||
{
|
||||
$this->set('char', $char);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the length of the border
|
||||
*
|
||||
* @param integer $length
|
||||
*
|
||||
* @return Border
|
||||
*/
|
||||
public function length($length)
|
||||
{
|
||||
$this->set('length', $length);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the border
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function result()
|
||||
{
|
||||
$length = $this->length ?: $this->util->width() ?: 100;
|
||||
$str = str_repeat($this->char, $length);
|
||||
$str = substr($str, 0, $length);
|
||||
|
||||
return $str;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Basic;
|
||||
|
||||
class Br extends Repeatable
|
||||
{
|
||||
/**
|
||||
* Return an empty string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function result()
|
||||
{
|
||||
return array_fill(0, $this->count, '');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Basic;
|
||||
|
||||
class Clear extends BasicTerminalObject
|
||||
{
|
||||
/**
|
||||
* Clear the terminal
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function result()
|
||||
{
|
||||
return "\e[H\e[2J";
|
||||
}
|
||||
|
||||
public function sameLine()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Basic;
|
||||
|
||||
use League\CLImate\TerminalObject\Helper\StringLength;
|
||||
|
||||
class Columns extends BasicTerminalObject
|
||||
{
|
||||
use StringLength;
|
||||
|
||||
/**
|
||||
* Number of columns
|
||||
*
|
||||
* @var integer $column_count
|
||||
*/
|
||||
protected $column_count;
|
||||
|
||||
/**
|
||||
* Data to columnize
|
||||
*
|
||||
* @var array $data
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
public function __construct($data, $column_count = null)
|
||||
{
|
||||
$this->data = $data;
|
||||
$this->column_count = $column_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of columns organize data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function result()
|
||||
{
|
||||
$keys = array_keys($this->data);
|
||||
$first_key = reset($keys);
|
||||
|
||||
return (!is_int($first_key)) ? $this->associativeColumns() : $this->columns();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get columns for a regular array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function columns()
|
||||
{
|
||||
$this->data = $this->setData();
|
||||
$column_widths = $this->getColumnWidths();
|
||||
$output = [];
|
||||
$count = count(reset($this->data));
|
||||
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$output[] = $this->getRow($i, $column_widths);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-configure the data into it's final form
|
||||
*/
|
||||
protected function setData()
|
||||
{
|
||||
// If it's already an array of arrays, we're good to go
|
||||
if (is_array(reset($this->data))) {
|
||||
return $this->setArrayOfArraysData();
|
||||
}
|
||||
|
||||
$column_width = $this->getColumnWidth($this->data);
|
||||
$row_count = $this->getMaxRows($column_width);
|
||||
|
||||
return array_chunk($this->data, $row_count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-configure an array of arrays into column arrays
|
||||
*/
|
||||
protected function setArrayOfArraysData()
|
||||
{
|
||||
$this->setColumnCountViaArray($this->data);
|
||||
|
||||
$new_data = array_fill(0, $this->column_count, []);
|
||||
|
||||
foreach ($this->data as $items) {
|
||||
for ($i = 0; $i < $this->column_count; $i++) {
|
||||
$new_data[$i][] = (array_key_exists($i, $items)) ? $items[$i] : null;
|
||||
}
|
||||
}
|
||||
|
||||
return $new_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get columns for an associative array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function associativeColumns()
|
||||
{
|
||||
$column_width = $this->getColumnWidth(array_keys($this->data));
|
||||
$output = [];
|
||||
|
||||
foreach ($this->data as $key => $value) {
|
||||
$output[] = $this->pad($key, $column_width) . $value;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the row of data
|
||||
*
|
||||
* @param integer $key
|
||||
* @param array $column_widths
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getRow($key, $column_widths)
|
||||
{
|
||||
$row = [];
|
||||
|
||||
for ($j = 0; $j < $this->column_count; $j++) {
|
||||
if (isset($this->data[$j]) && array_key_exists($key, $this->data[$j])) {
|
||||
$row[] = $this->pad($this->data[$j][$key], $column_widths[$j]);
|
||||
}
|
||||
}
|
||||
|
||||
return trim(implode('', $row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the standard column width
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
protected function getColumnWidth($data)
|
||||
{
|
||||
// Return the maximum width plus a buffer
|
||||
return $this->maxStrLen($data) + 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of each column's width
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getColumnWidths()
|
||||
{
|
||||
$column_widths = [];
|
||||
|
||||
for ($i = 0; $i < $this->column_count; $i++) {
|
||||
if (!isset($this->data[$i])) {
|
||||
$column_widths[] = 0;
|
||||
continue;
|
||||
}
|
||||
$column_widths[] = $this->getColumnWidth($this->data[$i]);
|
||||
}
|
||||
|
||||
return $column_widths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the count property
|
||||
*
|
||||
* @param integer $column_width
|
||||
*/
|
||||
protected function setColumnCount($column_width)
|
||||
{
|
||||
$this->column_count = (int) floor($this->util->width() / $column_width);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the count property via an array
|
||||
*
|
||||
* @param array $items
|
||||
*/
|
||||
protected function setColumnCountViaArray($items)
|
||||
{
|
||||
$counts = array_map(function ($arr) {
|
||||
return count($arr);
|
||||
}, $items);
|
||||
|
||||
$this->column_count = max($counts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of rows per column
|
||||
*
|
||||
* @param integer $column_width
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
protected function getMaxRows($column_width)
|
||||
{
|
||||
if (!$this->column_count) {
|
||||
$this->setColumnCount($column_width);
|
||||
}
|
||||
|
||||
return ceil(count($this->data) / $this->column_count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Basic;
|
||||
|
||||
use League\CLImate\TerminalObject\Helper\Art;
|
||||
|
||||
class Draw extends BasicTerminalObject
|
||||
{
|
||||
use Art;
|
||||
|
||||
public function __construct($art)
|
||||
{
|
||||
// Add the default art directory
|
||||
$this->addDir(__DIR__ . '/../../ASCII');
|
||||
|
||||
$this->art = $art;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the art
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function result()
|
||||
{
|
||||
$file = $this->artFile($this->art) ?: $this->artFile($this->default_art);
|
||||
|
||||
return $this->parse($file);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Basic;
|
||||
|
||||
class Dump extends BasicTerminalObject
|
||||
{
|
||||
/**
|
||||
* The data to convert to JSON
|
||||
*
|
||||
* @var mixed $data
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
public function __construct($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data as JSON
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function result()
|
||||
{
|
||||
ob_start();
|
||||
|
||||
var_dump($this->data);
|
||||
|
||||
$result = ob_get_contents();
|
||||
|
||||
ob_end_clean();
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Basic;
|
||||
|
||||
class Flank extends BasicTerminalObject
|
||||
{
|
||||
/**
|
||||
* The string that will be flanked
|
||||
*
|
||||
* @var string $str
|
||||
*/
|
||||
protected $str;
|
||||
|
||||
/**
|
||||
* The character(s) to repeat on either side of the string
|
||||
*
|
||||
* @var string $char
|
||||
*/
|
||||
protected $char = '#';
|
||||
|
||||
/**
|
||||
* How many times the character(s) should be repeated on either side
|
||||
*
|
||||
* @var integer $repeat
|
||||
*/
|
||||
protected $repeat = 3;
|
||||
|
||||
public function __construct($str, $char = null, $repeat = null)
|
||||
{
|
||||
$this->str = $str;
|
||||
|
||||
$this->char($char)->repeat($repeat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the character(s) to repeat on either side
|
||||
*
|
||||
* @param string $char
|
||||
*
|
||||
* @return Flank
|
||||
*/
|
||||
public function char($char)
|
||||
{
|
||||
$this->set('char', $char);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the repeat of the flank character(s)
|
||||
*
|
||||
* @param integer $repeat
|
||||
*
|
||||
* @return Flank
|
||||
*/
|
||||
public function repeat($repeat)
|
||||
{
|
||||
$this->set('repeat', $repeat);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the flanked string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function result()
|
||||
{
|
||||
$flank = str_repeat($this->char, $this->repeat);
|
||||
|
||||
return "{$flank} {$this->str} {$flank}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Basic;
|
||||
|
||||
class Inline extends Out
|
||||
{
|
||||
/**
|
||||
* Check if this object requires a new line should be added after the output
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function sameLine()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Basic;
|
||||
|
||||
class Json extends BasicTerminalObject
|
||||
{
|
||||
/**
|
||||
* The data to convert to JSON
|
||||
*
|
||||
* @var mixed $data
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
public function __construct($data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data as JSON
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function result()
|
||||
{
|
||||
return json_encode($this->data, JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Basic;
|
||||
|
||||
class Out extends BasicTerminalObject
|
||||
{
|
||||
/**
|
||||
* The content to output
|
||||
*
|
||||
* @var string $content
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
public function __construct($content)
|
||||
{
|
||||
$this->content = $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the content to output
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function result()
|
||||
{
|
||||
return $this->content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Basic;
|
||||
|
||||
abstract class Repeatable extends BasicTerminalObject
|
||||
{
|
||||
/**
|
||||
* How many times the element should be repeated
|
||||
*
|
||||
* @var integer
|
||||
*/
|
||||
protected $count;
|
||||
|
||||
public function __construct($count = 1)
|
||||
{
|
||||
$this->count = (int) round(max((int) $count, 1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Basic;
|
||||
|
||||
/**
|
||||
* Tab class to enable tabs to be output without using the escape character.
|
||||
*/
|
||||
class Tab extends Repeatable
|
||||
{
|
||||
/**
|
||||
* Check if this object requires a new line should be added after the output.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function sameLine()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the relevant number of tab characters.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function result()
|
||||
{
|
||||
return str_repeat("\t", $this->count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Basic;
|
||||
|
||||
use League\CLImate\TerminalObject\Helper\StringLength;
|
||||
|
||||
class Table extends BasicTerminalObject
|
||||
{
|
||||
use StringLength;
|
||||
|
||||
/**
|
||||
* The data for the table, an array of (arrays|objects)
|
||||
*
|
||||
* @var array $data
|
||||
*/
|
||||
protected $data = [];
|
||||
|
||||
/**
|
||||
* An array of the widths of each column in the table
|
||||
*
|
||||
* @var array $column_widths
|
||||
*/
|
||||
protected $column_widths = [];
|
||||
|
||||
/**
|
||||
* The width of the table
|
||||
*
|
||||
* @var integer $table_width
|
||||
*/
|
||||
protected $table_width = 0;
|
||||
|
||||
/**
|
||||
* The divider between table cells
|
||||
*
|
||||
* @var string $column_divider
|
||||
*/
|
||||
protected $column_divider = ' | ';
|
||||
|
||||
/**
|
||||
* The border to divide each row of the table
|
||||
*
|
||||
* @var string $border
|
||||
*/
|
||||
protected $border;
|
||||
|
||||
/**
|
||||
* The array of rows that will ultimately be returned
|
||||
*
|
||||
* @var array $rows
|
||||
*/
|
||||
protected $rows = [];
|
||||
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the built rows
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function result()
|
||||
{
|
||||
$this->column_widths = $this->getColumnWidths();
|
||||
$this->table_width = $this->getWidth();
|
||||
$this->border = $this->getBorder();
|
||||
|
||||
$this->buildHeaderRow();
|
||||
|
||||
foreach ($this->data as $key => $columns) {
|
||||
$this->rows[] = $this->buildRow($columns);
|
||||
$this->rows[] = $this->border;
|
||||
}
|
||||
|
||||
return $this->rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the width of the table
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
protected function getWidth()
|
||||
{
|
||||
$first_row = reset($this->data);
|
||||
$first_row = $this->buildRow($first_row);
|
||||
|
||||
return $this->lengthWithoutTags($first_row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the border for each row based on the table width
|
||||
*/
|
||||
protected function getBorder()
|
||||
{
|
||||
return (new Border())->length($this->table_width)->result();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a header row (if it's an array of associative arrays or objects),
|
||||
* if there is one, tack it onto the front of the rows array
|
||||
*/
|
||||
protected function buildHeaderRow()
|
||||
{
|
||||
$header_row = $this->getHeaderRow();
|
||||
|
||||
if ($header_row) {
|
||||
$this->rows[] = $this->border;
|
||||
$this->rows[] = $this->buildRow($header_row);
|
||||
$this->rows[] = (new Border())->char('=')->length($this->table_width)->result();
|
||||
} else {
|
||||
$this->rows[] = $this->border;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get table row
|
||||
*
|
||||
* @param mixed $columns
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function buildRow($columns)
|
||||
{
|
||||
$row = [];
|
||||
|
||||
foreach ($columns as $key => $column) {
|
||||
$row[] = $this->buildCell($key, $column);
|
||||
}
|
||||
|
||||
$row = implode($this->column_divider, $row);
|
||||
|
||||
return trim($this->column_divider . $row . $this->column_divider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the string for this particular table cell
|
||||
*
|
||||
* @param mixed $key
|
||||
* @param string $column
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function buildCell($key, $column)
|
||||
{
|
||||
return $this->pad($column, $this->column_widths[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the header row for the table if it's an associative array or object
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getHeaderRow()
|
||||
{
|
||||
$first_item = reset($this->data);
|
||||
|
||||
if (is_object($first_item)) {
|
||||
$first_item = get_object_vars($first_item);
|
||||
}
|
||||
|
||||
$keys = array_keys($first_item);
|
||||
$first_key = reset($keys);
|
||||
|
||||
// We have an associative array (probably), let's have a header row
|
||||
if (!is_int($first_key)) {
|
||||
return array_combine($keys, $keys);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the width of each column
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getColumnWidths()
|
||||
{
|
||||
$first_row = reset($this->data);
|
||||
|
||||
if (is_object($first_row)) {
|
||||
$first_row = get_object_vars($first_row);
|
||||
}
|
||||
|
||||
// Create an array with the columns as keys and values of zero
|
||||
$column_widths = $this->getDefaultColumnWidths($first_row);
|
||||
|
||||
foreach ($this->data as $columns) {
|
||||
foreach ($columns as $key => $column) {
|
||||
$column_widths[$key] = $this->getCellWidth($column_widths[$key], $column);
|
||||
}
|
||||
}
|
||||
|
||||
return $column_widths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up an array of default column widths
|
||||
*
|
||||
* @param array $columns
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDefaultColumnWidths(array $columns)
|
||||
{
|
||||
$widths = $this->arrayOfStrLens(array_keys($columns));
|
||||
|
||||
return array_combine(array_keys($columns), $widths);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the width of the columns without tags
|
||||
*
|
||||
* @param array $current_width
|
||||
* @param string $str
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
protected function getCellWidth($current_width, $str)
|
||||
{
|
||||
return max($current_width, $this->lengthWithoutTags($str));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Dynamic;
|
||||
|
||||
use League\CLImate\TerminalObject\Dynamic\Animation\Keyframe;
|
||||
use League\CLImate\TerminalObject\Helper\Art;
|
||||
use League\CLImate\TerminalObject\Helper\Sleeper;
|
||||
|
||||
class Animation extends DynamicTerminalObject
|
||||
{
|
||||
use Art;
|
||||
|
||||
/**
|
||||
* @var \League\CLImate\TerminalObject\Helper\Sleeper $sleeper
|
||||
*/
|
||||
protected $sleeper;
|
||||
|
||||
/**
|
||||
* @var \League\CLImate\TerminalObject\Dynamic\Animation\Keyframe $keyframes
|
||||
*/
|
||||
protected $keyframes;
|
||||
|
||||
public function __construct($art, Sleeper $sleeper = null, Keyframe $keyframes = null)
|
||||
{
|
||||
// Add the default art directory
|
||||
$this->addDir(__DIR__ . '/../../ASCII');
|
||||
|
||||
$this->setSleeper($sleeper);
|
||||
$this->setKeyFrames($keyframes);
|
||||
|
||||
$this->art = $art;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a basic animation
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
$files = $this->artDir($this->art);
|
||||
$animation = [];
|
||||
|
||||
foreach ($files as $file) {
|
||||
$animation[] = $this->parse($file);
|
||||
}
|
||||
|
||||
$this->animate($animation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the speed of the animation based on a percentage
|
||||
* (50% slower, 200% faster, etc)
|
||||
*
|
||||
* @param int|float $percentage
|
||||
*
|
||||
* @return \League\CLImate\TerminalObject\Dynamic\Animation
|
||||
*/
|
||||
public function speed($percentage)
|
||||
{
|
||||
$this->sleeper->speed($percentage);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the art
|
||||
*
|
||||
* @param string $direction
|
||||
* @return bool
|
||||
*/
|
||||
public function scroll($direction = 'right')
|
||||
{
|
||||
$this->setupKeyframes();
|
||||
|
||||
$mapping = $this->getScrollDirectionMapping();
|
||||
|
||||
if (!array_key_exists($direction, $mapping)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lines = $this->getLines();
|
||||
$enter_from = $mapping[$direction];
|
||||
$exit_to = $mapping[$enter_from];
|
||||
|
||||
$this->animate($this->keyframes->scroll($lines, $enter_from, $exit_to));
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate the art exiting the screen
|
||||
*
|
||||
* @param string $direction top|bottom|right|left
|
||||
*/
|
||||
public function exitTo($direction)
|
||||
{
|
||||
$this->setupKeyframes();
|
||||
|
||||
$this->animate($this->keyframes->exitTo($this->getLines(), $direction));
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate the art entering the screen
|
||||
*
|
||||
* @param string $direction top|bottom|right|left
|
||||
*/
|
||||
public function enterFrom($direction)
|
||||
{
|
||||
$this->setupKeyframes();
|
||||
|
||||
$this->animate($this->keyframes->enterFrom($this->getLines(), $direction));
|
||||
}
|
||||
|
||||
protected function getScrollDirectionMapping()
|
||||
{
|
||||
return [
|
||||
'left' => 'right',
|
||||
'right' => 'left',
|
||||
'top' => 'bottom',
|
||||
'bottom' => 'top',
|
||||
'up' => 'bottom',
|
||||
'down' => 'top',
|
||||
];
|
||||
}
|
||||
|
||||
protected function getLines()
|
||||
{
|
||||
return $this->parse($this->artFile($this->art));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \League\CLImate\TerminalObject\Helper\Sleeper $sleeper
|
||||
*/
|
||||
protected function setSleeper($sleeper = null)
|
||||
{
|
||||
$this->sleeper = $sleeper ?: new Sleeper();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param League\CLImate\TerminalObject\Dynamic\Animation\Keyframe $keyframes
|
||||
*/
|
||||
protected function setKeyFrames($keyframes)
|
||||
{
|
||||
$this->keyframes = $keyframes ?: new Keyframe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the necessary properties on the Keyframe class
|
||||
*/
|
||||
protected function setupKeyframes()
|
||||
{
|
||||
$this->keyframes->parser($this->parser);
|
||||
$this->keyframes->util($this->util);
|
||||
}
|
||||
|
||||
/**
|
||||
* Animate the given keyframes
|
||||
*
|
||||
* @param array $keyframes Array of arrays
|
||||
*/
|
||||
protected function animate(array $keyframes)
|
||||
{
|
||||
$count = 0;
|
||||
|
||||
foreach ($keyframes as $lines) {
|
||||
$this->writeKeyFrame($lines, $count);
|
||||
$this->sleeper->sleep();
|
||||
$count = count($lines);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the current keyframe to the terminal, line by line
|
||||
*
|
||||
* @param array $lines
|
||||
* @param integer $count
|
||||
*/
|
||||
protected function writeKeyFrame(array $lines, $count)
|
||||
{
|
||||
foreach ($lines as $key => $line) {
|
||||
$content = $this->getLineFormatted($line, $key, $count);
|
||||
$this->output->write($this->parser->apply($content));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the line to re-write previous lines, if necessary
|
||||
*
|
||||
* @param string $line
|
||||
* @param integer $key
|
||||
* @param integer $last_frame_count
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getLineFormatted($line, $key, $last_frame_count)
|
||||
{
|
||||
// If this is the first thing we're writing, just return the line
|
||||
if ($last_frame_count == 0) {
|
||||
return $line;
|
||||
}
|
||||
|
||||
$content = '';
|
||||
|
||||
// If this is the first line of the frame,
|
||||
// move the cursor up the total number of previous lines from the previous frame
|
||||
if ($key == 0) {
|
||||
$content .= $this->util->cursor->up($last_frame_count);
|
||||
}
|
||||
|
||||
$content .= $this->util->cursor->startOfCurrentLine();
|
||||
$content .= $this->util->cursor->deleteCurrentLine();
|
||||
$content .= $line;
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Dynamic\Animation;
|
||||
|
||||
use League\CLImate\Decorator\Parser\ParserImporter;
|
||||
use League\CLImate\TerminalObject\Helper\StringLength;
|
||||
use League\CLImate\Util\UtilImporter;
|
||||
|
||||
class Keyframe
|
||||
{
|
||||
use StringLength, ParserImporter, UtilImporter;
|
||||
|
||||
/**
|
||||
* Get the enter keyframes for the desired direction
|
||||
*
|
||||
* @param array $lines
|
||||
* @param string $direction
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function enterFrom($lines, $direction)
|
||||
{
|
||||
return array_reverse($this->exitTo($lines, $direction));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the exit keyframes for the desired direction
|
||||
*
|
||||
* @param array $lines
|
||||
* @param string $direction
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function exitTo($lines, $direction)
|
||||
{
|
||||
$lines = $this->adjustLines($lines, $direction);
|
||||
$line_method = $this->getLineMethod($direction);
|
||||
|
||||
$direction_keyframes = $this->getDirectionFrames($direction, $lines, $line_method);
|
||||
|
||||
$keyframes = array_fill(0, 4, $lines);
|
||||
$keyframes = array_merge($keyframes, $direction_keyframes);
|
||||
$keyframes[] = array_fill(0, count($lines), '');
|
||||
|
||||
return $keyframes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get scroll keyframes
|
||||
*
|
||||
* @param array $lines
|
||||
* @param string $enter_from
|
||||
* @param string $exit_to
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function scroll($lines, $enter_from, $exit_to)
|
||||
{
|
||||
$keyframes = $this->enterFrom($lines, $enter_from);
|
||||
$keyframes = array_merge($keyframes, $this->exitTo($lines, $exit_to));
|
||||
$keyframes = array_unique($keyframes, SORT_REGULAR);
|
||||
$keyframes[] = reset($keyframes);
|
||||
|
||||
return $keyframes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the line parser for the direction
|
||||
*
|
||||
* @param string $direction
|
||||
* @return string
|
||||
*/
|
||||
protected function getLineMethod($direction)
|
||||
{
|
||||
return 'current' . ucwords(strtolower($direction)) . 'Line';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust the array of lines if necessary
|
||||
*
|
||||
* @param array $lines
|
||||
* @param string $direction
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function adjustLines(array $lines, $direction)
|
||||
{
|
||||
$adjust_method = 'adjust' . ucwords(strtolower($direction)) . 'Lines';
|
||||
|
||||
if (method_exists($this, $adjust_method)) {
|
||||
return $this->$adjust_method($lines);
|
||||
}
|
||||
|
||||
return $lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad the array of lines for "right" animation
|
||||
*
|
||||
* @param array $lines
|
||||
* @return array
|
||||
*/
|
||||
protected function adjustRightLines(array $lines)
|
||||
{
|
||||
return $this->padArray($lines, $this->util->width());
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad the array of lines for "left" animation
|
||||
*
|
||||
* @param array $lines
|
||||
* @return array
|
||||
*/
|
||||
protected function adjustLeftLines(array $lines)
|
||||
{
|
||||
return $this->padArray($lines, $this->maxStrLen($lines));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the keyframes appropriate for the animation direction
|
||||
*
|
||||
* @param string $direction
|
||||
* @param array $lines
|
||||
* @param string $line_method
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getDirectionFrames($direction, array $lines, $line_method)
|
||||
{
|
||||
$mapping = [
|
||||
'exitHorizontalFrames' => ['left', 'right'],
|
||||
'exitVerticalFrames' => ['top', 'bottom'],
|
||||
];
|
||||
|
||||
foreach ($mapping as $method => $directions) {
|
||||
if (in_array($direction, $directions)) {
|
||||
return $this->$method($lines, $line_method);
|
||||
}
|
||||
}
|
||||
|
||||
// Fail gracefully, simply return an array
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create horizontal exit animation keyframes for the art
|
||||
*
|
||||
* @param array $lines
|
||||
* @param string $line_method
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function exitHorizontalFrames(array $lines, $line_method)
|
||||
{
|
||||
$keyframes = [];
|
||||
$length = strlen($lines[0]);
|
||||
|
||||
for ($i = $length; $i > 0; $i--) {
|
||||
$keyframes[] = $this->getHorizontalKeyframe($lines, $i, $line_method, $length);
|
||||
}
|
||||
|
||||
return $keyframes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the keyframe for a horizontal animation
|
||||
*
|
||||
* @param array $lines
|
||||
* @param int $frame_number
|
||||
* @param string $line_method
|
||||
* @param int $length
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getHorizontalKeyframe(array $lines, $frame_number, $line_method, $length)
|
||||
{
|
||||
$keyframe = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$keyframe[] = $this->$line_method($line, $frame_number, $length);
|
||||
}
|
||||
|
||||
return $keyframe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create vertical exit animation keyframes for the art
|
||||
*
|
||||
* @param array $lines
|
||||
* @param string $line_method
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function exitVerticalFrames(array $lines, $line_method)
|
||||
{
|
||||
$keyframes = [];
|
||||
$line_count = count($lines);
|
||||
|
||||
for ($i = $line_count - 1; $i >= 0; $i--) {
|
||||
$keyframes[] = $this->$line_method($lines, $line_count, $i);
|
||||
}
|
||||
|
||||
return $keyframes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current line as it is exiting left
|
||||
*
|
||||
* @param string $line
|
||||
* @param int $frame_number
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function currentLeftLine($line, $frame_number)
|
||||
{
|
||||
return substr($line, -$frame_number);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the current line as it is exiting right
|
||||
*
|
||||
* @param string $line
|
||||
* @param int $frame_number
|
||||
* @param int $length
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function currentRightLine($line, $frame_number, $length)
|
||||
{
|
||||
return str_repeat(' ', $length - $frame_number) . substr($line, 0, $frame_number);
|
||||
}
|
||||
|
||||
/**
|
||||
* Slice off X number of lines from the bottom and fill the rest with empty strings
|
||||
*
|
||||
* @param array $lines
|
||||
* @param integer $total_lines
|
||||
* @param integer $current
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function currentTopLine($lines, $total_lines, $current)
|
||||
{
|
||||
$keyframe = array_slice($lines, -$current, $current);
|
||||
|
||||
return array_merge($keyframe, array_fill(0, $total_lines - $current, ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Slice off X number of lines from the top and fill the rest with empty strings
|
||||
*
|
||||
* @param array $lines
|
||||
* @param integer $total_lines
|
||||
* @param integer $current
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function currentBottomLine($lines, $total_lines, $current)
|
||||
{
|
||||
$keyframe = array_fill(0, $total_lines - $current, '');
|
||||
|
||||
return array_merge($keyframe, array_slice($lines, 0, $current));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Dynamic\Checkbox;
|
||||
|
||||
use League\CLImate\Decorator\Parser\ParserImporter;
|
||||
use League\CLImate\TerminalObject\Helper\StringLength;
|
||||
use League\CLImate\Util\UtilImporter;
|
||||
|
||||
class Checkbox
|
||||
{
|
||||
use StringLength, ParserImporter, UtilImporter;
|
||||
|
||||
/**
|
||||
* The value of the checkbox
|
||||
*
|
||||
* @var string|int|bool $value
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* The label for the checkbox
|
||||
*
|
||||
* @var string|int $label
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* Whether the checkbox is checked
|
||||
*
|
||||
* @var bool $checked
|
||||
*/
|
||||
protected $checked = false;
|
||||
|
||||
/**
|
||||
* Whether pointer is currently pointing at the checkbox
|
||||
*
|
||||
* @var bool $current
|
||||
*/
|
||||
protected $current = false;
|
||||
|
||||
/**
|
||||
* Whether the checkbox is the first in the group
|
||||
*
|
||||
* @var bool $first
|
||||
*/
|
||||
protected $first = false;
|
||||
|
||||
/**
|
||||
* Whether the checkbox is the last in the group
|
||||
*
|
||||
* @var bool $last
|
||||
*/
|
||||
protected $last = false;
|
||||
|
||||
public function __construct($label, $value)
|
||||
{
|
||||
$this->value = (!is_int($value)) ? $value : $label;
|
||||
$this->label = $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isCurrent()
|
||||
{
|
||||
return $this->current;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isChecked()
|
||||
{
|
||||
return $this->checked;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isFirst()
|
||||
{
|
||||
return $this->first;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isLast()
|
||||
{
|
||||
return $this->last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the pointer is currently pointing at this checkbox
|
||||
*
|
||||
* @param bool $current
|
||||
*
|
||||
* @return Checkbox
|
||||
*/
|
||||
public function setCurrent($current = true)
|
||||
{
|
||||
$this->current = $current;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether the checkbox is currently checked
|
||||
*
|
||||
* @param bool $checked
|
||||
*
|
||||
* @return Checkbox
|
||||
*/
|
||||
public function setChecked($checked = true)
|
||||
{
|
||||
$this->checked = $checked;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Checkbox
|
||||
*/
|
||||
public function setFirst()
|
||||
{
|
||||
$this->first = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Checkbox
|
||||
*/
|
||||
public function setLast()
|
||||
{
|
||||
$this->last = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|int|bool
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build out basic checkbox string based on current options
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function buildCheckboxString()
|
||||
{
|
||||
$parts = [
|
||||
($this->isCurrent()) ? $this->pointer() : ' ',
|
||||
$this->checkbox($this->isChecked()),
|
||||
$this->label,
|
||||
];
|
||||
|
||||
$line = implode(' ', $parts);
|
||||
|
||||
return $line . $this->getPaddingString($line);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the padding string based on the length of the terminal/line
|
||||
*
|
||||
* @param string $line
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getPaddingString($line)
|
||||
{
|
||||
$length = $this->util->system->width() - $this->lengthWithoutTags($line);
|
||||
|
||||
return str_repeat(' ', $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the checkbox symbol
|
||||
*
|
||||
* @param bool $checked
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function checkbox($checked)
|
||||
{
|
||||
if ($checked) {
|
||||
return html_entity_decode("●");
|
||||
}
|
||||
|
||||
return html_entity_decode("○");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pointer symbol
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function pointer()
|
||||
{
|
||||
return html_entity_decode("❯");
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
if ($this->isFirst()) {
|
||||
return $this->buildCheckboxString();
|
||||
}
|
||||
|
||||
if ($this->isLast()) {
|
||||
return $this->buildCheckboxString() . $this->util->cursor->left(10) . '<hidden>';
|
||||
}
|
||||
|
||||
return $this->buildCheckboxString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Dynamic\Checkbox;
|
||||
|
||||
use League\CLImate\Decorator\Parser\ParserImporter;
|
||||
use League\CLImate\Util\OutputImporter;
|
||||
use League\CLImate\Util\UtilImporter;
|
||||
|
||||
class CheckboxGroup
|
||||
{
|
||||
use OutputImporter, ParserImporter, UtilImporter;
|
||||
|
||||
protected $checkboxes = [];
|
||||
|
||||
protected $count;
|
||||
|
||||
public function __construct(array $options)
|
||||
{
|
||||
foreach ($options as $key => $option) {
|
||||
$this->checkboxes[] = new Checkbox($option, $key);
|
||||
}
|
||||
|
||||
$this->count = count($this->checkboxes);
|
||||
|
||||
$this->checkboxes[0]->setFirst()->setCurrent();
|
||||
$this->checkboxes[$this->count - 1]->setLast();
|
||||
}
|
||||
|
||||
public function write()
|
||||
{
|
||||
array_map([$this, 'writeCheckbox'], $this->checkboxes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the checked option values
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getCheckedValues()
|
||||
{
|
||||
return array_values(array_map([$this, 'getValue'], $this->getChecked()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the newly selected option based on the direction
|
||||
*
|
||||
* @param string $direction 'previous' or 'next'
|
||||
*/
|
||||
public function setCurrent($direction)
|
||||
{
|
||||
list($option, $key) = $this->getCurrent();
|
||||
|
||||
$option->setCurrent(false);
|
||||
|
||||
$new_key = $this->getCurrentKey($direction, $option, $key);
|
||||
|
||||
$this->checkboxes[$new_key]->setCurrent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the current option's checked status
|
||||
*/
|
||||
public function toggleCurrent()
|
||||
{
|
||||
list($option, $key) = $this->getCurrent();
|
||||
|
||||
$option->setChecked(!$option->isChecked());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of checkboxes
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return $this->count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the checked options
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getChecked()
|
||||
{
|
||||
return array_filter($this->checkboxes, [$this, 'isChecked']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the option is checked
|
||||
*
|
||||
* @param Checkbox $option
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function isChecked($option)
|
||||
{
|
||||
return $option->isChecked();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the option's value
|
||||
*
|
||||
* @param Checkbox $option
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getValue($option)
|
||||
{
|
||||
return $option->getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently selected option
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getCurrent()
|
||||
{
|
||||
foreach ($this->checkboxes as $key => $option) {
|
||||
if ($option->isCurrent()) {
|
||||
return [$option, $key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the correct current key
|
||||
*
|
||||
* @param string $direction 'previous' or 'next'
|
||||
* @param Checkbox $option
|
||||
* @param int $key
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getCurrentKey($direction, $option, $key)
|
||||
{
|
||||
$method = 'get' . ucwords($direction). 'Key';
|
||||
|
||||
return $this->{$method}($option, $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Checkbox $option
|
||||
* @param int $key
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getPreviousKey($option, $key)
|
||||
{
|
||||
if ($option->isFirst()) {
|
||||
return count($this->checkboxes) - 1;
|
||||
}
|
||||
|
||||
return --$key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Checkbox $option
|
||||
* @param int $key
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function getNextKey($option, $key)
|
||||
{
|
||||
if ($option->isLast()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return ++$key;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Checkbox $checkbox
|
||||
*/
|
||||
protected function writeCheckbox($checkbox)
|
||||
{
|
||||
$checkbox->util($this->util);
|
||||
$checkbox->parser($this->parser);
|
||||
|
||||
$parsed = $this->parser->apply((string) $checkbox);
|
||||
|
||||
if ($checkbox->isLast()) {
|
||||
$this->output->sameLine()->write($parsed);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->output->write($parsed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Dynamic\Checkbox;
|
||||
|
||||
class RadioGroup extends CheckboxGroup
|
||||
{
|
||||
/**
|
||||
* Toggle the currently selected option, uncheck all of the others
|
||||
*/
|
||||
public function toggleCurrent()
|
||||
{
|
||||
list($checkbox, $checkbox_key) = $this->getCurrent();
|
||||
|
||||
$checkbox->setChecked(!$checkbox->isChecked());
|
||||
|
||||
foreach ($this->checkboxes as $key => $checkbox) {
|
||||
if ($key == $checkbox_key) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$checkbox->setChecked(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the checked option
|
||||
*
|
||||
* @return string|bool|int
|
||||
*/
|
||||
public function getCheckedValues()
|
||||
{
|
||||
if ($checked = $this->getChecked()) {
|
||||
return reset($checked)->getValue();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Dynamic;
|
||||
|
||||
use League\CLImate\Util\Reader\ReaderInterface;
|
||||
use League\CLImate\Util\Reader\Stdin;
|
||||
|
||||
class Checkboxes extends InputAbstract
|
||||
{
|
||||
/**
|
||||
* The options to choose from
|
||||
*
|
||||
* @var Checkbox\CheckboxGroup $checkboxes
|
||||
*/
|
||||
protected $checkboxes;
|
||||
|
||||
public function __construct($prompt, array $options, ReaderInterface $reader = null)
|
||||
{
|
||||
$this->prompt = $prompt;
|
||||
$this->reader = $reader ?: new Stdin();
|
||||
|
||||
$this->checkboxes = $this->buildCheckboxes($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do it! Prompt the user for information!
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function prompt()
|
||||
{
|
||||
$this->output->write($this->parser->apply($this->promptFormatted()));
|
||||
|
||||
$this->writeCheckboxes();
|
||||
|
||||
$this->util->system->exec('stty sane');
|
||||
|
||||
return $this->checkboxes->getCheckedValues();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build out the checkboxes
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return Checkbox\CheckboxGroup
|
||||
*/
|
||||
protected function buildCheckboxes(array $options)
|
||||
{
|
||||
return new Checkbox\CheckboxGroup($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the prompt string
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function promptFormatted()
|
||||
{
|
||||
return $this->prompt . ' (use <space> to select)';
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the checkboxes and listen for any keystrokes
|
||||
*/
|
||||
protected function writeCheckboxes()
|
||||
{
|
||||
$this->updateCheckboxView();
|
||||
|
||||
$this->util->system->exec('stty -icanon');
|
||||
$this->output->sameLine()->write($this->util->cursor->hide());
|
||||
|
||||
$this->listenForInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for input and act on it
|
||||
*/
|
||||
protected function listenForInput()
|
||||
{
|
||||
while ($char = $this->reader->char(1)) {
|
||||
if ($this->handleCharacter($char)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$this->moveCursorToTop();
|
||||
$this->updateCheckboxView();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the appropriate action based on the input character,
|
||||
* returns whether to stop listening or not
|
||||
*
|
||||
* @param string $char
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function handleCharacter($char)
|
||||
{
|
||||
switch ($char) {
|
||||
case "\n":
|
||||
$this->output->sameLine()->write($this->util->cursor->defaultStyle());
|
||||
$this->output->sameLine()->write("\e[0m");
|
||||
return true; // Break the while loop as well
|
||||
|
||||
case "\e":
|
||||
$this->handleAnsi();
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
$this->checkboxes->toggleCurrent();
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the cursor to the top of the option list
|
||||
*/
|
||||
protected function moveCursorToTop()
|
||||
{
|
||||
$output = $this->util->cursor->up($this->checkboxes->count() - 1);
|
||||
$output .= $this->util->cursor->startOfCurrentLine();
|
||||
|
||||
$this->output->sameLine()->write($output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle any ANSI characters
|
||||
*/
|
||||
protected function handleAnsi()
|
||||
{
|
||||
switch ($this->reader->char(2)) {
|
||||
// Up arrow
|
||||
case '[A':
|
||||
$this->checkboxes->setCurrent('previous');
|
||||
break;
|
||||
|
||||
// Down arrow
|
||||
case '[B':
|
||||
$this->checkboxes->setCurrent('next');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-write the checkboxes based on the current objects
|
||||
*/
|
||||
protected function updateCheckboxView()
|
||||
{
|
||||
$this->checkboxes->util($this->util);
|
||||
$this->checkboxes->output($this->output);
|
||||
$this->checkboxes->parser($this->parser);
|
||||
|
||||
$this->checkboxes->write();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Dynamic;
|
||||
|
||||
class Confirm extends Input
|
||||
{
|
||||
/**
|
||||
* Let us know if the user confirmed
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function confirmed()
|
||||
{
|
||||
$this->accept(['y', 'n'], true);
|
||||
$this->strict();
|
||||
|
||||
return ($this->prompt() == 'y');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Dynamic;
|
||||
|
||||
use League\CLImate\Decorator\Parser\ParserImporter;
|
||||
use League\CLImate\Settings\SettingsImporter;
|
||||
use League\CLImate\Util\OutputImporter;
|
||||
use League\CLImate\Util\UtilImporter;
|
||||
|
||||
/**
|
||||
* The dynamic terminal object doesn't adhere to the basic terminal object
|
||||
* contract, which is why it gets its own base class
|
||||
*/
|
||||
|
||||
abstract class DynamicTerminalObject implements DynamicTerminalObjectInterface
|
||||
{
|
||||
use SettingsImporter, ParserImporter, OutputImporter, UtilImporter;
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Dynamic;
|
||||
|
||||
use League\CLImate\Decorator\Parser\Parser;
|
||||
use League\CLImate\Util\UtilFactory;
|
||||
|
||||
interface DynamicTerminalObjectInterface
|
||||
{
|
||||
public function settings();
|
||||
|
||||
/**
|
||||
* @param $setting
|
||||
* @return void
|
||||
*/
|
||||
public function importSetting($setting);
|
||||
|
||||
/**
|
||||
* @param \League\CLImate\Decorator\Parser\Parser $parser
|
||||
*/
|
||||
public function parser(Parser $parser);
|
||||
|
||||
/**
|
||||
* @param UtilFactory $util
|
||||
*/
|
||||
public function util(UtilFactory $util);
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Dynamic;
|
||||
|
||||
use League\CLImate\Util\Reader\ReaderInterface;
|
||||
use League\CLImate\Util\Reader\Stdin;
|
||||
|
||||
class Input extends InputAbstract
|
||||
{
|
||||
/**
|
||||
* An array of acceptable responses
|
||||
*
|
||||
* @var array|object $acceptable
|
||||
*/
|
||||
protected $acceptable;
|
||||
|
||||
/**
|
||||
* Whether we should be strict about the response given
|
||||
*
|
||||
* @var boolean $strict
|
||||
*/
|
||||
protected $strict = false;
|
||||
|
||||
/**
|
||||
* Whether to accept multiple lines of input
|
||||
*
|
||||
* Terminated by ^D
|
||||
*
|
||||
* @var boolean $multiLine
|
||||
*/
|
||||
protected $multiLine = false;
|
||||
|
||||
/**
|
||||
* Whether we should display the acceptable responses to the user
|
||||
*
|
||||
* @var boolean $show_acceptable
|
||||
*/
|
||||
protected $show_acceptable = false;
|
||||
|
||||
/**
|
||||
* A default answer in the case of no user response,
|
||||
* prevents re-prompting
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $default = '';
|
||||
|
||||
public function __construct($prompt, ReaderInterface $reader = null)
|
||||
{
|
||||
$this->prompt = $prompt;
|
||||
$this->reader = $reader ?: new Stdin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do it! Prompt the user for information!
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function prompt()
|
||||
{
|
||||
$this->writePrompt();
|
||||
|
||||
$response = $this->valueOrDefault($this->getUserInput());
|
||||
|
||||
if ($this->isValidResponse($response)) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return $this->prompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Define the acceptable responses and whether or not to
|
||||
* display them to the user
|
||||
*
|
||||
* @param array|object $acceptable
|
||||
* @param boolean $show
|
||||
*
|
||||
* @return \League\CLImate\TerminalObject\Dynamic\Input
|
||||
*/
|
||||
public function accept($acceptable, $show = false)
|
||||
{
|
||||
$this->acceptable = $acceptable;
|
||||
$this->show_acceptable = $show;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define whether we should be strict about exact responses
|
||||
*
|
||||
* @return \League\CLImate\TerminalObject\Dynamic\Input
|
||||
*/
|
||||
public function strict()
|
||||
{
|
||||
$this->strict = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a default response
|
||||
*
|
||||
* @param string $default
|
||||
*
|
||||
* @return \League\CLImate\TerminalObject\Dynamic\Input
|
||||
*/
|
||||
public function defaultTo($default)
|
||||
{
|
||||
$this->default = $default;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set multiline input to true
|
||||
*
|
||||
* @return \League\CLImate\TerminalObject\Dynamic\Input
|
||||
*/
|
||||
public function multiLine()
|
||||
{
|
||||
$this->multiLine = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getUserInput()
|
||||
{
|
||||
if ($this->multiLine) {
|
||||
return $this->reader->multiLine();
|
||||
}
|
||||
|
||||
return $this->reader->line();
|
||||
}
|
||||
|
||||
/**
|
||||
* Write out the formatted prompt
|
||||
*/
|
||||
protected function writePrompt()
|
||||
{
|
||||
$prompt = $this->parser->apply($this->promptFormatted());
|
||||
|
||||
$this->output->sameLine()->write($prompt);
|
||||
}
|
||||
|
||||
/**
|
||||
* If no response was given and there is a default, return it,
|
||||
* otherwise return response
|
||||
*
|
||||
* @param string $response
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function valueOrDefault($response)
|
||||
{
|
||||
if (strlen($response) == 0 && strlen($this->default)) {
|
||||
return $this->default;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the acceptable responses as options
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function acceptableFormatted()
|
||||
{
|
||||
if (!is_array($this->acceptable)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$this->acceptable = array_map([$this, 'acceptableItemFormatted'], $this->acceptable);
|
||||
|
||||
return '[' . implode('/', $this->acceptable) . ']';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the acceptable item depending on whether it is the default or not
|
||||
*
|
||||
* @param string $item
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function acceptableItemFormatted($item)
|
||||
{
|
||||
if ($item == $this->default) {
|
||||
return '<bold>' . $item . '</bold>';
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the prompt incorporating spacing and any acceptable options
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function promptFormatted()
|
||||
{
|
||||
$prompt = $this->prompt . ' ';
|
||||
|
||||
if ($this->show_acceptable) {
|
||||
$prompt .= $this->acceptableFormatted() . ' ';
|
||||
}
|
||||
|
||||
return $prompt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply some string manipulation functions for normalization
|
||||
*
|
||||
* @param string|array $var
|
||||
* @return array
|
||||
*/
|
||||
protected function levelPlayingField($var)
|
||||
{
|
||||
$levelers = ['trim', 'strtolower'];
|
||||
|
||||
foreach ($levelers as $leveler) {
|
||||
if (is_array($var)) {
|
||||
$var = array_map($leveler, $var);
|
||||
} else {
|
||||
$var = $leveler($var);
|
||||
}
|
||||
}
|
||||
|
||||
return $var;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether or not the acceptable property is of type closure
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function acceptableIsClosure()
|
||||
{
|
||||
return (is_object($this->acceptable) && $this->acceptable instanceof \Closure);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user's response is in the acceptable responses array
|
||||
*
|
||||
* @param string $response
|
||||
*
|
||||
* @return boolean $response
|
||||
*/
|
||||
protected function isAcceptableResponse($response)
|
||||
{
|
||||
if ($this->strict) {
|
||||
return in_array($response, $this->acceptable);
|
||||
}
|
||||
|
||||
$acceptable = $this->levelPlayingField($this->acceptable);
|
||||
$response = $this->levelPlayingField($response);
|
||||
|
||||
return in_array($response, $acceptable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the user's response is valid based on the current settings
|
||||
*
|
||||
* @param string $response
|
||||
*
|
||||
* @return boolean $response
|
||||
*/
|
||||
protected function isValidResponse($response)
|
||||
{
|
||||
if (empty($this->acceptable)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->acceptableIsClosure()) {
|
||||
return call_user_func($this->acceptable, $response);
|
||||
}
|
||||
|
||||
return $this->isAcceptableResponse($response);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Dynamic;
|
||||
|
||||
use League\CLImate\Util\Reader\ReaderInterface;
|
||||
use League\CLImate\Util\Reader\Stdin;
|
||||
|
||||
abstract class InputAbstract extends DynamicTerminalObject
|
||||
{
|
||||
/**
|
||||
* The prompt text
|
||||
*
|
||||
* @var string $prompt
|
||||
*/
|
||||
protected $prompt;
|
||||
|
||||
/**
|
||||
* An instance of ReaderInterface
|
||||
*
|
||||
* @var \League\CLImate\Util\Reader\ReaderInterface $reader
|
||||
*/
|
||||
protected $reader;
|
||||
|
||||
/**
|
||||
* Do it! Prompt the user for information!
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function prompt();
|
||||
|
||||
/**
|
||||
* Format the prompt incorporating spacing and any acceptable options
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function promptFormatted();
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Dynamic;
|
||||
|
||||
class Padding extends DynamicTerminalObject
|
||||
{
|
||||
/**
|
||||
* The length that lines should be padded to
|
||||
*
|
||||
* @var integer $length
|
||||
*/
|
||||
protected $length = 0;
|
||||
|
||||
/**
|
||||
* The character(s) that should be used to pad
|
||||
*
|
||||
* @var string $char
|
||||
*/
|
||||
protected $char = '.';
|
||||
|
||||
|
||||
/**
|
||||
* If they pass in a padding character, set the char
|
||||
*
|
||||
* @param int $length
|
||||
* @param string $char
|
||||
*/
|
||||
public function __construct($length = null, $char = null)
|
||||
{
|
||||
if ($length !== null) {
|
||||
$this->length($length);
|
||||
}
|
||||
|
||||
if (is_string($char)) {
|
||||
$this->char($char);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the character(s) that should be used to pad
|
||||
*
|
||||
* @param string $char
|
||||
*
|
||||
* @return \League\CLImate\TerminalObject\Dynamic\Padding
|
||||
*/
|
||||
public function char($char)
|
||||
{
|
||||
$this->char = $char;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the length of the line that should be generated
|
||||
*
|
||||
* @param integer $length
|
||||
*
|
||||
* @return \League\CLImate\TerminalObject\Dynamic\Padding
|
||||
*/
|
||||
public function length($length)
|
||||
{
|
||||
$this->length = $length;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of the line based on the width of the terminal window
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
protected function getLength()
|
||||
{
|
||||
if (!$this->length) {
|
||||
$this->length = $this->util->width();
|
||||
}
|
||||
|
||||
return $this->length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pad the content with the characters
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function padContent($content)
|
||||
{
|
||||
if (strlen($this->char) > 0) {
|
||||
$length = $this->getLength();
|
||||
$padding_length = ceil($length / strlen($this->char));
|
||||
|
||||
$padding = str_repeat($this->char, $padding_length);
|
||||
$content .= substr($padding, 0, $length - strlen($content));
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the content and pad to the previously defined length
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return \League\CLImate\TerminalObject\Dynamic\Padding
|
||||
*/
|
||||
public function label($content)
|
||||
{
|
||||
// Handle long labels by splitting them across several lines
|
||||
$lines = str_split($content, $this->util->width());
|
||||
$content = array_pop($lines);
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$this->output->write($this->parser->apply($line));
|
||||
}
|
||||
|
||||
$content = $this->padContent($content);
|
||||
$content = $this->parser->apply($content);
|
||||
|
||||
$this->output->sameLine();
|
||||
$this->output->write($content);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Output result
|
||||
*
|
||||
* @param string $content
|
||||
*/
|
||||
public function result($content)
|
||||
{
|
||||
$this->output->write($this->parser->apply(' ' . $content));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Dynamic;
|
||||
|
||||
class Password extends Input
|
||||
{
|
||||
public function prompt()
|
||||
{
|
||||
$this->writePrompt();
|
||||
|
||||
return $this->reader->hidden();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Dynamic;
|
||||
|
||||
class Progress extends DynamicTerminalObject
|
||||
{
|
||||
/**
|
||||
* The total number of items involved
|
||||
*
|
||||
* @var integer $total
|
||||
*/
|
||||
protected $total = 0;
|
||||
|
||||
/**
|
||||
* The current item that the progress bar represents
|
||||
*
|
||||
* @var integer $current
|
||||
*/
|
||||
protected $current = 0;
|
||||
|
||||
/**
|
||||
* The current percentage displayed
|
||||
*
|
||||
* @var string $current_percentage
|
||||
*/
|
||||
protected $current_percentage = '';
|
||||
|
||||
/**
|
||||
* The string length of the bar when at 100%
|
||||
*
|
||||
* @var integer $bar_str_len
|
||||
*/
|
||||
protected $bar_str_len;
|
||||
|
||||
/**
|
||||
* Flag indicating whether we are writing the bar for the first time
|
||||
*
|
||||
* @var boolean $first_line
|
||||
*/
|
||||
protected $first_line = true;
|
||||
|
||||
/**
|
||||
* Current status bar label
|
||||
*
|
||||
* @var string $label
|
||||
*/
|
||||
protected $label;
|
||||
|
||||
/**
|
||||
* Force a redraw every time
|
||||
*
|
||||
* @var boolean $force_redraw
|
||||
*/
|
||||
protected $force_redraw = false;
|
||||
|
||||
/**
|
||||
* If they pass in a total, set the total
|
||||
*
|
||||
* @param integer $total
|
||||
*/
|
||||
public function __construct($total = null)
|
||||
{
|
||||
if ($total !== null) {
|
||||
$this->total($total);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the total property
|
||||
*
|
||||
* @param integer $total
|
||||
*
|
||||
* @return Progress
|
||||
*/
|
||||
public function total($total)
|
||||
{
|
||||
$this->total = $total;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the current percentage we are at and re-writes the progress bar
|
||||
*
|
||||
* @param integer $current
|
||||
* @param mixed $label
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function current($current, $label = null)
|
||||
{
|
||||
if ($this->total == 0) {
|
||||
// Avoid dividing by 0
|
||||
throw new \Exception('The progress total must be greater than zero.');
|
||||
}
|
||||
|
||||
if ($current > $this->total) {
|
||||
throw new \Exception('The current is greater than the total.');
|
||||
}
|
||||
|
||||
$this->drawProgressBar($current, $label);
|
||||
|
||||
$this->current = $current;
|
||||
$this->label = $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the current position we are at and re-writes the progress bar
|
||||
*
|
||||
* @param integer $increment The number of items to increment by
|
||||
* @param string $label
|
||||
*/
|
||||
public function advance($increment = 1, $label = null)
|
||||
{
|
||||
$this->current($this->current + $increment, $label);
|
||||
}
|
||||
|
||||
/**
|
||||
* Force the progress bar to redraw every time regardless of whether it has changed or not
|
||||
*
|
||||
* @param boolean $force
|
||||
* @return Progress
|
||||
*/
|
||||
public function forceRedraw($force = true)
|
||||
{
|
||||
$this->force_redraw = !!$force;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the progress bar, if necessary
|
||||
*
|
||||
* @param string $current
|
||||
* @param string $label
|
||||
*/
|
||||
protected function drawProgressBar($current, $label)
|
||||
{
|
||||
$percentage = $this->percentageFormatted($current / $this->total);
|
||||
|
||||
if ($this->shouldRedraw($percentage, $label)) {
|
||||
$progress_bar = $this->getProgressBar($current, $label);
|
||||
$this->output->write($this->parser->apply($progress_bar));
|
||||
}
|
||||
|
||||
$this->current_percentage = $percentage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the progress bar str and return it
|
||||
*
|
||||
* @param integer $current
|
||||
* @param string $label
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getProgressBar($current, $label)
|
||||
{
|
||||
if ($this->first_line) {
|
||||
// Drop down a line, we are about to
|
||||
// re-write this line for the progress bar
|
||||
$this->output->write('');
|
||||
$this->first_line = false;
|
||||
}
|
||||
|
||||
// Move the cursor up one line and clear it to the end
|
||||
$line_count = (strlen($label) > 0) ? 2 : 1;
|
||||
|
||||
$progress_bar = $this->util->cursor->up($line_count);
|
||||
$progress_bar .= $this->util->cursor->startOfCurrentLine();
|
||||
$progress_bar .= $this->util->cursor->deleteCurrentLine();
|
||||
$progress_bar .= $this->getProgressBarStr($current, $label);
|
||||
|
||||
return $progress_bar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the progress bar string, basically:
|
||||
* =============> 50% label
|
||||
*
|
||||
* @param integer $current
|
||||
* @param string $label
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getProgressBarStr($current, $label)
|
||||
{
|
||||
$percentage = $current / $this->total;
|
||||
$bar_length = round($this->getBarStrLen() * $percentage);
|
||||
|
||||
$bar = $this->getBar($bar_length);
|
||||
$number = $this->percentageFormatted($percentage);
|
||||
|
||||
if ($label) {
|
||||
$label = $this->labelFormatted($label);
|
||||
}
|
||||
|
||||
return trim("{$bar} {$number}{$label}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string for the actual bar based on the current length
|
||||
*
|
||||
* @param integer $length
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getBar($length)
|
||||
{
|
||||
$bar = str_repeat('=', $length);
|
||||
$padding = str_repeat(' ', $this->getBarStrLen() - $length);
|
||||
|
||||
return "{$bar}>{$padding}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length of the bar string based on the width of the terminal window
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
protected function getBarStrLen()
|
||||
{
|
||||
if (!$this->bar_str_len) {
|
||||
// Subtract 10 because of the '> 100%' plus some padding, max 100
|
||||
$this->bar_str_len = min($this->util->width() - 10, 100);
|
||||
}
|
||||
|
||||
return $this->bar_str_len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the percentage so it looks pretty
|
||||
*
|
||||
* @param integer $percentage
|
||||
* @return float
|
||||
*/
|
||||
protected function percentageFormatted($percentage)
|
||||
{
|
||||
return round($percentage * 100) . '%';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the label so it is positioned correctly
|
||||
*
|
||||
* @param string $label
|
||||
* @return string
|
||||
*/
|
||||
protected function labelFormatted($label)
|
||||
{
|
||||
return "\n" . $this->util->cursor->startOfCurrentLine() . $this->util->cursor->deleteCurrentLine() . $label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the progress bar has changed and we need to redrew
|
||||
*
|
||||
* @param string $percentage
|
||||
* @param string $label
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function shouldRedraw($percentage, $label)
|
||||
{
|
||||
return ($this->force_redraw || $percentage != $this->current_percentage || $label != $this->label);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Dynamic;
|
||||
|
||||
class Radio extends Checkboxes
|
||||
{
|
||||
/**
|
||||
* Build out the checkboxes
|
||||
*
|
||||
* @param array $options
|
||||
*
|
||||
* @return Checkbox\RadioGroup
|
||||
*/
|
||||
protected function buildCheckboxes(array $options)
|
||||
{
|
||||
return new Checkbox\RadioGroup($options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Helper;
|
||||
|
||||
trait Art
|
||||
{
|
||||
/**
|
||||
* The directories we should be looking for art in
|
||||
*
|
||||
* @var array $art_dirs
|
||||
*/
|
||||
protected $art_dirs = [];
|
||||
|
||||
/**
|
||||
* The default art if we can't find what the user requested
|
||||
*
|
||||
* @var string $default_art
|
||||
*/
|
||||
protected $default_art = '404';
|
||||
|
||||
/**
|
||||
* The art requested by the user
|
||||
*
|
||||
* @var string $art
|
||||
*/
|
||||
protected $art = '';
|
||||
|
||||
/**
|
||||
* Specify which settings Draw needs to import
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function settings()
|
||||
{
|
||||
return ['Art'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the Art setting (any directories the user added)
|
||||
*
|
||||
* @param \League\CLImate\Settings\Art $setting
|
||||
*/
|
||||
public function importSettingArt($setting)
|
||||
{
|
||||
foreach ($setting->dirs as $dir) {
|
||||
$this->addDir($dir);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a directory to search for art in
|
||||
*
|
||||
* @param string $dir
|
||||
*/
|
||||
protected function addDir($dir)
|
||||
{
|
||||
// Add any additional directories to the top of the array
|
||||
// so that the user can override art
|
||||
array_unshift($this->art_dirs, rtrim($dir, '/'));
|
||||
|
||||
// Keep the array clean
|
||||
$this->art_dirs = array_unique($this->art_dirs);
|
||||
$this->art_dirs = array_filter($this->art_dirs);
|
||||
$this->art_dirs = array_values($this->art_dirs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a valid art path
|
||||
*
|
||||
* @param string $art
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function artDir($art)
|
||||
{
|
||||
return $this->fileSearch($art, '/*.*');
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a valid art path
|
||||
*
|
||||
* @param string $art
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function artFile($art)
|
||||
{
|
||||
$files = $this->fileSearch($art, '.*');
|
||||
|
||||
return reset($files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a set of files in the current art directories
|
||||
* based on a pattern
|
||||
*
|
||||
* @param string $art
|
||||
* @param string $pattern
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function fileSearch($art, $pattern)
|
||||
{
|
||||
foreach ($this->art_dirs as $dir) {
|
||||
// Look for anything that has the $art filename
|
||||
$paths = glob($dir . '/' . $art . $pattern);
|
||||
|
||||
// If we've got one, no need to look any further
|
||||
if (!empty($paths)) {
|
||||
return $paths;
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the contents of the file and return each line
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function parse($path)
|
||||
{
|
||||
$output = file_get_contents($path);
|
||||
$output = explode("\n", $output);
|
||||
$output = array_map('rtrim', $output);
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Helper;
|
||||
|
||||
class Sleeper implements SleeperInterface
|
||||
{
|
||||
/**
|
||||
* The default length of the sleep
|
||||
*
|
||||
* @var int|float $speed
|
||||
*/
|
||||
protected $speed = 50000;
|
||||
|
||||
/**
|
||||
* Set the speed based on a percentage (50% slower, 200% faster, etc)
|
||||
*
|
||||
* @param int|float $percentage
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function speed($percentage)
|
||||
{
|
||||
if (is_numeric($percentage) && $percentage > 0) {
|
||||
$this->speed *= (100 / $percentage);
|
||||
}
|
||||
|
||||
return $this->speed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sleep for the specified amount of time
|
||||
*/
|
||||
public function sleep()
|
||||
{
|
||||
usleep($this->speed);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Helper;
|
||||
|
||||
interface SleeperInterface
|
||||
{
|
||||
/**
|
||||
* @param int|float $percentage
|
||||
*/
|
||||
public function speed($percentage);
|
||||
|
||||
public function sleep();
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Helper;
|
||||
|
||||
trait StringLength
|
||||
{
|
||||
/**
|
||||
* Tags the should not be ultimately considered
|
||||
* when calculating any string lengths
|
||||
*
|
||||
* @var array $ignore_tags
|
||||
*/
|
||||
protected $ignore_tags = [];
|
||||
|
||||
/**
|
||||
* Set the ignore tags property
|
||||
*/
|
||||
protected function setIgnoreTags()
|
||||
{
|
||||
if (!count($this->ignore_tags)) {
|
||||
$this->ignore_tags = array_keys($this->parser->tags->all());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the length of the string without any tags
|
||||
*
|
||||
* @param string $str
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
protected function lengthWithoutTags($str)
|
||||
{
|
||||
$this->setIgnoreTags();
|
||||
|
||||
return mb_strwidth($this->withoutTags($str), 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the string without the tags that are to be ignored
|
||||
*
|
||||
* @param string $str
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function withoutTags($str)
|
||||
{
|
||||
$this->setIgnoreTags();
|
||||
|
||||
return str_replace($this->ignore_tags, '', $str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply padding to a string
|
||||
*
|
||||
* @param string $str
|
||||
* @param string $final_length
|
||||
* @param string $padding_side
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function pad($str, $final_length, $padding_side = 'right')
|
||||
{
|
||||
$padding = $final_length - $this->lengthWithoutTags($str);
|
||||
|
||||
if ($padding_side == 'left') {
|
||||
return str_repeat(' ', $padding) . $str;
|
||||
}
|
||||
|
||||
return $str . str_repeat(' ', $padding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply padding to an array of strings
|
||||
*
|
||||
* @param array $arr
|
||||
* @param integer $final_length
|
||||
* @param string $padding_side
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function padArray($arr, $final_length, $padding_side = 'right')
|
||||
{
|
||||
foreach ($arr as $key => $value) {
|
||||
$arr[$key] = $this->pad($value, $final_length, $padding_side);
|
||||
}
|
||||
|
||||
return $arr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the max string length in an array
|
||||
*
|
||||
* @param array $arr
|
||||
* @return int
|
||||
*/
|
||||
protected function maxStrLen(array $arr)
|
||||
{
|
||||
return max($this->arrayOfStrLens($arr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of the string lengths from an array of strings
|
||||
*
|
||||
* @param array $arr
|
||||
* @return array
|
||||
*/
|
||||
protected function arrayOfStrLens(array $arr)
|
||||
{
|
||||
return array_map([$this, 'lengthWithoutTags'], $arr);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Router;
|
||||
|
||||
abstract class BaseRouter implements RouterInterface
|
||||
{
|
||||
protected $extensions = [];
|
||||
|
||||
/**
|
||||
* Add a custom extension to CLImate
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $class
|
||||
*/
|
||||
public function addExtension($key, $class)
|
||||
{
|
||||
$this->extensions[$key] = $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full path for the class based on the key
|
||||
*
|
||||
* @param string $class
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function path($class)
|
||||
{
|
||||
return $this->getExtension($class) ?: $this->getPath($this->shortName($class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the requested class is a
|
||||
* valid terminal object class
|
||||
*
|
||||
* @param string $class
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function exists($class)
|
||||
{
|
||||
$class = $this->path($class);
|
||||
|
||||
return (is_object($class) || class_exists($class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full path for the terminal object class
|
||||
*
|
||||
* @param string $class
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getPath($class)
|
||||
{
|
||||
return 'League\CLImate\TerminalObject\\' . $this->pathPrefix() . '\\' . $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an extension by its key
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return string|false Full class path to extension
|
||||
*/
|
||||
protected function getExtension($key)
|
||||
{
|
||||
if (array_key_exists($key, $this->extensions)) {
|
||||
return $this->extensions[$key];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class short name
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function shortName($name)
|
||||
{
|
||||
$name = str_replace('_', ' ', $name);
|
||||
$name = ucwords($name);
|
||||
|
||||
return str_replace(' ', '', $name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Router;
|
||||
|
||||
use League\CLImate\Util\Helper;
|
||||
use League\CLImate\Util\OutputImporter;
|
||||
|
||||
class BasicRouter extends BaseRouter
|
||||
{
|
||||
use OutputImporter;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function pathPrefix()
|
||||
{
|
||||
return 'Basic';
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a basic terminal object
|
||||
*
|
||||
* @param \League\CLImate\TerminalObject\Basic\BasicTerminalObject $obj
|
||||
* @return void
|
||||
*/
|
||||
public function execute($obj)
|
||||
{
|
||||
$results = Helper::toArray($obj->result());
|
||||
|
||||
$this->output->persist();
|
||||
|
||||
foreach ($results as $result) {
|
||||
if ($obj->sameLine()) {
|
||||
$this->output->sameLine();
|
||||
}
|
||||
|
||||
$this->output->write($obj->getParser()->apply($result));
|
||||
}
|
||||
|
||||
$this->output->persist(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Router;
|
||||
|
||||
use League\CLImate\Util\OutputImporter;
|
||||
|
||||
class DynamicRouter extends BaseRouter
|
||||
{
|
||||
use OutputImporter;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function pathPrefix()
|
||||
{
|
||||
return 'Dynamic';
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a dynamic terminal object using given arguments
|
||||
*
|
||||
* @param \League\CLImate\TerminalObject\Dynamic\DynamicTerminalObject $obj
|
||||
*
|
||||
* @return \League\CLImate\TerminalObject\Dynamic\DynamicTerminalObject
|
||||
*/
|
||||
public function execute($obj)
|
||||
{
|
||||
$obj->output($this->output);
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Router;
|
||||
|
||||
use League\CLImate\Util\Helper;
|
||||
|
||||
class ExtensionCollection
|
||||
{
|
||||
/**
|
||||
* @var array collection
|
||||
*/
|
||||
protected $collection = ['basic' => [], 'dynamic' => []];
|
||||
|
||||
/**
|
||||
* @var string $basic_interface
|
||||
*/
|
||||
protected $basic_interface = 'League\CLImate\TerminalObject\Basic\BasicTerminalObjectInterface';
|
||||
|
||||
/**
|
||||
* @var string $dynamic_interface
|
||||
*/
|
||||
protected $dynamic_interface = 'League\CLImate\TerminalObject\Dynamic\DynamicTerminalObjectInterface';
|
||||
|
||||
public function __construct($key, $class)
|
||||
{
|
||||
$this->createCollection($key, $class);
|
||||
}
|
||||
|
||||
public function collection()
|
||||
{
|
||||
return $this->collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the collection from the key/class
|
||||
*
|
||||
* @param string $original_key
|
||||
* @param string|object|array $original_class
|
||||
*
|
||||
* @return type
|
||||
*/
|
||||
protected function createCollection($original_key, $original_class)
|
||||
{
|
||||
$collection = $this->convertToArray($original_key, $original_class);
|
||||
|
||||
foreach ($collection as $key => $class) {
|
||||
$this->validateExtension($class);
|
||||
$this->collection[$this->getType($class)][$this->getKey($key, $class)] = $class;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given class and key to an array of classes
|
||||
*
|
||||
* @param string|object|array $class
|
||||
* @param string $key Optional custom key instead of class name
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function convertToArray($key, $class)
|
||||
{
|
||||
if (is_array($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return [$this->getKey($key, $class) => $class];
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the extension is valid
|
||||
*
|
||||
* @param string|object|array $class
|
||||
*/
|
||||
protected function validateExtension($class)
|
||||
{
|
||||
$this->validateClassExists($class);
|
||||
$this->validateClassImplementation($class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|object $class
|
||||
*
|
||||
* @throws \Exception if extension class does not exist
|
||||
*/
|
||||
protected function validateClassExists($class)
|
||||
{
|
||||
if (is_string($class) && !class_exists($class)) {
|
||||
throw new \Exception('Class does not exist: ' . $class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|object $class
|
||||
*
|
||||
* @throws \Exception if extension class does not implement either Dynamic or Basic interface
|
||||
*/
|
||||
protected function validateClassImplementation($class)
|
||||
{
|
||||
$str_class = is_string($class);
|
||||
|
||||
$valid_implementation = (is_a($class, $this->basic_interface, $str_class)
|
||||
|| is_a($class, $this->dynamic_interface, $str_class));
|
||||
|
||||
if (!$valid_implementation) {
|
||||
throw new \Exception('Class must implement either '
|
||||
. $this->basic_interface . ' or ' . $this->dynamic_interface);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the extension key based on the class
|
||||
*
|
||||
* @param string|null $key
|
||||
* @param string|object $class
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getKey($key, $class)
|
||||
{
|
||||
if ($key === null || !is_string($key)) {
|
||||
$class_path = (is_string($class)) ? $class : get_class($class);
|
||||
|
||||
$key = explode('\\', $class_path);
|
||||
$key = end($key);
|
||||
}
|
||||
|
||||
return Helper::snakeCase($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type of class the extension implements
|
||||
*
|
||||
* @param string|object $class
|
||||
*
|
||||
* @return string 'basic' or 'dynamic'
|
||||
*/
|
||||
protected function getType($class)
|
||||
{
|
||||
if (is_a($class, $this->basic_interface, is_string($class))) {
|
||||
return 'basic';
|
||||
}
|
||||
|
||||
return 'dynamic';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Router;
|
||||
|
||||
use League\CLImate\Decorator\Parser\ParserImporter;
|
||||
use League\CLImate\Settings\Manager;
|
||||
use League\CLImate\Settings\SettingsImporter;
|
||||
use League\CLImate\Util\OutputImporter;
|
||||
use League\CLImate\Util\UtilImporter;
|
||||
|
||||
class Router
|
||||
{
|
||||
use ParserImporter, SettingsImporter, OutputImporter, UtilImporter;
|
||||
|
||||
/**
|
||||
* An instance of the Settings Manager class
|
||||
*
|
||||
* @var \League\CLImate\Settings\Manager $settings;
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* An instance of the Dynamic Router class
|
||||
*
|
||||
* @var \League\CLImate\TerminalObject\Router\DynamicRouter $dynamic;
|
||||
*/
|
||||
protected $dynamic;
|
||||
|
||||
/**
|
||||
* An instance of the Basic Router class
|
||||
*
|
||||
* @var \League\CLImate\TerminalObject\Router\BasicRouter $basic;
|
||||
*/
|
||||
protected $basic;
|
||||
|
||||
public function __construct(DynamicRouter $dynamic = null, BasicRouter $basic = null)
|
||||
{
|
||||
$this->dynamic = $dynamic ?: new DynamicRouter();
|
||||
$this->basic = $basic ?: new BasicRouter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a custom class with the router
|
||||
*
|
||||
* @param string $key
|
||||
* @param string $class
|
||||
*/
|
||||
public function addExtension($key, $class)
|
||||
{
|
||||
$extension = new ExtensionCollection($key, $class);
|
||||
|
||||
foreach ($extension->collection() as $obj_type => $collection) {
|
||||
foreach ($collection as $obj_key => $obj_class) {
|
||||
$this->{$obj_type}->addExtension($obj_key, $obj_class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the name matches an existing terminal object
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function exists($name)
|
||||
{
|
||||
return ($this->basic->exists($name) || $this->dynamic->exists($name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a terminal object using given arguments
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $arguments
|
||||
*
|
||||
* @return null|\League\CLImate\TerminalObject\Basic\BasicTerminalObjectInterface
|
||||
*/
|
||||
public function execute($name, $arguments)
|
||||
{
|
||||
$router = $this->getRouter($name);
|
||||
|
||||
$router->output($this->output);
|
||||
|
||||
$obj = $this->getObject($router, $name, $arguments);
|
||||
|
||||
$obj->parser($this->parser);
|
||||
$obj->util($this->util);
|
||||
|
||||
// If the object needs any settings, import them
|
||||
foreach ($obj->settings() as $obj_setting) {
|
||||
$setting = $this->settings->get($obj_setting);
|
||||
|
||||
if ($setting) {
|
||||
$obj->importSetting($setting);
|
||||
}
|
||||
}
|
||||
|
||||
return $router->execute($obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the object whether it's a string or already instantiated
|
||||
*
|
||||
* @param \League\CLImate\TerminalObject\Router\RouterInterface $router
|
||||
* @param string $name
|
||||
* @param array $arguments
|
||||
*
|
||||
* @return \League\CLImate\TerminalObject\Dynamic\DynamicTerminalObjectInterface|\League\CLImate\TerminalObject\Basic\BasicTerminalObjectInterface
|
||||
*/
|
||||
protected function getObject($router, $name, $arguments)
|
||||
{
|
||||
$obj = $router->path($name);
|
||||
|
||||
if (is_string($obj)) {
|
||||
$obj = (new \ReflectionClass($obj))->newInstanceArgs($arguments);
|
||||
}
|
||||
|
||||
if (method_exists($obj, 'arguments')) {
|
||||
call_user_func_array([$obj, 'arguments'], $arguments);
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine which type of router we are using and return it
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return \League\CLImate\TerminalObject\Router\RouterInterface|null
|
||||
*/
|
||||
protected function getRouter($name)
|
||||
{
|
||||
if ($this->basic->exists($name)) {
|
||||
return $this->basic;
|
||||
}
|
||||
|
||||
if ($this->dynamic->exists($name)) {
|
||||
return $this->dynamic;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the settings property
|
||||
*
|
||||
* @param \League\CLImate\Settings\Manager $settings
|
||||
*
|
||||
* @return Router
|
||||
*/
|
||||
public function settings(Manager $settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace League\CLImate\TerminalObject\Router;
|
||||
|
||||
interface RouterInterface
|
||||
{
|
||||
/**
|
||||
* @param $class
|
||||
* @return string
|
||||
*/
|
||||
public function path($class);
|
||||
|
||||
/**
|
||||
* @param $class
|
||||
* @return boolean
|
||||
*/
|
||||
public function exists($class);
|
||||
|
||||
/**
|
||||
* @param $obj
|
||||
* @return null|\League\CLImate\TerminalObject\Dynamic\DynamicTerminalObject
|
||||
*/
|
||||
public function execute($obj);
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function pathPrefix();
|
||||
}
|
||||
Reference in New Issue
Block a user