src/flow/console/ICConsoleTree.php (112 lines of code) (raw):
<?php
final class ICConsoleTree extends PhutilConsoleView {
private $table;
private $treeColumn = null;
private $children = array();
private $parents = array();
private $rows = array();
private $hasAddedRows = false;
private $branchLength = 3;
public function setBranchLength($length) {
$this->branchLength = $length;
return $this;
}
public function setTable(PhutilConsoleTable $table) {
if ($this->table) {
throw new LogicException(
'You cannot set the table for a console tree more than once, or '.
'after adding columns.');
}
$this->table = $table;
return $this;
}
private function getTable() {
if (!$this->table) {
$this->table = (new PhutilConsoleTable())
->setShowHeader(false);
}
return $this->table;
}
protected function drawView() {
if (!$this->hasAddedRows) {
foreach ($this->parents as $node => $parents) {
if (!$parents) {
$this->descend($node);
}
}
$this->hasAddedRows = true;
}
return $this->table->drawView();
}
protected function descend($node, $depth = 0, array $open_depths = array(),
$leaf = false) {
$children = $this->children[$node];
list($row, $meta) = $this->rows[$node];
$meta_tree = idx($meta, $this->treeColumn);
$suffix = idx($meta_tree, 'suffix', '');
$tree = $this->formatTreeColumn($node, $depth, $open_depths, $leaf);
$row[$this->treeColumn] = tsprintf('%s**%s**%s', $tree, $node, $suffix);
$this->table->addRow($row);
array_push($open_depths, $depth);
while ($children) {
$child = array_pop($children);
if (!$children) {
array_pop($open_depths);
}
$this->descend($child, $depth + 1, $open_depths, !$children);
}
}
public static function drawTreeColumn($name, $depth, $leaf, $suffix) {
$console = new self();
$tree = $console->formatTreeColumn($name, $depth, array(), $leaf);
return tsprintf('%s**%s**%s', $tree, $name, $suffix);
}
private function drawGraphColumn($start = ' ', $pad = ' ') {
return ICBoxDrawing::draw($start.str_repeat($pad, $this->branchLength));
}
private function formatTreeColumn($node, $depth, array $open_depths, $leaf) {
$tree = '';
for ($i = 0; $i < $depth; ++$i) {
if ($i === $depth - 1) {
$tree .= $leaf ?
$this->drawGraphColumn('|_', '-') :
$this->drawGraphColumn('|-', '-');
} else if (array_search($i, $open_depths) !== false) {
$tree .= $this->drawGraphColumn('|');
} else {
$tree .= $this->drawGraphColumn();
}
}
return $tree;
}
public function addColumn($key, array $column, $is_tree = false) {
if ($is_tree && $this->treeColumn) {
throw new LogicException('Only one column may be used to display the '.
'tree.');
} else if ($is_tree) {
$this->treeColumn = $key;
}
$this->getTable()->addColumn($key, $column);
return $this;
}
public function addRow(array $data, $parent = null, array $meta = array()) {
if (!$this->treeColumn) {
throw new LogicException('You must add one column specified as the tree '.
'column.');
}
$node = idx($data, $this->treeColumn);
if (!$node || idx($this->rows, $node)) {
throw new LogicException('All rows must have a unique value for their '.
'tree column.');
}
$this->rows[$node] = array($data, $meta);
if (!idx($this->children, $node)) {
$this->children[$node] = array();
}
if ($parent) {
$this->children[$parent][] = $node;
}
$this->parents[$node] = $parent;
return $this;
}
}