) will be sorted instead
+ * of the itself.
+ */
+jQuery.fn.sortElements = (function(){
+
+ var sort = [].sort;
+
+ return function(comparator, getSortable) {
+
+ getSortable = getSortable || function(){return this;};
+
+ var placements = this.map(function(){
+
+ var sortElement = getSortable.call(this),
+ parentNode = sortElement.parentNode,
+
+ // Since the element itself will change position, we have
+ // to have some way of storing it's original position in
+ // the DOM. The easiest way is to have a 'flag' node:
+ nextSibling = parentNode.insertBefore(
+ document.createTextNode(''),
+ sortElement.nextSibling
+ );
+
+ return function() {
+
+ if (parentNode === this) {
+ throw new Error(
+ "You can't sort elements if any one is a descendant of another."
+ );
+ }
+
+ // Insert before flag:
+ parentNode.insertBefore(this, nextSibling);
+ // Remove flag:
+ parentNode.removeChild(nextSibling);
+
+ };
+
+ });
+
+ return sort.call(this, comparator).each(function(i){
+ placements[i].call(getSortable.call(this));
+ });
+
+ };
+
+})();
+ $(window).load(function() {
+ var $document = $(document);
+ var $left = $('#left');
+ var $right = $('#right');
+ var $rightInner = $('#rightInner');
+ var $splitter = $('#splitter');
+ var $groups = $('#groups');
+ var $content = $('#content');
+
+ // Menu
+
+ // Hide deep packages and namespaces
+ $('ul span', $groups).click(function(event) {
+ event.preventDefault();
+ event.stopPropagation();
+ $(this)
+ .toggleClass('collapsed')
+ .parent()
+ .next('ul')
+ .toggleClass('collapsed');
+ }).click();
+
+ $active = $('ul li.active', $groups);
+ if ($active.length > 0) {
+ // Open active
+ $('> a > span', $active).click();
+ } else {
+ $main = $('> ul > li.main', $groups);
+ if ($main.length > 0) {
+ // Open first level of the main project
+ $('> a > span', $main).click();
+ } else {
+ // Open first level of all
+ $('> ul > li > a > span', $groups).click();
+ }
+ }
+
+ // Content
+
+ // Search autocompletion
+ var autocompleteFound = false;
+ var autocompleteFiles = {'c': 'class', 'co': 'constant', 'f': 'function', 'm': 'class', 'mm': 'class', 'p': 'class', 'mp': 'class', 'cc': 'class'};
+ var $search = $('#search input[name=q]');
+ $search
+ .autocomplete(ApiGen.elements, {
+ matchContains: true,
+ scrollHeight: 200,
+ max: 20,
+ noRecord: '',
+ highlight: function(value, term) {
+ var term = term.toUpperCase().replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1").replace(/[A-Z0-9]/g, function(m, offset) {
+ return offset === 0 ? '(?:' + m + '|^' + m.toLowerCase() + ')' : '(?:(?:[^<>]|<[^<>]*>)*' + m + '|' + m.toLowerCase() + ')';
+ });
+ return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term + ")(?![^<>]*>)(?![^&;]+;)"), "$1 ");
+ },
+ formatItem: function(data) {
+ return data.length > 1 ? data[1].replace(/^(.+\\)(.+)$/, '$1 $2 ') : data[0];
+ },
+ formatMatch: function(data) {
+ return data[1];
+ },
+ formatResult: function(data) {
+ return data[1];
+ },
+ show: function($list) {
+ var $items = $('li span', $list);
+ var maxWidth = Math.max.apply(null, $items.map(function() {
+ return $(this).width();
+ }));
+ // 10px padding
+ $list
+ .width(Math.max(maxWidth + 10, $search.innerWidth()))
+ .css('left', $search.offset().left + $search.outerWidth() - $list.outerWidth());
+ }
+ }).result(function(event, data) {
+ autocompleteFound = true;
+ var location = window.location.href.split('/');
+ location.pop();
+ var parts = data[1].split(/::|$/);
+ var file = $.sprintf(ApiGen.config.templates[autocompleteFiles[data[0]]].filename, parts[0].replace(/\(\)/, '').replace(/[^\w]/g, '.'));
+ if (parts[1]) {
+ file += '#' + ('mm' === data[0] || 'mp' === data[0] ? 'm' : '') + parts[1].replace(/([\w]+)\(\)/, '_$1');
+ }
+ location.push(file);
+ window.location = location.join('/');
+
+ // Workaround for Opera bug
+ $(this).closest('form').attr('action', location.join('/'));
+ }).closest('form')
+ .submit(function() {
+ var query = $search.val();
+ if ('' === query) {
+ return false;
+ }
+ return !autocompleteFound && '' !== $('#search input[name=cx]').val();
+ });
+
+ // Save natural order
+ $('table.summary tr[data-order]', $content).each(function(index) {
+ do {
+ index = '0' + index;
+ } while (index.length < 3);
+ $(this).attr('data-order-natural', index);
+ });
+
+ // Switch between natural and alphabetical order
+ var $caption = $('table.summary', $content)
+ .filter(':has(tr[data-order])')
+ .find('caption');
+ $caption
+ .click(function() {
+ var $this = $(this);
+ var order = $this.data('order') || 'natural';
+ order = 'natural' === order ? 'alphabetical' : 'natural';
+ $this.data('order', order);
+ $.cookie('order', order, {expires: 365});
+ var attr = 'alphabetical' === order ? 'data-order' : 'data-order-natural';
+ $this
+ .closest('table')
+ .find('tr').sortElements(function(a, b) {
+ return $(a).attr(attr) > $(b).attr(attr) ? 1 : -1;
+ });
+ return false;
+ })
+ .addClass('switchable')
+ .attr('title', 'Switch between natural and alphabetical order');
+ if ((null === $.cookie('order') && 'alphabetical' === ApiGen.config.options.elementsOrder) || 'alphabetical' === $.cookie('order')) {
+ $caption.click();
+ }
+
+ // Open details
+ if (ApiGen.config.options.elementDetailsCollapsed) {
+ var trCollapsed = true;
+ $('tr', $content).filter(':has(.detailed)')
+ .click(function() {
+ var $this = $(this);
+ if (trCollapsed) {
+ $('.short', $this).hide();
+ $('.detailed', $this).show();
+ trCollapsed = false;
+ } else {
+ $('.short', $this).show();
+ $('.detailed', $this).hide();
+ trCollapsed = true;
+ }
+ });
+ }
+
+ // Splitter
+ var splitterWidth = $splitter.width();
+ var splitterPosition = $.cookie('splitter') ? parseInt($.cookie('splitter')) : null;
+ var splitterPositionBackup = $.cookie('splitterBackup') ? parseInt($.cookie('splitterBackup')) : null;
+ function setSplitterPosition(position)
+ {
+ splitterPosition = position;
+
+ $left.width(position);
+ $right.css('margin-left', position + splitterWidth);
+ $splitter.css('left', position);
+ }
+ function setContentWidth()
+ {
+ var width = $rightInner.width();
+ $rightInner
+ .toggleClass('medium', width <= 960)
+ .toggleClass('small', width <= 650);
+ }
+ $splitter.mousedown(function() {
+ $splitter.addClass('active');
+
+ $document.mousemove(function(event) {
+ if (event.pageX >= 230 && $document.width() - event.pageX >= 600 + splitterWidth) {
+ setSplitterPosition(event.pageX);
+ setContentWidth();
+ }
+ });
+
+ $()
+ .add($splitter)
+ .add($document)
+ .mouseup(function() {
+ $splitter
+ .removeClass('active')
+ .unbind('mouseup');
+ $document
+ .unbind('mousemove')
+ .unbind('mouseup');
+
+ $.cookie('splitter', splitterPosition, {expires: 365});
+ });
+
+ return false;
+ });
+ $splitter.dblclick(function() {
+ if (splitterPosition) {
+ splitterPositionBackup = $left.width();
+ setSplitterPosition(0);
+ } else {
+ setSplitterPosition(splitterPositionBackup);
+ splitterPositionBackup = null;
+ }
+
+ setContentWidth();
+
+ $.cookie('splitter', splitterPosition, {expires: 365});
+ $.cookie('splitterBackup', splitterPositionBackup, {expires: 365});
+ });
+ if (null !== splitterPosition) {
+ setSplitterPosition(splitterPosition);
+ }
+ setContentWidth();
+ $(window).resize(setContentWidth);
+
+ // Select selected lines
+ var matches = window.location.hash.substr(1).match(/^\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*$/);
+ if (null !== matches) {
+ var lists = matches[0].split(',');
+ for (var i = 0; i < lists.length; i++) {
+ var lines = lists[i].split('-');
+ lines[0] = parseInt(lines[0]);
+ lines[1] = parseInt(lines[1] || lines[0]);
+ for (var j = lines[0]; j <= lines[1]; j++) {
+ $('#' + j).addClass('selected');
+ }
+ }
+
+ var $firstLine = $('#' + parseInt(matches[0]));
+ if ($firstLine.length > 0) {
+ $document.scrollTop($firstLine.offset().top);
+ }
+ }
+
+ // Save selected lines
+ var lastLine;
+ $('.l a').click(function(event) {
+ event.preventDefault();
+
+ var selectedLine = $(this).parent().index() + 1;
+ var $selectedLine = $('pre.code .l').eq(selectedLine - 1);
+
+ if (event.shiftKey) {
+ if (lastLine) {
+ for (var i = Math.min(selectedLine, lastLine); i <= Math.max(selectedLine, lastLine); i++) {
+ $('#' + i).addClass('selected');
+ }
+ } else {
+ $selectedLine.addClass('selected');
+ }
+ } else if (event.ctrlKey) {
+ $selectedLine.toggleClass('selected');
+ } else {
+ var $selected = $('.l.selected')
+ .not($selectedLine)
+ .removeClass('selected');
+ if ($selected.length > 0) {
+ $selectedLine.addClass('selected');
+ } else {
+ $selectedLine.toggleClass('selected');
+ }
+ }
+
+ lastLine = $selectedLine.hasClass('selected') ? selectedLine : null;
+
+ // Update hash
+ var lines = $('.l.selected')
+ .map(function() {
+ return parseInt($(this).attr('id'));
+ })
+ .get()
+ .sort(function(a, b) {
+ return a - b;
+ });
+
+ var hash = [];
+ var list = [];
+ for (var j = 0; j < lines.length; j++) {
+ if (0 === j && j + 1 === lines.length) {
+ hash.push(lines[j]);
+ } else if (0 === j) {
+ list[0] = lines[j];
+ } else if (lines[j - 1] + 1 !== lines[j] && j + 1 === lines.length) {
+ hash.push(list.join('-'));
+ hash.push(lines[j]);
+ } else if (lines[j - 1] + 1 !== lines[j]) {
+ hash.push(list.join('-'));
+ list = [lines[j]];
+ } else if (j + 1 === lines.length) {
+ list[1] = lines[j];
+ hash.push(list.join('-'));
+ } else {
+ list[1] = lines[j];
+ }
+ }
+
+ hash = hash.join(',');
+ $backup = $('#' + hash).removeAttr('id');
+ window.location.hash = hash;
+ $backup.attr('id', hash);
+ });
+});
+
diff --git a/resources/footer.png b/resources/footer.png
new file mode 100644
index 0000000..d99890c
Binary files /dev/null and b/resources/footer.png differ
diff --git a/resources/inherit.png b/resources/inherit.png
new file mode 100644
index 0000000..957079b
Binary files /dev/null and b/resources/inherit.png differ
diff --git a/resources/resize.png b/resources/resize.png
new file mode 100644
index 0000000..fb98a7a
Binary files /dev/null and b/resources/resize.png differ
diff --git a/resources/sort.png b/resources/sort.png
new file mode 100644
index 0000000..0d0fea1
Binary files /dev/null and b/resources/sort.png differ
diff --git a/resources/style.css b/resources/style.css
new file mode 100644
index 0000000..8bf17b5
--- /dev/null
+++ b/resources/style.css
@@ -0,0 +1,619 @@
+body {
+ font: 13px/1.5 Verdana, 'Geneva CE', lucida, sans-serif;
+ margin: 0;
+ padding: 0;
+ background: #ffffff;
+ color: #333333;
+}
+
+h1, h2, h3, h4, caption {
+ font-family: 'Trebuchet MS', 'Geneva CE', lucida, sans-serif;
+ color: #053368;
+}
+
+h1 {
+ color: #1e5eb6;
+ font-size: 230%;
+ font-weight: normal;
+ margin: .3em 0;
+}
+
+h2 {
+ color: #1e5eb6;
+ font-size: 150%;
+ font-weight: normal;
+ margin: -.3em 0 .3em 0;
+}
+
+h3 {
+ font-size: 1.6em;
+ font-weight: normal;
+ margin-bottom: 2px;
+}
+
+h4 {
+ font-size: 100%;
+ font-weight: bold;
+ padding: 0;
+ margin: 0;
+}
+
+caption {
+ border: 1px solid #cccccc;
+ background: #ecede5;
+ font-weight: bold;
+ font-size: 1.2em;
+ padding: 3px 5px;
+ text-align: left;
+ margin-bottom: 0;
+}
+
+p {
+ margin: .7em 0 1em;
+ padding: 0;
+}
+
+hr {
+ margin: 2em 0 1em;
+ border: none;
+ border-top: 1px solid #cccccc;
+ height: 0;
+}
+
+a {
+ color: #006aeb;
+ padding: 3px 1px;
+ text-decoration: none;
+}
+
+h1 a {
+ color: #1e5eb6;
+}
+
+a:hover, a:active, a:focus, a:hover b, a:hover var {
+ background-color: #006aeb;
+ color: #ffffff !important;
+}
+
+code, var, pre {
+ font-family: monospace;
+}
+
+var {
+ font-weight: bold;
+ font-style: normal;
+ color: #ca8a04;
+}
+
+pre {
+ margin: 0;
+}
+
+code a b {
+ color: #000000;
+}
+
+.deprecated {
+ text-decoration: line-through;
+ opacity: .5;
+}
+
+.invalid {
+ color: #e71818;
+}
+
+.hidden {
+ display: none;
+}
+
+/* Left side */
+#left {
+ overflow: auto;
+ width: 270px;
+ height: 100%;
+ position: fixed;
+}
+
+/* Menu */
+#menu {
+ padding: 10px;
+}
+
+#menu ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+#menu ul ul {
+ padding-left: 10px;
+}
+
+#menu li {
+ white-space: nowrap;
+ position: relative;
+}
+
+#menu a {
+ display: block;
+ padding: 0 2px;
+}
+
+#menu .active > a, #menu > span {
+ color: #333333;
+ background: none;
+ font-weight: bold;
+}
+
+#menu .active > a.invalid {
+ color: #e71818;
+}
+
+#menu .active > a:hover, #menu .active > a:active, #menu .active > a:focus {
+ background-color: #006aeb;
+}
+
+#menu #groups span {
+ position: absolute;
+ top: 4px;
+ right: 2px;
+ cursor: pointer;
+ display: block;
+ width: 12px;
+ height: 12px;
+ background: url('collapsed.png') transparent 0 0 no-repeat;
+}
+
+#menu #groups span:hover {
+ background-position: -12px 0;
+}
+
+#menu #groups span.collapsed {
+ background-position: 0 -12px;
+}
+
+#menu #groups span.collapsed:hover {
+ background-position: -12px -12px;
+}
+
+#menu #groups ul.collapsed {
+ display: none;
+}
+
+/* Right side */
+#right {
+ overflow: auto;
+ margin-left: 275px;
+ height: 100%;
+ position: relative;
+ left: 0;
+ right: 0;
+}
+
+#rightInner {
+ max-width: 1000px;
+ min-width: 350px;
+}
+
+/* Search */
+#search {
+ float: right;
+ margin: 3px 8px;
+}
+
+#search input.text {
+ padding: 3px 5px;
+ width: 250px;
+}
+
+/* Autocomplete */
+.ac_results {
+ padding: 0;
+ border: 1px solid #cccccc;
+ background-color: #ffffff;
+ overflow: hidden;
+ z-index: 99999;
+}
+
+.ac_results ul {
+ width: 100%;
+ list-style-position: outside;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+}
+
+.ac_results li {
+ margin: 0;
+ padding: 2px 5px;
+ cursor: default;
+ display: block;
+ font: 12px 'Trebuchet MS', 'Geneva CE', lucida, sans-serif;
+ line-height: 16px;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+.ac_results li strong {
+ color: #000000;
+}
+
+.ac_odd {
+ background-color: #eeeeee;
+}
+
+.ac_over {
+ background-color: #006aeb;
+ color: #ffffff;
+}
+
+.ac_results li.ac_over strong {
+ color: #ffffff;
+}
+
+/* Navigation */
+#navigation {
+ padding: 3px 8px;
+ background-color: #f6f6f4;
+ height: 26px;
+}
+
+#navigation ul {
+ list-style: none;
+ margin: 0 8px 4px 0;
+ padding: 0;
+ overflow: hidden;
+ float: left;
+}
+
+#navigation ul + ul {
+ border-left: 1px solid #000000;
+ padding-left: 8px;
+}
+
+#navigation ul li {
+ float: left;
+ margin: 2px;
+ padding: 0 3px;
+ font-family: Verdana, 'Geneva CE', lucida, sans-serif;
+ color: #808080;
+}
+
+#navigation ul li.active {
+ background-color: #053368;
+ color: #ffffff;
+ font-weight: bold;
+}
+
+#navigation ul li a {
+ color: #000000;
+ font-weight: bold;
+ padding: 0;
+}
+
+#navigation ul li span {
+ float: left;
+ padding: 0 3px;
+}
+
+#navigation ul li a:hover span, #navigation ul li a:active span, #navigation ul li a:focus span {
+ background-color: #006aeb;
+}
+
+/* Content */
+#content {
+ clear: both;
+ padding: 5px 15px;
+}
+
+.description pre {
+ padding: .6em;
+ background: #fcfcf7;
+}
+
+#content > .description {
+ background: #ecede5;
+ padding: 1px 8px;
+ margin: 1.2em 0;
+}
+
+#content > .description pre {
+ margin: .5em 0;
+}
+
+dl.tree {
+ margin: 1.2em 0;
+}
+
+dl.tree dd {
+ margin: 0;
+ padding: 0;
+}
+
+.info {
+ margin: 1.2em 0;
+}
+
+.summary {
+ border: 1px solid #cccccc;
+ border-collapse: collapse;
+ font-size: 1em;
+ width: 100%;
+ margin: 1.2em 0 2.4em;
+}
+
+.summary caption {
+ border-width: 1px 1px 0;
+}
+
+.summary caption.switchable {
+ background: #ecede5 url('sort.png') no-repeat center right;
+ cursor: pointer;
+}
+
+.summary td {
+ border: 1px solid #cccccc;
+ margin: 0;
+ padding: 3px 10px;
+ font-size: 1em;
+ vertical-align: top;
+}
+
+.summary td:first-child {
+ text-align: right;
+}
+
+.summary td hr {
+ margin: 3px -10px;
+}
+
+#packages.summary td:first-child, #namespaces.summary td:first-child, .inherited.summary td:first-child, .used.summary td:first-child {
+ text-align: left;
+}
+
+.summary tr:hover td {
+ background: #f6f6f4;
+}
+
+.summary .description pre {
+ border: .5em solid #ecede5;
+}
+
+.summary .description p {
+ margin: 0;
+}
+
+.summary .description p + p, .summary .description ul {
+ margin: 3px 0 0 0;
+}
+
+.summary .description.detailed h4 {
+ margin-top: 3px;
+}
+
+.summary dl {
+ margin: 0;
+}
+
+.summary dd {
+ margin: 0 0 0 25px;
+}
+
+.name, .attributes {
+ white-space: nowrap;
+}
+
+.value code {
+ white-space: pre-wrap;
+}
+
+td.name, td.attributes {
+ width: 1%;
+}
+
+td.attributes {
+ width: 1%;
+}
+
+.class .methods .name, .class .properties .name, .class .constants .name {
+ width: auto;
+ white-space: normal;
+}
+
+.class .methods .name > div > code {
+ white-space: pre-wrap;
+}
+
+.class .methods .name > div > code span, .function .value > code {
+ white-space: nowrap;
+ display: inline-block;
+}
+
+.class .methods td.name > div, .class td.value > div {
+ position: relative;
+ padding-right: 1em;
+}
+
+.anchor {
+ position: absolute;
+ top: 0;
+ right: 0;
+ line-height: 1;
+ font-size: 85%;
+ margin: 0;
+ color: #006aeb !important;
+}
+
+.list {
+ margin: 0 0 5px 25px;
+}
+
+div.invalid {
+ background-color: #fae4e0;
+ padding: 10px;
+}
+
+/* Splitter */
+#splitter {
+ position: fixed;
+ height: 100%;
+ width: 5px;
+ left: 270px;
+ background: #1e5eb6 url('resize.png') left center no-repeat;
+ cursor: e-resize;
+}
+
+#splitter.active {
+ opacity: .5;
+}
+
+/* Footer */
+#footer {
+ border-top: 1px solid #e9eeef;
+ clear: both;
+ color: #a7a7a7;
+ font-size: 8pt;
+ text-align: center;
+ padding: 20px 0 0;
+ margin: 3em 0 0;
+ height: 90px;
+ background: #ffffff url('footer.png') no-repeat center top;
+}
+
+/* Tree */
+div.tree ul {
+ list-style: none;
+ background: url('tree-vertical.png') left repeat-y;
+ padding: 0;
+ margin-left: 20px;
+}
+
+div.tree li {
+ margin: 0;
+ padding: 0;
+}
+
+div.tree div {
+ padding-left: 30px;
+}
+
+div.tree div.notlast {
+ background: url('tree-hasnext.png') left 10px no-repeat;
+}
+
+div.tree div.last {
+ background: url('tree-last.png') left -240px no-repeat;
+}
+
+div.tree li.last {
+ background: url('tree-cleaner.png') left center repeat-y;
+}
+
+div.tree span.padding {
+ padding-left: 15px;
+}
+
+/* Source code */
+.php-keyword1 {
+ color: #e71818;
+ font-weight: bold;
+}
+
+.php-keyword2 {
+ font-weight: bold;
+}
+
+.php-var {
+ color: #d59401;
+ font-weight: bold;
+}
+
+.php-num {
+ color: #cd0673;
+}
+
+.php-quote {
+ color: #008000;
+}
+
+.php-comment {
+ color: #929292;
+}
+
+.xlang {
+ color: #ff0000;
+ font-weight: bold;
+}
+
+pre.numbers {
+ float: left;
+}
+
+span.l {
+ display: block;
+}
+
+span.l.selected {
+ background: #f6f6f4;
+}
+
+span.l a {
+ color: #333333;
+}
+
+span.l a:hover, div.l a:active, div.l a:focus {
+ background: transparent;
+ color: #333333 !important;
+}
+
+span.l .php-var a {
+ color: #d59401;
+}
+
+span.l .php-var a:hover, span.l .php-var a:active, span.l .php-var a:focus {
+ color: #d59401 !important;
+}
+
+span.l a.l {
+ padding-left: 2px;
+ color: #c0c0c0;
+}
+
+span.l a.l:hover, span.l a.l:active, span.l a.l:focus {
+ background: transparent;
+ color: #c0c0c0 !important;
+}
+
+#rightInner.medium #navigation {
+ height: 52px;
+}
+
+#rightInner.medium #navigation ul:first-child + ul {
+ clear: left;
+ border: none;
+ padding: 0;
+}
+
+#rightInner.medium .name, #rightInner.medium .attributes {
+ white-space: normal;
+}
+
+#rightInner.small #search {
+ float: left;
+}
+
+#rightInner.small #navigation {
+ height: 78px;
+}
+
+#rightInner.small #navigation ul:first-child {
+ clear: both;
+}
+
+/* global style */
+.left, .summary td.left {
+ text-align: left;
+}
+.right, .summary td.right {
+ text-align: right;
+}
diff --git a/resources/tree-cleaner.png b/resources/tree-cleaner.png
new file mode 100644
index 0000000..2eb9085
Binary files /dev/null and b/resources/tree-cleaner.png differ
diff --git a/resources/tree-hasnext.png b/resources/tree-hasnext.png
new file mode 100644
index 0000000..91d6b79
Binary files /dev/null and b/resources/tree-hasnext.png differ
diff --git a/resources/tree-last.png b/resources/tree-last.png
new file mode 100644
index 0000000..7f319f8
Binary files /dev/null and b/resources/tree-last.png differ
diff --git a/resources/tree-vertical.png b/resources/tree-vertical.png
new file mode 100644
index 0000000..384908b
Binary files /dev/null and b/resources/tree-vertical.png differ
diff --git a/screenshot.png b/screenshot.png
deleted file mode 100644
index 171f641..0000000
Binary files a/screenshot.png and /dev/null differ
diff --git a/screenshot2.png b/screenshot2.png
deleted file mode 100644
index 686bcdf..0000000
Binary files a/screenshot2.png and /dev/null differ
diff --git a/source-class-Minimal.html b/source-class-Minimal.html
new file mode 100644
index 0000000..82624b7
--- /dev/null
+++ b/source-class-Minimal.html
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+ File examples/minimal.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28:
+
+ <?php
+require __DIR__ . '/../vendor/autoload.php' ;
+use splitbrain\phpcli\CLI;
+use splitbrain\phpcli\Options;
+
+ class Minimal extends CLI
+{
+
+ protected function setup(Options $options )
+ {
+ $options ->setHelp('A very minimal example that does nothing but print a version' );
+ $options ->registerOption('version' , 'print version' , 'v' );
+ }
+
+
+ protected function main(Options $options )
+ {
+ if ($options ->getOpt ('version' )) {
+ $this ->info('1.0.0' );
+ } else {
+ echo $options ->help();
+ }
+ }
+ }
+
+ $cli = new Minimal();
+$cli ->run();
+
+
+
+
+
+
+
+
diff --git a/source-class-logging.html b/source-class-logging.html
new file mode 100644
index 0000000..ccc9a22
--- /dev/null
+++ b/source-class-logging.html
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+ File examples/logging.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37:
+
+ <?php
+require __DIR__ . '/../vendor/autoload.php' ;
+
+ use splitbrain\phpcli\CLI;
+use splitbrain\phpcli\Options;
+
+ class logging extends CLI
+{
+
+ protected $logdefault = 'debug' ;
+
+
+ protected function setup(Options $options )
+ {
+ $options ->setHelp('A very minimal example that demos the logging' );
+ }
+
+
+ protected function main(Options $options )
+ {
+ $this ->debug('This is a debug message' );
+ $this ->info('This is a info message' );
+ $this ->notice('This is a notice message' );
+ $this ->success('This is a success message' );
+ $this ->warning('This is a warning message' );
+ $this ->error('This is a error message' );
+ $this ->critical('This is a critical message' );
+ $this ->alert('This is a alert message' );
+ $this ->emergency('This is a emergency message' );
+ throw new \Exception('Exception will be caught, too' );
+ }
+ }
+
+
+ $cli = new logging();
+$cli ->run();
+
+
+
+
+
+
+
+
diff --git a/source-class-splitbrain.phpcli.CLI.html b/source-class-splitbrain.phpcli.CLI.html
new file mode 100644
index 0000000..1e8fe0b
--- /dev/null
+++ b/source-class-splitbrain.phpcli.CLI.html
@@ -0,0 +1,478 @@
+
+
+
+
+
+
+ File src/CLI.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363:
+
<?php
+
+ namespace splitbrain\phpcli;
+
+
+ abstract class CLI
+{
+
+ protected $bin ;
+
+ protected $options ;
+
+ public $colors ;
+
+
+ protected $loglevel = array (
+ 'debug' => array ('' , Colors::C_RESET, STDOUT),
+ 'info' => array ('โน ' , Colors::C_CYAN, STDOUT),
+ 'notice' => array ('โ ' , Colors::C_CYAN, STDOUT),
+ 'success' => array ('โ ' , Colors::C_GREEN, STDOUT),
+ 'warning' => array ('โ ' , Colors::C_BROWN, STDERR),
+ 'error' => array ('โ ' , Colors::C_RED, STDERR),
+ 'critical' => array ('โ ' , Colors::C_LIGHTRED, STDERR),
+ 'alert' => array ('โ ' , Colors::C_LIGHTRED, STDERR),
+ 'emergency' => array ('โ ' , Colors::C_LIGHTRED, STDERR),
+ );
+
+ protected $logdefault = 'info' ;
+
+
+ public function __construct($autocatch = true )
+ {
+ if ($autocatch ) {
+ set_exception_handler (array ($this , 'fatal' ));
+ }
+
+ $this ->colors = new Colors();
+ $this ->options = new Options($this ->colors);
+ }
+
+
+ abstract protected function setup(Options $options );
+
+
+ abstract protected function main(Options $options );
+
+
+ public function run()
+ {
+ if ('cli' != php_sapi_name ()) {
+ throw new Exception('This has to be run from the command line' );
+ }
+
+ $this ->setup($this ->options);
+ $this ->registerDefaultOptions();
+ $this ->parseOptions();
+ $this ->handleDefaultOptions();
+ $this ->setupLogging();
+ $this ->checkArgments();
+ $this ->execute();
+
+ exit (0 );
+ }
+
+
+
+
+ protected function registerDefaultOptions()
+ {
+ $this ->options->registerOption(
+ 'help' ,
+ 'Display this help screen and exit immediately.' ,
+ 'h'
+ );
+ $this ->options->registerOption(
+ 'no-colors' ,
+ 'Do not use any colors in output. Useful when piping output to other tools or files.'
+ );
+ $this ->options->registerOption(
+ 'loglevel' ,
+ 'Minimum level of messages to display. Default is ' . $this ->colors->wrap($this ->logdefault, Colors::C_CYAN) . '. ' .
+ 'Valid levels are: debug, info, notice, success, warning, error, critical, alert, emergency.' ,
+ null ,
+ 'level'
+ );
+ }
+
+
+ protected function handleDefaultOptions()
+ {
+ if ($this ->options->getOpt ('no-colors' )) {
+ $this ->colors->disable();
+ }
+ if ($this ->options->getOpt ('help' )) {
+ echo $this ->options->help();
+ exit (0 );
+ }
+ }
+
+
+ protected function setupLogging()
+ {
+ $level = $this ->options->getOpt ('loglevel' , $this ->logdefault);
+ if (!isset ($this ->loglevel[$level ])) $this ->fatal('Unknown log level' );
+ foreach (array_keys ($this ->loglevel) as $l ) {
+ if ($l == $level ) break ;
+ unset ($this ->loglevel[$l ]);
+ }
+ }
+
+
+ protected function parseOptions()
+ {
+ $this ->options->parseOptions();
+ }
+
+
+ protected function checkArgments()
+ {
+ $this ->options->checkArguments();
+ }
+
+
+ protected function execute()
+ {
+ $this ->main($this ->options);
+ }
+
+
+
+
+
+
+ public function fatal($error , array $context = array ())
+ {
+ $code = 0 ;
+ if (is_object ($error ) && is_a ($error , 'Exception' )) {
+
+ $this ->debug(get_class ($error ) . ' caught in ' . $error ->getFile() . ':' . $error ->getLine());
+ $this ->debug($error ->getTraceAsString());
+ $code = $error ->getCode();
+ $error = $error ->getMessage();
+
+ }
+ if (!$code ) {
+ $code = Exception::E_ANY;
+ }
+
+ $this ->critical($error , $context );
+ exit ($code );
+ }
+
+
+ public function emergency($message , array $context = array ())
+ {
+ $this ->log ('emergency' , $message , $context );
+ }
+
+
+ public function alert($message , array $context = array ())
+ {
+ $this ->log ('alert' , $message , $context );
+ }
+
+
+ public function critical($message , array $context = array ())
+ {
+ $this ->log ('critical' , $message , $context );
+ }
+
+
+ public function error($message , array $context = array ())
+ {
+ $this ->log ('error' , $message , $context );
+ }
+
+
+ public function warning($message , array $context = array ())
+ {
+ $this ->log ('warning' , $message , $context );
+ }
+
+
+ public function success($string , array $context = array ())
+ {
+ $this ->log ('success' , $string , $context );
+ }
+
+
+ public function notice($message , array $context = array ())
+ {
+ $this ->log ('notice' , $message , $context );
+ }
+
+
+ public function info($message , array $context = array ())
+ {
+ $this ->log ('info' , $message , $context );
+ }
+
+
+ public function debug($message , array $context = array ())
+ {
+ $this ->log ('debug' , $message , $context );
+ }
+
+
+ public function log ($level , $message , array $context = array ())
+ {
+
+ if (!isset ($this ->loglevel[$level ])) return ;
+
+
+
+
+ list ($prefix , $color , $channel ) = $this ->loglevel[$level ];
+ if (!$this ->colors->isEnabled()) $prefix = '' ;
+
+ $message = $this ->interpolate($message , $context );
+ $this ->colors->ptln($prefix . $message , $color , $channel );
+ }
+
+
+ function interpolate($message , array $context = array ())
+ {
+
+ $replace = array ();
+ foreach ($context as $key => $val ) {
+
+ if (!is_array ($val ) && (!is_object ($val ) || method_exists ($val , '__toString' ))) {
+ $replace ['{' . $key . '}' ] = $val ;
+ }
+ }
+
+
+ return strtr ($message , $replace );
+ }
+
+
+ }
+
+
+
+
+
+
+
+
+
diff --git a/source-class-splitbrain.phpcli.Colors.html b/source-class-splitbrain.phpcli.Colors.html
new file mode 100644
index 0000000..d23b181
--- /dev/null
+++ b/source-class-splitbrain.phpcli.Colors.html
@@ -0,0 +1,286 @@
+
+
+
+
+
+
+ File src/Colors.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171:
+
<?php
+
+ namespace splitbrain\phpcli;
+
+
+ class Colors
+{
+
+ const C_RESET = 'reset' ;
+ const C_BLACK = 'black' ;
+ const C_DARKGRAY = 'darkgray' ;
+ const C_BLUE = 'blue' ;
+ const C_LIGHTBLUE = 'lightblue' ;
+ const C_GREEN = 'green' ;
+ const C_LIGHTGREEN = 'lightgreen' ;
+ const C_CYAN = 'cyan' ;
+ const C_LIGHTCYAN = 'lightcyan' ;
+ const C_RED = 'red' ;
+ const C_LIGHTRED = 'lightred' ;
+ const C_PURPLE = 'purple' ;
+ const C_LIGHTPURPLE = 'lightpurple' ;
+ const C_BROWN = 'brown' ;
+ const C_YELLOW = 'yellow' ;
+ const C_LIGHTGRAY = 'lightgray' ;
+ const C_WHITE = 'white' ;
+
+
+ protected $colors = array (
+ self::C_RESET => "\33[0m" ,
+ self::C_BLACK => "\33[0;30m" ,
+ self::C_DARKGRAY => "\33[1;30m" ,
+ self::C_BLUE => "\33[0;34m" ,
+ self::C_LIGHTBLUE => "\33[1;34m" ,
+ self::C_GREEN => "\33[0;32m" ,
+ self::C_LIGHTGREEN => "\33[1;32m" ,
+ self::C_CYAN => "\33[0;36m" ,
+ self::C_LIGHTCYAN => "\33[1;36m" ,
+ self::C_RED => "\33[0;31m" ,
+ self::C_LIGHTRED => "\33[1;31m" ,
+ self::C_PURPLE => "\33[0;35m" ,
+ self::C_LIGHTPURPLE => "\33[1;35m" ,
+ self::C_BROWN => "\33[0;33m" ,
+ self::C_YELLOW => "\33[1;33m" ,
+ self::C_LIGHTGRAY => "\33[0;37m" ,
+ self::C_WHITE => "\33[1;37m" ,
+ );
+
+
+ protected $enabled = true ;
+
+
+ public function __construct()
+ {
+ if (function_exists ('posix_isatty' ) && !posix_isatty (STDOUT)) {
+ $this ->enabled = false ;
+ return ;
+ }
+ if (!getenv ('TERM' )) {
+ $this ->enabled = false ;
+ return ;
+ }
+ }
+
+
+ public function enable()
+ {
+ $this ->enabled = true ;
+ }
+
+
+ public function disable()
+ {
+ $this ->enabled = false ;
+ }
+
+
+ public function isEnabled()
+ {
+ return $this ->enabled;
+ }
+
+
+ public function ptln($line , $color , $channel = STDOUT)
+ {
+ $this ->set($color );
+ fwrite ($channel , rtrim ($line ) . "\n" );
+ $this ->reset ();
+ }
+
+
+ public function wrap($text , $color )
+ {
+ return $this ->getColorCode($color ) . $text . $this ->getColorCode('reset' );
+ }
+
+
+ public function getColorCode($color )
+ {
+ if (!$this ->enabled) {
+ return '' ;
+ }
+ if (!isset ($this ->colors[$color ])) {
+ throw new Exception("No such color $color " );
+ }
+
+ return $this ->colors[$color ];
+ }
+
+
+ public function set($color , $channel = STDOUT)
+ {
+ fwrite ($channel , $this ->getColorCode($color ));
+ }
+
+
+ public function reset ($channel = STDOUT)
+ {
+ $this ->set('reset' , $channel );
+ }
+ }
+
+
+
+
+
+
+
+
+
diff --git a/source-class-splitbrain.phpcli.Exception.html b/source-class-splitbrain.phpcli.Exception.html
new file mode 100644
index 0000000..a02b65a
--- /dev/null
+++ b/source-class-splitbrain.phpcli.Exception.html
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+ File src/Exception.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36:
+
<?php
+
+ namespace splitbrain\phpcli;
+
+
+ class Exception extends \RuntimeException
+{
+ const E_ANY = -1 ;
+ const E_UNKNOWN_OPT = 1 ;
+ const E_OPT_ARG_REQUIRED = 2 ;
+ const E_OPT_ARG_DENIED = 3 ;
+ const E_OPT_ABIGUOUS = 4 ;
+ const E_ARG_READ = 5 ;
+
+
+ public function __construct($message = "" , $code = 0 , \Exception $previous = null )
+ {
+ if (!$code ) {
+ $code = self::E_ANY;
+ }
+ parent::__construct($message , $code , $previous );
+ }
+ }
+
+
+
+
+
+
+
+
+
diff --git a/source-class-splitbrain.phpcli.Options.html b/source-class-splitbrain.phpcli.Options.html
new file mode 100644
index 0000000..f51db75
--- /dev/null
+++ b/source-class-splitbrain.phpcli.Options.html
@@ -0,0 +1,620 @@
+
+
+
+
+
+
+ File src/Options.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385: 386: 387: 388: 389: 390: 391: 392: 393: 394: 395: 396: 397: 398: 399: 400: 401: 402: 403: 404: 405: 406: 407: 408: 409: 410: 411: 412: 413: 414: 415: 416: 417: 418: 419: 420: 421: 422: 423: 424: 425: 426: 427: 428: 429: 430: 431: 432: 433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443: 444: 445: 446: 447: 448: 449: 450: 451: 452: 453: 454: 455: 456: 457: 458: 459: 460: 461: 462: 463: 464: 465: 466: 467: 468: 469: 470: 471: 472: 473: 474: 475: 476: 477: 478: 479: 480: 481: 482: 483: 484: 485: 486: 487: 488: 489: 490: 491: 492: 493: 494: 495: 496: 497: 498: 499: 500: 501: 502: 503: 504: 505:
+
<?php
+
+ namespace splitbrain\phpcli;
+
+
+ class Options
+{
+
+ protected $setup ;
+
+
+ protected $options = array ();
+
+
+ protected $command = '' ;
+
+
+ protected $args = array ();
+
+
+ protected $bin ;
+
+
+ protected $colors ;
+
+
+ protected $newline = "\n" ;
+
+
+ public function __construct(Colors $colors = null )
+ {
+ if (!is_null ($colors )) {
+ $this ->colors = $colors ;
+ } else {
+ $this ->colors = new Colors();
+ }
+
+ $this ->setup = array (
+ '' => array (
+ 'opts' => array (),
+ 'args' => array (),
+ 'help' => '' ,
+ 'commandhelp' => 'This tool accepts a command as first parameter as outlined below:'
+ )
+ );
+
+ $this ->args = $this ->readPHPArgv();
+ $this ->bin = basename (array_shift ($this ->args));
+
+ $this ->options = array ();
+ }
+
+
+ public function getBin()
+ {
+ return $this ->bin;
+ }
+
+
+ public function setHelp($help )
+ {
+ $this ->setup['' ]['help' ] = $help ;
+ }
+
+
+ public function setCommandHelp($help )
+ {
+ $this ->setup['' ]['commandhelp' ] = $help ;
+ }
+
+
+ public function useCompactHelp($set = true )
+ {
+ $this ->newline = $set ? '' : "\n" ;
+ }
+
+
+ public function registerArgument($arg , $help , $required = true , $command = '' )
+ {
+ if (!isset ($this ->setup[$command ])) {
+ throw new Exception("Command $command not registered" );
+ }
+
+ $this ->setup[$command ]['args' ][] = array (
+ 'name' => $arg ,
+ 'help' => $help ,
+ 'required' => $required
+ );
+ }
+
+
+ public function registerCommand($command , $help )
+ {
+ if (isset ($this ->setup[$command ])) {
+ throw new Exception("Command $command already registered" );
+ }
+
+ $this ->setup[$command ] = array (
+ 'opts' => array (),
+ 'args' => array (),
+ 'help' => $help
+ );
+
+ }
+
+
+ public function registerOption($long , $help , $short = null , $needsarg = false , $command = '' )
+ {
+ if (!isset ($this ->setup[$command ])) {
+ throw new Exception("Command $command not registered" );
+ }
+
+ $this ->setup[$command ]['opts' ][$long ] = array (
+ 'needsarg' => $needsarg ,
+ 'help' => $help ,
+ 'short' => $short
+ );
+
+ if ($short ) {
+ if (strlen ($short ) > 1 ) {
+ throw new Exception("Short options should be exactly one ASCII character" );
+ }
+
+ $this ->setup[$command ]['short' ][$short ] = $long ;
+ }
+ }
+
+
+ public function checkArguments()
+ {
+ $argc = count ($this ->args);
+
+ $req = 0 ;
+ foreach ($this ->setup[$this ->command]['args' ] as $arg ) {
+ if (!$arg ['required' ]) {
+ break ;
+ }
+ $req ++;
+ }
+
+ if ($req > $argc ) {
+ throw new Exception("Not enough arguments" , Exception::E_OPT_ARG_REQUIRED);
+ }
+ }
+
+
+ public function parseOptions()
+ {
+ $non_opts = array ();
+
+ $argc = count ($this ->args);
+ for ($i = 0 ; $i < $argc ; $i ++) {
+ $arg = $this ->args[$i ];
+
+
+
+ if ($arg == '--' ) {
+ $non_opts = array_merge ($non_opts , array_slice ($this ->args, $i + 1 ));
+ break ;
+ }
+
+
+ if ($arg == '-' ) {
+ $non_opts = array_merge ($non_opts , array_slice ($this ->args, $i ));
+ break ;
+ }
+
+
+ if ($arg [0 ] != '-' ) {
+ $non_opts = array_merge ($non_opts , array_slice ($this ->args, $i ));
+ break ;
+ }
+
+
+ if (strlen ($arg ) > 1 && $arg [1 ] === '-' ) {
+ $arg = explode ('=' , substr ($arg , 2 ), 2 );
+ $opt = array_shift ($arg );
+ $val = array_shift ($arg );
+
+ if (!isset ($this ->setup[$this ->command]['opts' ][$opt ])) {
+ throw new Exception("No such option ' $opt '" , Exception::E_UNKNOWN_OPT);
+ }
+
+
+ if ($this ->setup[$this ->command]['opts' ][$opt ]['needsarg' ]) {
+ if (is_null ($val ) && $i + 1 < $argc && !preg_match ('/^--?[\w]/' , $this ->args[$i + 1 ])) {
+ $val = $this ->args[++$i ];
+ }
+ if (is_null ($val )) {
+ throw new Exception("Option $opt requires an argument" ,
+ Exception::E_OPT_ARG_REQUIRED);
+ }
+ $this ->options[$opt ] = $val ;
+ } else {
+ $this ->options[$opt ] = true ;
+ }
+
+ continue ;
+ }
+
+
+ $opt = substr ($arg , 1 );
+ if (!isset ($this ->setup[$this ->command]['short' ][$opt ])) {
+ throw new Exception("No such option $arg " , Exception::E_UNKNOWN_OPT);
+ } else {
+ $opt = $this ->setup[$this ->command]['short' ][$opt ];
+ }
+
+
+ if ($this ->setup[$this ->command]['opts' ][$opt ]['needsarg' ]) {
+ $val = null ;
+ if ($i + 1 < $argc && !preg_match ('/^--?[\w]/' , $this ->args[$i + 1 ])) {
+ $val = $this ->args[++$i ];
+ }
+ if (is_null ($val )) {
+ throw new Exception("Option $arg requires an argument" ,
+ Exception::E_OPT_ARG_REQUIRED);
+ }
+ $this ->options[$opt ] = $val ;
+ } else {
+ $this ->options[$opt ] = true ;
+ }
+ }
+
+
+ $this ->args = $non_opts ;
+
+
+ if (!$this ->command && $this ->args && isset ($this ->setup[$this ->args[0 ]])) {
+
+ $this ->command = array_shift ($this ->args);
+ $this ->parseOptions();
+ }
+ }
+
+
+ public function getOpt ($option = null , $default = false )
+ {
+ if ($option === null ) {
+ return $this ->options;
+ }
+
+ if (isset ($this ->options[$option ])) {
+ return $this ->options[$option ];
+ }
+ return $default ;
+ }
+
+
+ public function getCmd()
+ {
+ return $this ->command;
+ }
+
+
+ public function getArgs()
+ {
+ return $this ->args;
+ }
+
+
+ public function help()
+ {
+ $tf = new TableFormatter($this ->colors);
+ $text = '' ;
+
+ $hascommands = (count ($this ->setup) > 1 );
+ $commandhelp = $this ->setup['' ]["commandhelp" ];
+
+ foreach ($this ->setup as $command => $config ) {
+ $hasopts = (bool)$this ->setup[$command ]['opts' ];
+ $hasargs = (bool)$this ->setup[$command ]['args' ];
+
+
+ if (!$command ) {
+ $text .= $this ->colors->wrap('USAGE:' , Colors::C_BROWN);
+ $text .= "\n" ;
+ $text .= ' ' . $this ->bin;
+ $mv = 2 ;
+ } else {
+ $text .= $this ->newline;
+ $text .= $this ->colors->wrap(' ' . $command , Colors::C_PURPLE);
+ $mv = 4 ;
+ }
+
+ if ($hasopts ) {
+ $text .= ' ' . $this ->colors->wrap('<OPTIONS>' , Colors::C_GREEN);
+ }
+
+ if (!$command && $hascommands ) {
+ $text .= ' ' . $this ->colors->wrap('<COMMAND> ...' , Colors::C_PURPLE);
+ }
+
+ foreach ($this ->setup[$command ]['args' ] as $arg ) {
+ $out = $this ->colors->wrap('<' . $arg ['name' ] . '>' , Colors::C_CYAN);
+
+ if (!$arg ['required' ]) {
+ $out = '[' . $out . ']' ;
+ }
+ $text .= ' ' . $out ;
+ }
+ $text .= $this ->newline;
+
+
+ if ($this ->setup[$command ]['help' ]) {
+ $text .= "\n" ;
+ $text .= $tf ->format(
+ array ($mv , '*' ),
+ array ('' , $this ->setup[$command ]['help' ] . $this ->newline)
+ );
+ }
+
+
+ if ($hasopts ) {
+ if (!$command ) {
+ $text .= "\n" ;
+ $text .= $this ->colors->wrap('OPTIONS:' , Colors::C_BROWN);
+ }
+ $text .= "\n" ;
+ foreach ($this ->setup[$command ]['opts' ] as $long => $opt ) {
+
+ $name = '' ;
+ if ($opt ['short' ]) {
+ $name .= '-' . $opt ['short' ];
+ if ($opt ['needsarg' ]) {
+ $name .= ' <' . $opt ['needsarg' ] . '>' ;
+ }
+ $name .= ', ' ;
+ }
+ $name .= "-- $long " ;
+ if ($opt ['needsarg' ]) {
+ $name .= ' <' . $opt ['needsarg' ] . '>' ;
+ }
+
+ $text .= $tf ->format(
+ array ($mv , '30%' , '*' ),
+ array ('' , $name , $opt ['help' ]),
+ array ('' , 'green' , '' )
+ );
+ $text .= $this ->newline;
+ }
+ }
+
+
+ if ($hasargs ) {
+ if (!$command ) {
+ $text .= "\n" ;
+ $text .= $this ->colors->wrap('ARGUMENTS:' , Colors::C_BROWN);
+ }
+ $text .= $this ->newline;
+ foreach ($this ->setup[$command ]['args' ] as $arg ) {
+ $name = '<' . $arg ['name' ] . '>' ;
+
+ $text .= $tf ->format(
+ array ($mv , '30%' , '*' ),
+ array ('' , $name , $arg ['help' ]),
+ array ('' , 'cyan' , '' )
+ );
+ }
+ }
+
+
+ if (!$command && $hascommands ) {
+ $text .= "\n" ;
+ $text .= $this ->colors->wrap('COMMANDS:' , Colors::C_BROWN);
+ $text .= "\n" ;
+ $text .= $tf ->format(
+ array ($mv , '*' ),
+ array ('' , $commandhelp )
+ );
+ $text .= $this ->newline;
+ }
+ }
+
+ return $text ;
+ }
+
+
+ private function readPHPArgv()
+ {
+ global $argv ;
+ if (!is_array ($argv )) {
+ if (!@is_array ($_SERVER ['argv' ])) {
+ if (!@is_array ($GLOBALS ['HTTP_SERVER_VARS' ]['argv' ])) {
+ throw new Exception(
+ "Could not read cmd args (register_argc_argv=Off?)" ,
+ Exception::E_ARG_READ
+ );
+ }
+ return $GLOBALS ['HTTP_SERVER_VARS' ]['argv' ];
+ }
+ return $_SERVER ['argv' ];
+ }
+ return $argv ;
+ }
+ }
+
+
+
+
+
+
+
+
+
+
diff --git a/source-class-splitbrain.phpcli.PSR3CLI.html b/source-class-splitbrain.phpcli.PSR3CLI.html
new file mode 100644
index 0000000..2a4bb25
--- /dev/null
+++ b/source-class-splitbrain.phpcli.PSR3CLI.html
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+ File src/PSR3CLI.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
+
<?php
+
+ namespace splitbrain\phpcli;
+
+ use Psr\Log \LoggerInterface;
+
+
+ abstract class PSR3CLI extends CLI implements LoggerInterface {
+}
+
+
+
+
+
+
+
+
diff --git a/source-class-splitbrain.phpcli.TableFormatter.html b/source-class-splitbrain.phpcli.TableFormatter.html
new file mode 100644
index 0000000..4cd6f06
--- /dev/null
+++ b/source-class-splitbrain.phpcli.TableFormatter.html
@@ -0,0 +1,440 @@
+
+
+
+
+
+
+ File src/TableFormatter.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325:
+
<?php
+
+ namespace splitbrain\phpcli;
+
+
+ class TableFormatter
+{
+
+ protected $border = ' ' ;
+
+
+ protected $max = 74 ;
+
+
+ protected $colors ;
+
+
+ public function __construct(Colors $colors = null )
+ {
+
+ $width = $this ->getTerminalWidth();
+ if ($width ) {
+ $this ->max = $width - 1 ;
+ }
+
+ if ($colors ) {
+ $this ->colors = $colors ;
+ } else {
+ $this ->colors = new Colors();
+ }
+ }
+
+
+ public function getBorder()
+ {
+ return $this ->border;
+ }
+
+
+ public function setBorder($border )
+ {
+ $this ->border = $border ;
+ }
+
+
+ public function getMaxWidth()
+ {
+ return $this ->max ;
+ }
+
+
+ public function setMaxWidth($max )
+ {
+ $this ->max = $max ;
+ }
+
+
+ protected function getTerminalWidth()
+ {
+
+ if (isset ($_SERVER ['COLUMNS' ])) return (int)$_SERVER ['COLUMNS' ];
+
+
+ $process = proc_open ('tput cols' , array (
+ 1 => array ('pipe' , 'w' ),
+ 2 => array ('pipe' , 'w' ),
+ ), $pipes );
+ $width = (int)stream_get_contents ($pipes [1 ]);
+ proc_close ($process );
+
+ return $width ;
+ }
+
+
+ protected function calculateColLengths($columns )
+ {
+ $idx = 0 ;
+ $border = $this ->strlen ($this ->border);
+ $fixed = (count ($columns ) - 1 ) * $border ;
+ $fluid = -1 ;
+
+
+ foreach ($columns as $idx => $col ) {
+
+ if ((string)intval ($col ) === (string)$col ) {
+ $fixed += $col ;
+ continue ;
+ }
+
+ if (substr ($col , -1 ) == '%' ) {
+ continue ;
+ }
+ if ($col == '*' ) {
+
+ if ($fluid < 0 ) {
+ $fluid = $idx ;
+ continue ;
+ } else {
+ throw new Exception('Only one fluid column allowed!' );
+ }
+ }
+ throw new Exception("unknown column format $col " );
+ }
+
+ $alloc = $fixed ;
+ $remain = $this ->max - $alloc ;
+
+
+ foreach ($columns as $idx => $col ) {
+ if (substr ($col , -1 ) != '%' ) {
+ continue ;
+ }
+ $perc = floatval ($col );
+
+ $real = (int)floor (($perc * $remain ) / 100 );
+
+ $columns [$idx ] = $real ;
+ $alloc += $real ;
+ }
+
+ $remain = $this ->max - $alloc ;
+ if ($remain < 0 ) {
+ throw new Exception("Wanted column widths exceed available space" );
+ }
+
+
+ if ($fluid < 0 ) {
+ $columns [$idx ] += ($remain );
+ } else {
+ $columns [$fluid ] = $remain ;
+ }
+
+ return $columns ;
+ }
+
+
+ public function format($columns , $texts , $colors = array ())
+ {
+ $columns = $this ->calculateColLengths($columns );
+
+ $wrapped = array ();
+ $maxlen = 0 ;
+
+ foreach ($columns as $col => $width ) {
+ $wrapped [$col ] = explode ("\n" , $this ->wordwrap ($texts [$col ], $width , "\n" , true ));
+ $len = count ($wrapped [$col ]);
+ if ($len > $maxlen ) {
+ $maxlen = $len ;
+ }
+
+ }
+
+ $last = count ($columns ) - 1 ;
+ $out = '' ;
+ for ($i = 0 ; $i < $maxlen ; $i ++) {
+ foreach ($columns as $col => $width ) {
+ if (isset ($wrapped [$col ][$i ])) {
+ $val = $wrapped [$col ][$i ];
+ } else {
+ $val = '' ;
+ }
+ $chunk = $this ->pad($val , $width );
+ if (isset ($colors [$col ]) && $colors [$col ]) {
+ $chunk = $this ->colors->wrap($chunk , $colors [$col ]);
+ }
+ $out .= $chunk ;
+
+
+ if ($col != $last ) {
+ $out .= $this ->border;
+ }
+ }
+ $out .= "\n" ;
+ }
+ return $out ;
+
+ }
+
+
+ protected function pad($string , $len )
+ {
+ $strlen = $this ->strlen ($string );
+ if ($strlen > $len ) return $string ;
+
+ $pad = $len - $strlen ;
+ return $string . str_pad ('' , $pad , ' ' );
+ }
+
+
+ protected function strlen ($string )
+ {
+
+ $string = preg_replace ("/\33\\[\\d+(;\\d+)?m/" , '' , $string );
+
+ if (function_exists ('mb_strlen' )) {
+ return mb_strlen ($string , 'utf-8' );
+ }
+
+ return strlen ($string );
+ }
+
+
+ protected function substr ($string , $start = 0 , $length = null )
+ {
+ if (function_exists ('mb_substr' )) {
+ return mb_substr ($string , $start , $length );
+ } else {
+
+ if ($length ) {
+ return substr ($string , $start , $length );
+ } else {
+ return substr ($string , $start );
+ }
+ }
+ }
+
+
+ protected function wordwrap ($str , $width = 75 , $break = "\n" , $cut = false )
+ {
+ $lines = explode ($break , $str );
+ foreach ($lines as &$line ) {
+ $line = rtrim ($line );
+ if ($this ->strlen ($line ) <= $width ) {
+ continue ;
+ }
+ $words = explode (' ' , $line );
+ $line = '' ;
+ $actual = '' ;
+ foreach ($words as $word ) {
+ if ($this ->strlen ($actual . $word ) <= $width ) {
+ $actual .= $word . ' ' ;
+ } else {
+ if ($actual != '' ) {
+ $line .= rtrim ($actual ) . $break ;
+ }
+ $actual = $word ;
+ if ($cut ) {
+ while ($this ->strlen ($actual ) > $width ) {
+ $line .= $this ->substr ($actual , 0 , $width ) . $break ;
+ $actual = $this ->substr ($actual , $width );
+ }
+ }
+ $actual .= ' ' ;
+ }
+ }
+ $line .= trim ($actual );
+ }
+ return implode ($break , $lines );
+ }
+ }
+
+
+
+
+
+
+
+
diff --git a/source-class-splitbrain.phpcli.tests.Options.html b/source-class-splitbrain.phpcli.tests.Options.html
new file mode 100644
index 0000000..f866896
--- /dev/null
+++ b/source-class-splitbrain.phpcli.tests.Options.html
@@ -0,0 +1,206 @@
+
+
+
+
+
+
+ File tests/OptionsTest.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91:
+
<?php
+
+ namespace splitbrain\phpcli\tests;
+
+ class Options extends \splitbrain\phpcli\Options
+{
+ public $args ;
+ }
+
+ class OptionsTest extends \PHPUnit\Framework\TestCase
+{
+
+
+ function test_optionvariants(
+ $option ,
+ $value ,
+ $argument
+ ) {
+ $options = new Options();
+ $options ->registerOption('exclude' , 'exclude files' , 'x' , 'file' );
+
+ $options ->args = array ($option , $value , $argument );
+ $options ->parseOptions();
+
+ $this ->assertEquals($value , $options ->getOpt ('exclude' ));
+ $this ->assertEquals(array ($argument ), $options ->args);
+ $this ->assertFalse($options ->getOpt ('nothing' ));
+ }
+
+
+ public function optionDataProvider() {
+ return array (
+ array ('-x' , 'foo' , 'bang' ),
+ array ('--exclude' , 'foo' , 'bang' ),
+ array ('-x' , 'foo-bar' , 'bang' ),
+ array ('--exclude' , 'foo-bar' , 'bang' ),
+ array ('-x' , 'foo' , 'bang--bang' ),
+ array ('--exclude' , 'foo' , 'bang--bang' ),
+ );
+ }
+
+ function test_simplelong2()
+ {
+ $options = new Options();
+ $options ->registerOption('exclude' , 'exclude files' , 'x' , 'file' );
+
+ $options ->args = array ('--exclude=foo' , 'bang' );
+ $options ->parseOptions();
+
+ $this ->assertEquals('foo' , $options ->getOpt ('exclude' ));
+ $this ->assertEquals(array ('bang' ), $options ->args);
+ $this ->assertFalse($options ->getOpt ('nothing' ));
+ }
+
+ function test_complex()
+ {
+ $options = new Options();
+
+ $options ->registerOption('plugins' , 'run on plugins only' , 'p' );
+ $options ->registerCommand('status' , 'display status info' );
+ $options ->registerOption('long' , 'display long lines' , 'l' , false , 'status' );
+
+ $options ->args = array ('-p' , 'status' , '--long' , 'foo' );
+ $options ->parseOptions();
+
+ $this ->assertEquals('status' , $options ->getCmd());
+ $this ->assertTrue($options ->getOpt ('plugins' ));
+ $this ->assertTrue($options ->getOpt ('long' ));
+ $this ->assertEquals(array ('foo' ), $options ->args);
+ }
+
+ function test_commandhelp()
+ {
+ $options = new Options();
+ $options ->registerCommand('cmd' , 'a command' );
+ $this ->assertStringContainsString('accepts a command as first parameter' , $options ->help());
+
+ $options ->setCommandHelp('foooooobaar' );
+ $this ->assertStringNotContainsString('accepts a command as first parameter' , $options ->help());
+ $this ->assertStringContainsString('foooooobaar' , $options ->help());
+ }
+ }
+
+
+
+
+
+
+
+
+
diff --git a/source-class-splitbrain.phpcli.tests.OptionsTest.html b/source-class-splitbrain.phpcli.tests.OptionsTest.html
new file mode 100644
index 0000000..f866896
--- /dev/null
+++ b/source-class-splitbrain.phpcli.tests.OptionsTest.html
@@ -0,0 +1,206 @@
+
+
+
+
+
+
+ File tests/OptionsTest.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91:
+
<?php
+
+ namespace splitbrain\phpcli\tests;
+
+ class Options extends \splitbrain\phpcli\Options
+{
+ public $args ;
+ }
+
+ class OptionsTest extends \PHPUnit\Framework\TestCase
+{
+
+
+ function test_optionvariants(
+ $option ,
+ $value ,
+ $argument
+ ) {
+ $options = new Options();
+ $options ->registerOption('exclude' , 'exclude files' , 'x' , 'file' );
+
+ $options ->args = array ($option , $value , $argument );
+ $options ->parseOptions();
+
+ $this ->assertEquals($value , $options ->getOpt ('exclude' ));
+ $this ->assertEquals(array ($argument ), $options ->args);
+ $this ->assertFalse($options ->getOpt ('nothing' ));
+ }
+
+
+ public function optionDataProvider() {
+ return array (
+ array ('-x' , 'foo' , 'bang' ),
+ array ('--exclude' , 'foo' , 'bang' ),
+ array ('-x' , 'foo-bar' , 'bang' ),
+ array ('--exclude' , 'foo-bar' , 'bang' ),
+ array ('-x' , 'foo' , 'bang--bang' ),
+ array ('--exclude' , 'foo' , 'bang--bang' ),
+ );
+ }
+
+ function test_simplelong2()
+ {
+ $options = new Options();
+ $options ->registerOption('exclude' , 'exclude files' , 'x' , 'file' );
+
+ $options ->args = array ('--exclude=foo' , 'bang' );
+ $options ->parseOptions();
+
+ $this ->assertEquals('foo' , $options ->getOpt ('exclude' ));
+ $this ->assertEquals(array ('bang' ), $options ->args);
+ $this ->assertFalse($options ->getOpt ('nothing' ));
+ }
+
+ function test_complex()
+ {
+ $options = new Options();
+
+ $options ->registerOption('plugins' , 'run on plugins only' , 'p' );
+ $options ->registerCommand('status' , 'display status info' );
+ $options ->registerOption('long' , 'display long lines' , 'l' , false , 'status' );
+
+ $options ->args = array ('-p' , 'status' , '--long' , 'foo' );
+ $options ->parseOptions();
+
+ $this ->assertEquals('status' , $options ->getCmd());
+ $this ->assertTrue($options ->getOpt ('plugins' ));
+ $this ->assertTrue($options ->getOpt ('long' ));
+ $this ->assertEquals(array ('foo' ), $options ->args);
+ }
+
+ function test_commandhelp()
+ {
+ $options = new Options();
+ $options ->registerCommand('cmd' , 'a command' );
+ $this ->assertStringContainsString('accepts a command as first parameter' , $options ->help());
+
+ $options ->setCommandHelp('foooooobaar' );
+ $this ->assertStringNotContainsString('accepts a command as first parameter' , $options ->help());
+ $this ->assertStringContainsString('foooooobaar' , $options ->help());
+ }
+ }
+
+
+
+
+
+
+
+
+
diff --git a/source-class-splitbrain.phpcli.tests.TableFormatter.html b/source-class-splitbrain.phpcli.tests.TableFormatter.html
new file mode 100644
index 0000000..6c8da34
--- /dev/null
+++ b/source-class-splitbrain.phpcli.tests.TableFormatter.html
@@ -0,0 +1,257 @@
+
+
+
+
+
+
+ File tests/TableFormatterTest.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142:
+
<?php
+
+ namespace splitbrain\phpcli\tests;
+
+ use splitbrain\phpcli\Colors;
+
+ class TableFormatter extends \splitbrain\phpcli\TableFormatter
+{
+ public function calculateColLengths($columns )
+ {
+ return parent::calculateColLengths($columns );
+ }
+
+ public function strlen ($string )
+ {
+ return parent::strlen ($string );
+ }
+
+ public function wordwrap ($str , $width = 75 , $break = "\n" , $cut = false )
+ {
+ return parent::wordwrap ($str , $width , $break , $cut );
+ }
+
+ }
+
+ class TableFormatterTest extends \PHPUnit\Framework\TestCase
+{
+
+
+ public function calcProvider()
+ {
+ return array (
+ array (
+ array (5 , 5 , 5 ),
+ array (5 , 5 , 88 )
+ ),
+
+ array (
+ array ('*' , 5 , 5 ),
+ array (88 , 5 , 5 )
+ ),
+
+ array (
+ array (5 , '50%' , '50%' ),
+ array (5 , 46 , 47 )
+ ),
+
+ array (
+ array (5 , '*' , '50%' ),
+ array (5 , 47 , 46 )
+ ),
+ );
+ }
+
+
+ public function test_calc($input , $expect , $max = 100 , $border = ' ' )
+ {
+ $tf = new TableFormatter();
+ $tf ->setMaxWidth($max );
+ $tf ->setBorder($border );
+
+ $result = $tf ->calculateColLengths($input );
+
+ $this ->assertEquals($max , array_sum ($result ) + (strlen ($border ) * (count ($input ) - 1 )));
+ $this ->assertEquals($expect , $result );
+
+ }
+
+
+ public function test_wrap()
+ {
+ $text = "this is a long string something\n" .
+ "123456789012345678901234567890" ;
+
+ $expt = "this is a long\n" .
+ "string\n" .
+ "something\n" .
+ "123456789012345\n" .
+ "678901234567890" ;
+
+ $tf = new TableFormatter();
+ $this ->assertEquals($expt , $tf ->wordwrap ($text , 15 , "\n" , true ));
+
+ }
+
+ public function test_length()
+ {
+ $text = "this is hรคppy โบ" ;
+ $expect = " $text |test" ;
+
+ $tf = new TableFormatter();
+ $tf ->setBorder('|' );
+ $result = $tf ->format([20 , '*' ], [$text , 'test' ]);
+
+ $this ->assertEquals($expect , trim ($result ));
+ }
+
+ public function test_colorlength()
+ {
+ $color = new Colors();
+
+ $text = 'this is ' . $color ->wrap('green' , Colors::C_GREEN);
+ $expect = " $text |test" ;
+
+ $tf = new TableFormatter();
+ $tf ->setBorder('|' );
+ $result = $tf ->format([20 , '*' ], [$text , 'test' ]);
+
+ $this ->assertEquals($expect , trim ($result ));
+ }
+
+ public function test_onewrap()
+ {
+ $col1 = "test\nwrap" ;
+ $col2 = "test" ;
+
+ $expect = "test |test \n" .
+ "wrap | \n" ;
+
+ $tf = new TableFormatter();
+ $tf ->setMaxWidth(11 );
+ $tf ->setBorder('|' );
+
+ $result = $tf ->format([5 , '*' ], [$col1 , $col2 ]);
+ $this ->assertEquals($expect , $result );
+ }
+ }
+
+
+
+
+
+
+
+
+
diff --git a/source-class-splitbrain.phpcli.tests.TableFormatterTest.html b/source-class-splitbrain.phpcli.tests.TableFormatterTest.html
new file mode 100644
index 0000000..6c8da34
--- /dev/null
+++ b/source-class-splitbrain.phpcli.tests.TableFormatterTest.html
@@ -0,0 +1,257 @@
+
+
+
+
+
+
+ File tests/TableFormatterTest.php
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142:
+
<?php
+
+ namespace splitbrain\phpcli\tests;
+
+ use splitbrain\phpcli\Colors;
+
+ class TableFormatter extends \splitbrain\phpcli\TableFormatter
+{
+ public function calculateColLengths($columns )
+ {
+ return parent::calculateColLengths($columns );
+ }
+
+ public function strlen ($string )
+ {
+ return parent::strlen ($string );
+ }
+
+ public function wordwrap ($str , $width = 75 , $break = "\n" , $cut = false )
+ {
+ return parent::wordwrap ($str , $width , $break , $cut );
+ }
+
+ }
+
+ class TableFormatterTest extends \PHPUnit\Framework\TestCase
+{
+
+
+ public function calcProvider()
+ {
+ return array (
+ array (
+ array (5 , 5 , 5 ),
+ array (5 , 5 , 88 )
+ ),
+
+ array (
+ array ('*' , 5 , 5 ),
+ array (88 , 5 , 5 )
+ ),
+
+ array (
+ array (5 , '50%' , '50%' ),
+ array (5 , 46 , 47 )
+ ),
+
+ array (
+ array (5 , '*' , '50%' ),
+ array (5 , 47 , 46 )
+ ),
+ );
+ }
+
+
+ public function test_calc($input , $expect , $max = 100 , $border = ' ' )
+ {
+ $tf = new TableFormatter();
+ $tf ->setMaxWidth($max );
+ $tf ->setBorder($border );
+
+ $result = $tf ->calculateColLengths($input );
+
+ $this ->assertEquals($max , array_sum ($result ) + (strlen ($border ) * (count ($input ) - 1 )));
+ $this ->assertEquals($expect , $result );
+
+ }
+
+
+ public function test_wrap()
+ {
+ $text = "this is a long string something\n" .
+ "123456789012345678901234567890" ;
+
+ $expt = "this is a long\n" .
+ "string\n" .
+ "something\n" .
+ "123456789012345\n" .
+ "678901234567890" ;
+
+ $tf = new TableFormatter();
+ $this ->assertEquals($expt , $tf ->wordwrap ($text , 15 , "\n" , true ));
+
+ }
+
+ public function test_length()
+ {
+ $text = "this is hรคppy โบ" ;
+ $expect = " $text |test" ;
+
+ $tf = new TableFormatter();
+ $tf ->setBorder('|' );
+ $result = $tf ->format([20 , '*' ], [$text , 'test' ]);
+
+ $this ->assertEquals($expect , trim ($result ));
+ }
+
+ public function test_colorlength()
+ {
+ $color = new Colors();
+
+ $text = 'this is ' . $color ->wrap('green' , Colors::C_GREEN);
+ $expect = " $text |test" ;
+
+ $tf = new TableFormatter();
+ $tf ->setBorder('|' );
+ $result = $tf ->format([20 , '*' ], [$text , 'test' ]);
+
+ $this ->assertEquals($expect , trim ($result ));
+ }
+
+ public function test_onewrap()
+ {
+ $col1 = "test\nwrap" ;
+ $col2 = "test" ;
+
+ $expect = "test |test \n" .
+ "wrap | \n" ;
+
+ $tf = new TableFormatter();
+ $tf ->setMaxWidth(11 );
+ $tf ->setBorder('|' );
+
+ $result = $tf ->format([5 , '*' ], [$col1 , $col2 ]);
+ $this ->assertEquals($expect , $result );
+ }
+ }
+
+
+
+
+
+
+
+
+
diff --git a/src/Base.php b/src/Base.php
deleted file mode 100644
index a3b6049..0000000
--- a/src/Base.php
+++ /dev/null
@@ -1,333 +0,0 @@
-
- * @license MIT
- */
-abstract class Base
-{
- /** @var string the executed script itself */
- protected $bin;
- /** @var Options the option parser */
- protected $options;
- /** @var Colors */
- public $colors;
-
- /** @var array PSR-3 compatible loglevels and their prefix, color, output channel, enabled status */
- protected $loglevel = array(
- 'debug' => array(
- 'icon' => '',
- 'color' => Colors::C_RESET,
- 'channel' => STDOUT,
- 'enabled' => true
- ),
- 'info' => array(
- 'icon' => 'โน ',
- 'color' => Colors::C_CYAN,
- 'channel' => STDOUT,
- 'enabled' => true
- ),
- 'notice' => array(
- 'icon' => 'โ ',
- 'color' => Colors::C_CYAN,
- 'channel' => STDOUT,
- 'enabled' => true
- ),
- 'success' => array(
- 'icon' => 'โ ',
- 'color' => Colors::C_GREEN,
- 'channel' => STDOUT,
- 'enabled' => true
- ),
- 'warning' => array(
- 'icon' => 'โ ',
- 'color' => Colors::C_BROWN,
- 'channel' => STDERR,
- 'enabled' => true
- ),
- 'error' => array(
- 'icon' => 'โ ',
- 'color' => Colors::C_RED,
- 'channel' => STDERR,
- 'enabled' => true
- ),
- 'critical' => array(
- 'icon' => 'โ ',
- 'color' => Colors::C_LIGHTRED,
- 'channel' => STDERR,
- 'enabled' => true
- ),
- 'alert' => array(
- 'icon' => 'โ ',
- 'color' => Colors::C_LIGHTRED,
- 'channel' => STDERR,
- 'enabled' => true
- ),
- 'emergency' => array(
- 'icon' => 'โ ',
- 'color' => Colors::C_LIGHTRED,
- 'channel' => STDERR,
- 'enabled' => true
- ),
- );
-
- /** @var string default log level */
- protected $logdefault = 'info';
-
- /**
- * constructor
- *
- * Initialize the arguments, set up helper classes and set up the CLI environment
- *
- * @param bool $autocatch should exceptions be catched and handled automatically?
- */
- public function __construct($autocatch = true)
- {
- if ($autocatch) {
- set_exception_handler(array($this, 'fatal'));
- }
- $this->setLogLevel($this->logdefault);
- $this->colors = new Colors();
- $this->options = new Options($this->colors);
- }
-
- /**
- * Register options and arguments on the given $options object
- *
- * @param Options $options
- * @return void
- *
- * @throws Exception
- */
- abstract protected function setup(Options $options);
-
- /**
- * Your main program
- *
- * Arguments and options have been parsed when this is run
- *
- * @param Options $options
- * @return void
- *
- * @throws Exception
- */
- abstract protected function main(Options $options);
-
- /**
- * Execute the CLI program
- *
- * Executes the setup() routine, adds default options, initiate the options parsing and argument checking
- * and finally executes main() - Each part is split into their own protected function below, so behaviour
- * can easily be overwritten
- *
- * @throws Exception
- */
- public function run()
- {
- if ('cli' != php_sapi_name()) {
- throw new Exception('This has to be run from the command line');
- }
-
- $this->setup($this->options);
- $this->registerDefaultOptions();
- $this->parseOptions();
- $this->handleDefaultOptions();
- $this->setupLogging();
- $this->checkArguments();
- $this->execute();
- }
-
- // region run handlers - for easier overriding
-
- /**
- * Add the default help, color and log options
- */
- protected function registerDefaultOptions()
- {
- $this->options->registerOption(
- 'help',
- 'Display this help screen and exit immediately.',
- 'h'
- );
- $this->options->registerOption(
- 'no-colors',
- 'Do not use any colors in output. Useful when piping output to other tools or files.'
- );
- $this->options->registerOption(
- 'loglevel',
- 'Minimum level of messages to display. Default is ' . $this->colors->wrap($this->logdefault, Colors::C_CYAN) . '. ' .
- 'Valid levels are: debug, info, notice, success, warning, error, critical, alert, emergency.',
- null,
- 'level'
- );
- }
-
- /**
- * Handle the default options
- */
- protected function handleDefaultOptions()
- {
- if ($this->options->getOpt('no-colors')) {
- $this->colors->disable();
- }
- if ($this->options->getOpt('help')) {
- echo $this->options->help();
- exit(0);
- }
- }
-
- /**
- * Handle the logging options
- */
- protected function setupLogging()
- {
- $level = $this->options->getOpt('loglevel', $this->logdefault);
- $this->setLogLevel($level);
- }
-
- /**
- * Wrapper around the option parsing
- */
- protected function parseOptions()
- {
- $this->options->parseOptions();
- }
-
- /**
- * Wrapper around the argument checking
- */
- protected function checkArguments()
- {
- $this->options->checkArguments();
- }
-
- /**
- * Wrapper around main
- */
- protected function execute()
- {
- $this->main($this->options);
- }
-
- // endregion
-
- // region logging
-
- /**
- * Set the current log level
- *
- * @param string $level
- */
- public function setLogLevel($level)
- {
- if (!isset($this->loglevel[$level])) $this->fatal('Unknown log level');
- $enable = false;
- foreach (array_keys($this->loglevel) as $l) {
- if ($l == $level) $enable = true;
- $this->loglevel[$l]['enabled'] = $enable;
- }
- }
-
- /**
- * Check if a message with the given level should be logged
- *
- * @param string $level
- * @return bool
- */
- public function isLogLevelEnabled($level)
- {
- if (!isset($this->loglevel[$level])) $this->fatal('Unknown log level');
- return $this->loglevel[$level]['enabled'];
- }
-
- /**
- * Exits the program on a fatal error
- *
- * @param \Exception|string $error either an exception or an error message
- * @param array $context
- */
- public function fatal($error, array $context = array())
- {
- $code = 0;
- if (is_object($error) && is_a($error, 'Exception')) {
- /** @var Exception $error */
- $this->logMessage('debug', get_class($error) . ' caught in ' . $error->getFile() . ':' . $error->getLine());
- $this->logMessage('debug', $error->getTraceAsString());
- $code = $error->getCode();
- $error = $error->getMessage();
-
- }
- if (!$code) {
- $code = Exception::E_ANY;
- }
-
- $this->logMessage('critical', $error, $context);
- exit($code);
- }
-
- /**
- * Normal, positive outcome (This is not a PSR-3 level)
- *
- * @param string $string
- * @param array $context
- */
- public function success($string, array $context = array())
- {
- $this->logMessage('success', $string, $context);
- }
-
- /**
- * @param string $level
- * @param string $message
- * @param array $context
- */
- protected function logMessage($level, $message, array $context = array())
- {
- // unknown level is always an error
- if (!isset($this->loglevel[$level])) $level = 'error';
-
- $info = $this->loglevel[$level];
- if (!$this->isLogLevelEnabled($level)) return; // no logging for this level
-
- $message = $this->interpolate($message, $context);
-
- // when colors are wanted, we also add the icon
- if ($this->colors->isEnabled()) {
- $message = $info['icon'] . $message;
- }
-
- $this->colors->ptln($message, $info['color'], $info['channel']);
- }
-
- /**
- * Interpolates context values into the message placeholders.
- *
- * @param $message
- * @param array $context
- * @return string
- */
- protected function interpolate($message, array $context = array())
- {
- // build a replacement array with braces around the context keys
- $replace = array();
- foreach ($context as $key => $val) {
- // check that the value can be casted to string
- if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
- $replace['{' . $key . '}'] = $val;
- }
- }
-
- // interpolate replacement values into the message and return
- return strtr((string)$message, $replace);
- }
-
- // endregion
-}
diff --git a/src/CLI.php b/src/CLI.php
deleted file mode 100644
index 217983a..0000000
--- a/src/CLI.php
+++ /dev/null
@@ -1,127 +0,0 @@
-
- * @license MIT
- */
-abstract class CLI extends Base
-{
- /**
- * System is unusable.
- *
- * @param string $message
- * @param array $context
- *
- * @return void
- */
- public function emergency($message, array $context = array())
- {
- $this->log('emergency', $message, $context);
- }
-
- /**
- * Action must be taken immediately.
- *
- * Example: Entire website down, database unavailable, etc. This should
- * trigger the SMS alerts and wake you up.
- *
- * @param string $message
- * @param array $context
- */
- public function alert($message, array $context = array())
- {
- $this->log('alert', $message, $context);
- }
-
- /**
- * Critical conditions.
- *
- * Example: Application component unavailable, unexpected exception.
- *
- * @param string $message
- * @param array $context
- */
- public function critical($message, array $context = array())
- {
- $this->log('critical', $message, $context);
- }
-
- /**
- * Runtime errors that do not require immediate action but should typically
- * be logged and monitored.
- *
- * @param string $message
- * @param array $context
- */
- public function error($message, array $context = array())
- {
- $this->log('error', $message, $context);
- }
-
- /**
- * Exceptional occurrences that are not errors.
- *
- * Example: Use of deprecated APIs, poor use of an API, undesirable things
- * that are not necessarily wrong.
- *
- * @param string $message
- * @param array $context
- */
- public function warning($message, array $context = array())
- {
- $this->log('warning', $message, $context);
- }
-
-
-
- /**
- * Normal but significant events.
- *
- * @param string $message
- * @param array $context
- */
- public function notice($message, array $context = array())
- {
- $this->log('notice', $message, $context);
- }
-
- /**
- * Interesting events.
- *
- * Example: User logs in, SQL logs.
- *
- * @param string $message
- * @param array $context
- */
- public function info($message, array $context = array())
- {
- $this->log('info', $message, $context);
- }
-
- /**
- * Detailed debug information.
- *
- * @param string $message
- * @param array $context
- */
- public function debug($message, array $context = array())
- {
- $this->log('debug', $message, $context);
- }
-
- /**
- * @param string $level
- * @param string $message
- * @param array $context
- */
- public function log($level, $message, array $context = array())
- {
- $this->logMessage($level, $message, $context);
- }
-}
diff --git a/src/Colors.php b/src/Colors.php
deleted file mode 100644
index dd1fd04..0000000
--- a/src/Colors.php
+++ /dev/null
@@ -1,177 +0,0 @@
-
- * @license MIT
- */
-class Colors
-{
- // these constants make IDE autocompletion easier, but color names can also be passed as strings
- const C_RESET = 'reset';
- const C_BLACK = 'black';
- const C_DARKGRAY = 'darkgray';
- const C_BLUE = 'blue';
- const C_LIGHTBLUE = 'lightblue';
- const C_GREEN = 'green';
- const C_LIGHTGREEN = 'lightgreen';
- const C_CYAN = 'cyan';
- const C_LIGHTCYAN = 'lightcyan';
- const C_RED = 'red';
- const C_LIGHTRED = 'lightred';
- const C_PURPLE = 'purple';
- const C_LIGHTPURPLE = 'lightpurple';
- const C_BROWN = 'brown';
- const C_YELLOW = 'yellow';
- const C_LIGHTGRAY = 'lightgray';
- const C_WHITE = 'white';
-
- // Regex pattern to match color codes
- const C_CODE_REGEX = "/(\33\[[0-9;]+m)/";
-
- /** @var array known color names */
- protected $colors = array(
- self::C_RESET => "\33[0m",
- self::C_BLACK => "\33[0;30m",
- self::C_DARKGRAY => "\33[1;30m",
- self::C_BLUE => "\33[0;34m",
- self::C_LIGHTBLUE => "\33[1;34m",
- self::C_GREEN => "\33[0;32m",
- self::C_LIGHTGREEN => "\33[1;32m",
- self::C_CYAN => "\33[0;36m",
- self::C_LIGHTCYAN => "\33[1;36m",
- self::C_RED => "\33[0;31m",
- self::C_LIGHTRED => "\33[1;31m",
- self::C_PURPLE => "\33[0;35m",
- self::C_LIGHTPURPLE => "\33[1;35m",
- self::C_BROWN => "\33[0;33m",
- self::C_YELLOW => "\33[1;33m",
- self::C_LIGHTGRAY => "\33[0;37m",
- self::C_WHITE => "\33[1;37m",
- );
-
- /** @var bool should colors be used? */
- protected $enabled = true;
-
- /**
- * Constructor
- *
- * Tries to disable colors for non-terminals
- */
- public function __construct()
- {
- if (function_exists('posix_isatty') && !posix_isatty(STDOUT)) {
- $this->enabled = false;
- return;
- }
- if (!getenv('TERM')) {
- $this->enabled = false;
- return;
- }
- if (getenv('NO_COLOR')) { // https://no-color.org/
- $this->enabled = false;
- return;
- }
- }
-
- /**
- * enable color output
- */
- public function enable()
- {
- $this->enabled = true;
- }
-
- /**
- * disable color output
- */
- public function disable()
- {
- $this->enabled = false;
- }
-
- /**
- * @return bool is color support enabled?
- */
- public function isEnabled()
- {
- return $this->enabled;
- }
-
- /**
- * Convenience function to print a line in a given color
- *
- * @param string $line the line to print, a new line is added automatically
- * @param string $color one of the available color names
- * @param resource $channel file descriptor to write to
- *
- * @throws Exception
- */
- public function ptln($line, $color, $channel = STDOUT)
- {
- $this->set($color, $channel);
- fwrite($channel, rtrim($line) . "\n");
- $this->reset($channel);
- }
-
- /**
- * Returns the given text wrapped in the appropriate color and reset code
- *
- * @param string $text string to wrap
- * @param string $color one of the available color names
- * @return string the wrapped string
- * @throws Exception
- */
- public function wrap($text, $color)
- {
- return $this->getColorCode($color) . $text . $this->getColorCode('reset');
- }
-
- /**
- * Gets the appropriate terminal code for the given color
- *
- * @param string $color one of the available color names
- * @return string color code
- * @throws Exception
- */
- public function getColorCode($color)
- {
- if (!$this->enabled) {
- return '';
- }
- if (!isset($this->colors[$color])) {
- throw new Exception("No such color $color");
- }
-
- return $this->colors[$color];
- }
-
- /**
- * Set the given color for consecutive output
- *
- * @param string $color one of the supported color names
- * @param resource $channel file descriptor to write to
- * @throws Exception
- */
- public function set($color, $channel = STDOUT)
- {
- fwrite($channel, $this->getColorCode($color));
- }
-
- /**
- * reset the terminal color
- *
- * @param resource $channel file descriptor to write to
- *
- * @throws Exception
- */
- public function reset($channel = STDOUT)
- {
- $this->set('reset', $channel);
- }
-}
diff --git a/src/Exception.php b/src/Exception.php
deleted file mode 100644
index 4d24d58..0000000
--- a/src/Exception.php
+++ /dev/null
@@ -1,35 +0,0 @@
-
- * @license MIT
- */
-class Exception extends \RuntimeException
-{
- const E_ANY = -1; // no error code specified
- const E_UNKNOWN_OPT = 1; //Unrecognized option
- const E_OPT_ARG_REQUIRED = 2; //Option requires argument
- const E_OPT_ARG_DENIED = 3; //Option not allowed argument
- const E_OPT_ABIGUOUS = 4; //Option abiguous
- const E_ARG_READ = 5; //Could not read argv
-
- /**
- * @param string $message The Exception message to throw.
- * @param int $code The Exception code
- * @param \Exception $previous The previous exception used for the exception chaining.
- */
- public function __construct($message = "", $code = 0, \Exception $previous = null)
- {
- if (!$code) {
- $code = self::E_ANY;
- }
- parent::__construct($message, $code, $previous);
- }
-}
diff --git a/src/Options.php b/src/Options.php
deleted file mode 100644
index 5ee6b69..0000000
--- a/src/Options.php
+++ /dev/null
@@ -1,504 +0,0 @@
-
- * @license MIT
- */
-class Options
-{
- /** @var array keeps the list of options to parse */
- protected $setup;
-
- /** @var array store parsed options */
- protected $options = array();
-
- /** @var string current parsed command if any */
- protected $command = '';
-
- /** @var array passed non-option arguments */
- protected $args = array();
-
- /** @var string the executed script */
- protected $bin;
-
- /** @var Colors for colored help output */
- protected $colors;
-
- /** @var string newline used for spacing help texts */
- protected $newline = "\n";
-
- /**
- * Constructor
- *
- * @param Colors $colors optional configured color object
- * @throws Exception when arguments can't be read
- */
- public function __construct(Colors $colors = null)
- {
- if (!is_null($colors)) {
- $this->colors = $colors;
- } else {
- $this->colors = new Colors();
- }
-
- $this->setup = array(
- '' => array(
- 'opts' => array(),
- 'args' => array(),
- 'help' => '',
- 'commandhelp' => 'This tool accepts a command as first parameter as outlined below:'
- )
- ); // default command
-
- $this->args = $this->readPHPArgv();
- $this->bin = basename(array_shift($this->args));
-
- $this->options = array();
- }
-
- /**
- * Gets the bin value
- */
- public function getBin()
- {
- return $this->bin;
- }
-
- /**
- * Sets the help text for the tool itself
- *
- * @param string $help
- */
- public function setHelp($help)
- {
- $this->setup['']['help'] = $help;
- }
-
- /**
- * Sets the help text for the tools commands itself
- *
- * @param string $help
- */
- public function setCommandHelp($help)
- {
- $this->setup['']['commandhelp'] = $help;
- }
-
- /**
- * Use a more compact help screen with less new lines
- *
- * @param bool $set
- */
- public function useCompactHelp($set = true)
- {
- $this->newline = $set ? '' : "\n";
- }
-
- /**
- * Register the names of arguments for help generation and number checking
- *
- * This has to be called in the order arguments are expected
- *
- * @param string $arg argument name (just for help)
- * @param string $help help text
- * @param bool $required is this a required argument
- * @param string $command if theses apply to a sub command only
- * @throws Exception
- */
- public function registerArgument($arg, $help, $required = true, $command = '')
- {
- if (!isset($this->setup[$command])) {
- throw new Exception("Command $command not registered");
- }
-
- $this->setup[$command]['args'][] = array(
- 'name' => $arg,
- 'help' => $help,
- 'required' => $required
- );
- }
-
- /**
- * This registers a sub command
- *
- * Sub commands have their own options and use their own function (not main()).
- *
- * @param string $command
- * @param string $help
- * @throws Exception
- */
- public function registerCommand($command, $help)
- {
- if (isset($this->setup[$command])) {
- throw new Exception("Command $command already registered");
- }
-
- $this->setup[$command] = array(
- 'opts' => array(),
- 'args' => array(),
- 'help' => $help
- );
-
- }
-
- /**
- * Register an option for option parsing and help generation
- *
- * @param string $long multi character option (specified with --)
- * @param string $help help text for this option
- * @param string|null $short one character option (specified with -)
- * @param bool|string $needsarg does this option require an argument? give it a name here
- * @param string $command what command does this option apply to
- * @throws Exception
- */
- public function registerOption($long, $help, $short = null, $needsarg = false, $command = '')
- {
- if (!isset($this->setup[$command])) {
- throw new Exception("Command $command not registered");
- }
-
- $this->setup[$command]['opts'][$long] = array(
- 'needsarg' => $needsarg,
- 'help' => $help,
- 'short' => $short
- );
-
- if ($short) {
- if (strlen($short) > 1) {
- throw new Exception("Short options should be exactly one ASCII character");
- }
-
- $this->setup[$command]['short'][$short] = $long;
- }
- }
-
- /**
- * Checks the actual number of arguments against the required number
- *
- * Throws an exception if arguments are missing.
- *
- * This is run from CLI automatically and usually does not need to be called directly
- *
- * @throws Exception
- */
- public function checkArguments()
- {
- $argc = count($this->args);
-
- $req = 0;
- foreach ($this->setup[$this->command]['args'] as $arg) {
- if (!$arg['required']) {
- break;
- } // last required arguments seen
- $req++;
- }
-
- if ($req > $argc) {
- throw new Exception("Not enough arguments", Exception::E_OPT_ARG_REQUIRED);
- }
- }
-
- /**
- * Parses the given arguments for known options and command
- *
- * The given $args array should NOT contain the executed file as first item anymore! The $args
- * array is stripped from any options and possible command. All found otions can be accessed via the
- * getOpt() function
- *
- * Note that command options will overwrite any global options with the same name
- *
- * This is run from CLI automatically and usually does not need to be called directly
- *
- * @throws Exception
- */
- public function parseOptions()
- {
- $non_opts = array();
-
- $argc = count($this->args);
- for ($i = 0; $i < $argc; $i++) {
- $arg = $this->args[$i];
-
- // The special element '--' means explicit end of options. Treat the rest of the arguments as non-options
- // and end the loop.
- if ($arg == '--') {
- $non_opts = array_merge($non_opts, array_slice($this->args, $i + 1));
- break;
- }
-
- // '-' is stdin - a normal argument
- if ($arg == '-') {
- $non_opts = array_merge($non_opts, array_slice($this->args, $i));
- break;
- }
-
- // first non-option
- if ($arg[0] != '-') {
- $non_opts = array_merge($non_opts, array_slice($this->args, $i));
- break;
- }
-
- // long option
- if (strlen($arg) > 1 && $arg[1] === '-') {
- $arg = explode('=', substr($arg, 2), 2);
- $opt = array_shift($arg);
- $val = array_shift($arg);
-
- if (!isset($this->setup[$this->command]['opts'][$opt])) {
- throw new Exception("No such option '$opt'", Exception::E_UNKNOWN_OPT);
- }
-
- // argument required?
- if ($this->setup[$this->command]['opts'][$opt]['needsarg']) {
- if (is_null($val) && $i + 1 < $argc && !preg_match('/^--?[\w]/', $this->args[$i + 1])) {
- $val = $this->args[++$i];
- }
- if (is_null($val)) {
- throw new Exception("Option $opt requires an argument",
- Exception::E_OPT_ARG_REQUIRED);
- }
- $this->options[$opt] = $val;
- } else {
- $this->options[$opt] = true;
- }
-
- continue;
- }
-
- // short option
- $opt = substr($arg, 1);
- if (!isset($this->setup[$this->command]['short'][$opt])) {
- throw new Exception("No such option $arg", Exception::E_UNKNOWN_OPT);
- } else {
- $opt = $this->setup[$this->command]['short'][$opt]; // store it under long name
- }
-
- // argument required?
- if ($this->setup[$this->command]['opts'][$opt]['needsarg']) {
- $val = null;
- if ($i + 1 < $argc && !preg_match('/^--?[\w]/', $this->args[$i + 1])) {
- $val = $this->args[++$i];
- }
- if (is_null($val)) {
- throw new Exception("Option $arg requires an argument",
- Exception::E_OPT_ARG_REQUIRED);
- }
- $this->options[$opt] = $val;
- } else {
- $this->options[$opt] = true;
- }
- }
-
- // parsing is now done, update args array
- $this->args = $non_opts;
-
- // if not done yet, check if first argument is a command and reexecute argument parsing if it is
- if (!$this->command && $this->args && isset($this->setup[$this->args[0]])) {
- // it is a command!
- $this->command = array_shift($this->args);
- $this->parseOptions(); // second pass
- }
- }
-
- /**
- * Get the value of the given option
- *
- * Please note that all options are accessed by their long option names regardless of how they were
- * specified on commandline.
- *
- * Can only be used after parseOptions() has been run
- *
- * @param mixed $option
- * @param bool|string $default what to return if the option was not set
- * @return bool|string|string[]
- */
- public function getOpt($option = null, $default = false)
- {
- if ($option === null) {
- return $this->options;
- }
-
- if (isset($this->options[$option])) {
- return $this->options[$option];
- }
- return $default;
- }
-
- /**
- * Return the found command if any
- *
- * @return string
- */
- public function getCmd()
- {
- return $this->command;
- }
-
- /**
- * Get all the arguments passed to the script
- *
- * This will not contain any recognized options or the script name itself
- *
- * @return array
- */
- public function getArgs()
- {
- return $this->args;
- }
-
- /**
- * Builds a help screen from the available options. You may want to call it from -h or on error
- *
- * @return string
- *
- * @throws Exception
- */
- public function help()
- {
- $tf = new TableFormatter($this->colors);
- $text = '';
-
- $hascommands = (count($this->setup) > 1);
- $commandhelp = $this->setup['']["commandhelp"];
-
- foreach ($this->setup as $command => $config) {
- $hasopts = (bool)$this->setup[$command]['opts'];
- $hasargs = (bool)$this->setup[$command]['args'];
-
- // usage or command syntax line
- if (!$command) {
- $text .= $this->colors->wrap('USAGE:', Colors::C_BROWN);
- $text .= "\n";
- $text .= ' ' . $this->bin;
- $mv = 2;
- } else {
- $text .= $this->newline;
- $text .= $this->colors->wrap(' ' . $command, Colors::C_PURPLE);
- $mv = 4;
- }
-
- if ($hasopts) {
- $text .= ' ' . $this->colors->wrap('', Colors::C_GREEN);
- }
-
- if (!$command && $hascommands) {
- $text .= ' ' . $this->colors->wrap(' ...', Colors::C_PURPLE);
- }
-
- foreach ($this->setup[$command]['args'] as $arg) {
- $out = $this->colors->wrap('<' . $arg['name'] . '>', Colors::C_CYAN);
-
- if (!$arg['required']) {
- $out = '[' . $out . ']';
- }
- $text .= ' ' . $out;
- }
- $text .= $this->newline;
-
- // usage or command intro
- if ($this->setup[$command]['help']) {
- $text .= "\n";
- $text .= $tf->format(
- array($mv, '*'),
- array('', $this->setup[$command]['help'] . $this->newline)
- );
- }
-
- // option description
- if ($hasopts) {
- if (!$command) {
- $text .= "\n";
- $text .= $this->colors->wrap('OPTIONS:', Colors::C_BROWN);
- }
- $text .= "\n";
- foreach ($this->setup[$command]['opts'] as $long => $opt) {
-
- $name = '';
- if ($opt['short']) {
- $name .= '-' . $opt['short'];
- if ($opt['needsarg']) {
- $name .= ' <' . $opt['needsarg'] . '>';
- }
- $name .= ', ';
- }
- $name .= "--$long";
- if ($opt['needsarg']) {
- $name .= ' <' . $opt['needsarg'] . '>';
- }
-
- $text .= $tf->format(
- array($mv, '30%', '*'),
- array('', $name, $opt['help']),
- array('', 'green', '')
- );
- $text .= $this->newline;
- }
- }
-
- // argument description
- if ($hasargs) {
- if (!$command) {
- $text .= "\n";
- $text .= $this->colors->wrap('ARGUMENTS:', Colors::C_BROWN);
- }
- $text .= $this->newline;
- foreach ($this->setup[$command]['args'] as $arg) {
- $name = '<' . $arg['name'] . '>';
-
- $text .= $tf->format(
- array($mv, '30%', '*'),
- array('', $name, $arg['help']),
- array('', 'cyan', '')
- );
- }
- }
-
- // head line and intro for following command documentation
- if (!$command && $hascommands) {
- $text .= "\n";
- $text .= $this->colors->wrap('COMMANDS:', Colors::C_BROWN);
- $text .= "\n";
- $text .= $tf->format(
- array($mv, '*'),
- array('', $commandhelp)
- );
- $text .= $this->newline;
- }
- }
-
- return $text;
- }
-
- /**
- * Safely read the $argv PHP array across different PHP configurations.
- * Will take care on register_globals and register_argc_argv ini directives
- *
- * @throws Exception
- * @return array the $argv PHP array or PEAR error if not registered
- */
- private function readPHPArgv()
- {
- global $argv;
- if (!is_array($argv)) {
- if (!@is_array($_SERVER['argv'])) {
- if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
- throw new Exception(
- "Could not read cmd args (register_argc_argv=Off?)",
- Exception::E_ARG_READ
- );
- }
- return $GLOBALS['HTTP_SERVER_VARS']['argv'];
- }
- return $_SERVER['argv'];
- }
- return $argv;
- }
-}
-
diff --git a/src/PSR3CLI.php b/src/PSR3CLI.php
deleted file mode 100644
index 0078cd7..0000000
--- a/src/PSR3CLI.php
+++ /dev/null
@@ -1,16 +0,0 @@
-logMessage($level, $message, $context);
- }
-}
diff --git a/src/TableFormatter.php b/src/TableFormatter.php
deleted file mode 100644
index 20d71c6..0000000
--- a/src/TableFormatter.php
+++ /dev/null
@@ -1,338 +0,0 @@
-
- * @license MIT
- */
-class TableFormatter
-{
- /** @var string border between columns */
- protected $border = ' ';
-
- /** @var int the terminal width */
- protected $max = 74;
-
- /** @var Colors for coloring output */
- protected $colors;
-
- /**
- * TableFormatter constructor.
- *
- * @param Colors|null $colors
- */
- public function __construct(Colors $colors = null)
- {
- // try to get terminal width
- $width = $this->getTerminalWidth();
- if ($width) {
- $this->max = $width - 1;
- }
-
- if ($colors) {
- $this->colors = $colors;
- } else {
- $this->colors = new Colors();
- }
- }
-
- /**
- * The currently set border (defaults to ' ')
- *
- * @return string
- */
- public function getBorder()
- {
- return $this->border;
- }
-
- /**
- * Set the border. The border is set between each column. Its width is
- * added to the column widths.
- *
- * @param string $border
- */
- public function setBorder($border)
- {
- $this->border = $border;
- }
-
- /**
- * Width of the terminal in characters
- *
- * initially autodetected
- *
- * @return int
- */
- public function getMaxWidth()
- {
- return $this->max;
- }
-
- /**
- * Set the width of the terminal to assume (in characters)
- *
- * @param int $max
- */
- public function setMaxWidth($max)
- {
- $this->max = $max;
- }
-
- /**
- * Tries to figure out the width of the terminal
- *
- * @return int terminal width, 0 if unknown
- */
- protected function getTerminalWidth()
- {
- // from environment
- if (isset($_SERVER['COLUMNS'])) return (int)$_SERVER['COLUMNS'];
-
- // via tput
- $process = proc_open('tput cols', array(
- 1 => array('pipe', 'w'),
- 2 => array('pipe', 'w'),
- ), $pipes);
- $width = (int)stream_get_contents($pipes[1]);
- proc_close($process);
-
- return $width;
- }
-
- /**
- * Takes an array with dynamic column width and calculates the correct width
- *
- * Column width can be given as fixed char widths, percentages and a single * width can be given
- * for taking the remaining available space. When mixing percentages and fixed widths, percentages
- * refer to the remaining space after allocating the fixed width
- *
- * @param array $columns
- * @return int[]
- * @throws Exception
- */
- protected function calculateColLengths($columns)
- {
- $idx = 0;
- $border = $this->strlen($this->border);
- $fixed = (count($columns) - 1) * $border; // borders are used already
- $fluid = -1;
-
- // first pass for format check and fixed columns
- foreach ($columns as $idx => $col) {
- // handle fixed columns
- if ((string)intval($col) === (string)$col) {
- $fixed += $col;
- continue;
- }
- // check if other colums are using proper units
- if (substr($col, -1) == '%') {
- continue;
- }
- if ($col == '*') {
- // only one fluid
- if ($fluid < 0) {
- $fluid = $idx;
- continue;
- } else {
- throw new Exception('Only one fluid column allowed!');
- }
- }
- throw new Exception("unknown column format $col");
- }
-
- $alloc = $fixed;
- $remain = $this->max - $alloc;
-
- // second pass to handle percentages
- foreach ($columns as $idx => $col) {
- if (substr($col, -1) != '%') {
- continue;
- }
- $perc = floatval($col);
-
- $real = (int)floor(($perc * $remain) / 100);
-
- $columns[$idx] = $real;
- $alloc += $real;
- }
-
- $remain = $this->max - $alloc;
- if ($remain < 0) {
- throw new Exception("Wanted column widths exceed available space");
- }
-
- // assign remaining space
- if ($fluid < 0) {
- $columns[$idx] += ($remain); // add to last column
- } else {
- $columns[$fluid] = $remain;
- }
-
- return $columns;
- }
-
- /**
- * Displays text in multiple word wrapped columns
- *
- * @param int[] $columns list of column widths (in characters, percent or '*')
- * @param string[] $texts list of texts for each column
- * @param array $colors A list of color names to use for each column. use empty string for default
- * @return string
- * @throws Exception
- */
- public function format($columns, $texts, $colors = array())
- {
- $columns = $this->calculateColLengths($columns);
-
- $wrapped = array();
- $maxlen = 0;
-
- foreach ($columns as $col => $width) {
- $wrapped[$col] = explode("\n", $this->wordwrap($texts[$col], $width, "\n", true));
- $len = count($wrapped[$col]);
- if ($len > $maxlen) {
- $maxlen = $len;
- }
-
- }
-
- $last = count($columns) - 1;
- $out = '';
- for ($i = 0; $i < $maxlen; $i++) {
- foreach ($columns as $col => $width) {
- if (isset($wrapped[$col][$i])) {
- $val = $wrapped[$col][$i];
- } else {
- $val = '';
- }
- $chunk = $this->pad($val, $width);
- if (isset($colors[$col]) && $colors[$col]) {
- $chunk = $this->colors->wrap($chunk, $colors[$col]);
- }
- $out .= $chunk;
-
- // border
- if ($col != $last) {
- $out .= $this->border;
- }
- }
- $out .= "\n";
- }
- return $out;
-
- }
-
- /**
- * Pad the given string to the correct length
- *
- * @param string $string
- * @param int $len
- * @return string
- */
- protected function pad($string, $len)
- {
- $strlen = $this->strlen($string);
- if ($strlen > $len) return $string;
-
- $pad = $len - $strlen;
- return $string . str_pad('', $pad, ' ');
- }
-
- /**
- * Measures char length in UTF-8 when possible
- *
- * @param $string
- * @return int
- */
- protected function strlen($string)
- {
- // don't count color codes
- $string = preg_replace("/\33\\[\\d+(;\\d+)?m/", '', $string);
-
- if (function_exists('mb_strlen')) {
- return mb_strlen($string, 'utf-8');
- }
-
- return strlen($string);
- }
-
- /**
- * @param string $string
- * @param int $start
- * @param int|null $length
- * @return string
- */
- protected function substr($string, $start = 0, $length = null)
- {
- if (function_exists('mb_substr')) {
- return mb_substr($string, $start, $length);
- } else {
- // mb_substr() treats $length differently than substr()
- if ($length) {
- return substr($string, $start, $length);
- } else {
- return substr($string, $start);
- }
- }
- }
-
- /**
- * @param string $str
- * @param int $width
- * @param string $break
- * @param bool $cut
- * @return string
- * @link http://stackoverflow.com/a/4988494
- */
- protected function wordwrap($str, $width = 75, $break = "\n", $cut = false)
- {
- $lines = explode($break, $str);
- $color_reset = $this->colors->getColorCode(Colors::C_RESET);
- foreach ($lines as &$line) {
- $line = rtrim($line);
- if ($this->strlen($line) <= $width) {
- continue;
- }
- $words = explode(' ', $line);
- $line = '';
- $actual = '';
- $color = '';
- foreach ($words as $word) {
- if (preg_match_all(Colors::C_CODE_REGEX, $word, $color_codes) ) {
- # Word contains color codes
- foreach ($color_codes[0] as $code) {
- if ($code == $color_reset) {
- $color = '';
- } else {
- # Remember color so we can reapply it after a line break
- $color = $code;
- }
- }
- }
- if ($this->strlen($actual . $word) <= $width) {
- $actual .= $word . ' ';
- } else {
- if ($actual != '') {
- $line .= rtrim($actual) . $break;
- }
- $actual = $color . $word;
- if ($cut) {
- while ($this->strlen($actual) > $width) {
- $line .= $this->substr($actual, 0, $width) . $break;
- $actual = $color . $this->substr($actual, $width);
- }
- }
- $actual .= ' ';
- }
- }
- $line .= trim($actual);
- }
- return implode($break, $lines);
- }
-}
diff --git a/tests/LogLevelTest.php b/tests/LogLevelTest.php
deleted file mode 100644
index 1e5fcc8..0000000
--- a/tests/LogLevelTest.php
+++ /dev/null
@@ -1,91 +0,0 @@
-setLogLevel($level);
- foreach ($enabled as $e) {
- $this->assertTrue($cli->isLogLevelEnabled($e), "$e is not enabled but should be");
- }
- foreach ($disabled as $d) {
- $this->assertFalse($cli->isLogLevelEnabled($d), "$d is enabled but should not be");
- }
- }
-
-
-}
diff --git a/tests/OptionsTest.php b/tests/OptionsTest.php
deleted file mode 100644
index b8f2782..0000000
--- a/tests/OptionsTest.php
+++ /dev/null
@@ -1,90 +0,0 @@
-registerOption('exclude', 'exclude files', 'x', 'file');
-
- $options->args = array($option, $value, $argument);
- $options->parseOptions();
-
- $this->assertEquals($value, $options->getOpt('exclude'));
- $this->assertEquals(array($argument), $options->args);
- $this->assertFalse($options->getOpt('nothing'));
- }
-
- /**
- * @return array
- */
- public function optionDataProvider() {
- return array(
- array('-x', 'foo', 'bang'),
- array('--exclude', 'foo', 'bang'),
- array('-x', 'foo-bar', 'bang'),
- array('--exclude', 'foo-bar', 'bang'),
- array('-x', 'foo', 'bang--bang'),
- array('--exclude', 'foo', 'bang--bang'),
- );
- }
-
- function test_simplelong2()
- {
- $options = new Options();
- $options->registerOption('exclude', 'exclude files', 'x', 'file');
-
- $options->args = array('--exclude=foo', 'bang');
- $options->parseOptions();
-
- $this->assertEquals('foo', $options->getOpt('exclude'));
- $this->assertEquals(array('bang'), $options->args);
- $this->assertFalse($options->getOpt('nothing'));
- }
-
- function test_complex()
- {
- $options = new Options();
-
- $options->registerOption('plugins', 'run on plugins only', 'p');
- $options->registerCommand('status', 'display status info');
- $options->registerOption('long', 'display long lines', 'l', false, 'status');
-
- $options->args = array('-p', 'status', '--long', 'foo');
- $options->parseOptions();
-
- $this->assertEquals('status', $options->getCmd());
- $this->assertTrue($options->getOpt('plugins'));
- $this->assertTrue($options->getOpt('long'));
- $this->assertEquals(array('foo'), $options->args);
- }
-
- function test_commandhelp()
- {
- $options = new Options();
- $options->registerCommand('cmd', 'a command');
- $this->assertStringContainsString('accepts a command as first parameter', $options->help());
-
- $options->setCommandHelp('foooooobaar');
- $this->assertStringNotContainsString('accepts a command as first parameter', $options->help());
- $this->assertStringContainsString('foooooobaar', $options->help());
- }
-}
diff --git a/tests/TableFormatterTest.php b/tests/TableFormatterTest.php
deleted file mode 100644
index 3b1860a..0000000
--- a/tests/TableFormatterTest.php
+++ /dev/null
@@ -1,188 +0,0 @@
-setMaxWidth($max);
- $tf->setBorder($border);
-
- $result = $tf->calculateColLengths($input);
-
- $this->assertEquals($max, array_sum($result) + (strlen($border) * (count($input) - 1)));
- $this->assertEquals($expect, $result);
-
- }
-
- /**
- * Check wrapping
- */
- public function test_wrap()
- {
- $text = "this is a long string something\n" .
- "123456789012345678901234567890";
-
- $expt = "this is a long\n" .
- "string\n" .
- "something\n" .
- "123456789012345\n" .
- "678901234567890";
-
- $tf = new TableFormatter();
- $this->assertEquals($expt, $tf->wordwrap($text, 15, "\n", true));
-
- }
-
- public function test_length()
- {
- $text = "this is hรคppy โบ";
- $expect = "$text |test";
-
- $tf = new TableFormatter();
- $tf->setBorder('|');
- $result = $tf->format(array(20, '*'), array($text, 'test'));
-
- $this->assertEquals($expect, trim($result));
- }
-
- public function test_colorlength()
- {
- $color = new Colors();
-
- $text = 'this is ' . $color->wrap('green', Colors::C_GREEN);
- $expect = "$text |test";
-
- $tf = new TableFormatter();
- $tf->setBorder('|');
- $result = $tf->format(array(20, '*'), array($text, 'test'));
-
- $this->assertEquals($expect, trim($result));
- }
-
- public function test_onewrap()
- {
- $col1 = "test\nwrap";
- $col2 = "test";
-
- $expect = "test |test \n" .
- "wrap | \n";
-
- $tf = new TableFormatter();
- $tf->setMaxWidth(11);
- $tf->setBorder('|');
-
- $result = $tf->format(array(5, '*'), array($col1, $col2));
- $this->assertEquals($expect, $result);
- }
-
- /**
- * Test that colors are correctly applied when text is wrapping across lines.
- *
- * @dataProvider colorwrapProvider
- */
- public function test_colorwrap($text, $expect)
- {
- $tf = new TableFormatter();
- $tf->setMaxWidth(15);
-
- $this->assertEquals($expect, $tf->format(array('*'), array($text)));
- }
-
- /**
- * Data provider for test_colorwrap.
- *
- * @return array[]
- */
- public function colorwrapProvider()
- {
- $color = new Colors();
- $cyan = $color->getColorCode(Colors::C_CYAN);
- $reset = $color->getColorCode(Colors::C_RESET);
- $wrap = function ($str) use ($color) {
- return $color->wrap($str, Colors::C_CYAN);
- };
-
- return array(
- 'color word line 1' => array(
- "This is ". $wrap("cyan") . " text wrapping",
- "This is {$cyan}cyan{$reset} \ntext wrapping \n",
- ),
- 'color word line 2' => array(
- "This is text ". $wrap("cyan") . " wrapping",
- "This is text \n{$cyan}cyan{$reset} wrapping \n",
- ),
- 'color across lines' => array(
- "This is ". $wrap("cyan text") . " wrapping",
- "This is {$cyan}cyan \ntext{$reset} wrapping \n",
- ),
- 'color across lines until end' => array(
- "This is ". $wrap("cyan text wrapping"),
- "This is {$cyan}cyan \n{$cyan}text wrapping{$reset} \n",
- ),
- );
- }
-}
diff --git a/tree.html b/tree.html
new file mode 100644
index 0000000..8e93ca2
--- /dev/null
+++ b/tree.html
@@ -0,0 +1,165 @@
+
+
+
+
+
+ Tree
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Tree
+
+
Classes
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Exceptions
+
+
+
+ Exception
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+