diff --git a/Yuba.php b/Yuba.php
index 93c87ea..be6b387 100755
--- a/Yuba.php
+++ b/Yuba.php
@@ -3,11 +3,10 @@
// Yuba
// //
//////////////////////////////////////////
-$version = "0.7.5";
+$version = "0.7.6";
ini_set('memory_limit', '4096M');
date_default_timezone_set("America/Los_Angeles");
-$time_start = microtime(true);
// Includes & Prefs
//////////////////////////////////////////
@@ -16,6 +15,8 @@ require("functions.php");
require("filetypes.php");
$wopt_noprofile = 1;
+$wopt_steps = 9;
+$wopt_currstep = 1;
$p = unserialize(file_get_contents("prefs.php"));
@@ -97,9 +98,9 @@ echo "Gathering system info...\n";
$host = gethostname();
$disks = shell_exec("diskutil list 2>&1");
$df = shell_exec("df 2>&1");
-$df_volume = shell_exec("df ".$zpath." | tail -n 1 | rev | cut -d' ' -f1 | rev");
-$df_device = shell_exec("df ".$zpath." | tail -n 1 | cut -d' ' -f1");
-$mdutil = shell_exec("mdutil -s ".$df_volume);
+$df_volume = shell_exec("df ".escapeshellarg($zpath)." | tail -n 1 | rev | cut -d' ' -f1 | rev");
+$df_device = shell_exec("df ".escapeshellarg($zpath)." | tail -n 1 | cut -d' ' -f1");
+$mdutil = shell_exec("mdutil -s ".escapeshellarg($df_volume));
if (strpos($mdutil,"disabled")) {
echo "Warning: spotlight indexing is disabled\n";
$p['spotlight'] = false;
@@ -343,7 +344,7 @@ $family = array();
$fids = array();
$noread = array();
-echo ProgressBar::start($passed_total,"Prescan");
+echo ProgressBar::start($passed_total,"Prescan (".stepString().")");
foreach ($files as $splFileInfo) {
@@ -397,7 +398,7 @@ foreach ($files as $splFileInfo) {
//$family[$pkey]['children'][] = $key;
$family[$pkey]['children'][] = $i+1;
- echo ProgressBar::next();
+ echo ProgressBar::next(true);
$i++;
}
@@ -407,7 +408,7 @@ echo ProgressBar::finish();
// Thow permissions error
if (count($noread)) {
- "Current user (".posix_getuid().") does not have read access to the following files:";
+ echo "Current user (".posix_getuid().") does not have read access to the following files:\n";
foreach ($noread as $file) {
echo $file."\n";
}
@@ -471,15 +472,17 @@ $stmt .= "ignored=".$ignored.", ";
$stmt .= "dupes=".($dupecount ? $dupecount : 0);
$dbo->exec($stmt);
+$wopt_currstep++;
+
// Contents
//////////////////////////////////////////
if ($p['contents']) {
echo "DO CONTENTS HERE\n";
- // make a dir in the bundle called contents (similar to db)
+ // make a dir in the bundle called contents (similar to thumbs)
// match files smaller than x and with file extension of txt etc
- // copy files to hash dirs in bundle (like db dir)
+ // copy files to hash dirs in bundle (like thumbs dir)
}
@@ -488,7 +491,7 @@ if ($p['contents']) {
if ($p['thumbs']) {
- echo ProgressBar::start(count($fx),"Generating thumbnails");
+ echo ProgressBar::start(count($fx),"Generating thumbnails (".stepString().")");
foreach ($fx as $array) {
@@ -566,7 +569,7 @@ if ($p['thumbs']) {
if ($p['meta']) {
- echo ProgressBar::start(count($fx),"Collecting external metadata...");
+ echo ProgressBar::start(count($fx),"Collecting external metadata (".stepString().")");
foreach ($fx as $array) {
@@ -580,19 +583,24 @@ if ($p['meta']) {
echo ProgressBar::next("Not a media file: ".shortlabel($pathname));
continue;
}
-
+
if (in_array($ext, $e_files)) {
$check = $dbp->query("SELECT EXISTS(SELECT 1 FROM exiftool WHERE fid='".$fid."')")->fetch()[0];
if (!$check) {
- $rawexif = eval("return ".`$bin_exiftool -php $shellpath`);
- $stmt = $dbp->prepare("INSERT INTO exiftool VALUES (:fid, :tags)");
- $stmt->BindValue(":fid",$fid);
- $stmt->BindValue(":tags",serialize($rawexif[0]));
- $stmt->execute();
- $found = 0;
+ $arrstring = shell_exec($bin_exiftool." -php ".$shellpath);
+ // $rawexif = eval("return ".`$bin_exiftool -php $shellpath`);
+ // do an addtl check below to prevent "PHP Parse error: syntax error, unexpected end of file, expecting ';'"
+ if (substr($arrstring,0,5) == "Array") {
+ $rawexif = eval("return ".$arrstring);
+ $stmt = $dbp->prepare("INSERT INTO exiftool VALUES (:fid, :tags)");
+ $stmt->BindValue(":fid",$fid);
+ $stmt->BindValue(":tags",serialize($rawexif[0]));
+ $stmt->execute();
+ $found = 0;
+ }
}
}
-
+
if (in_array($ext, $m_files)) {
$check = $dbp->query("SELECT EXISTS(SELECT 1 FROM mediainfo WHERE fid='".$fid."')")->fetch()[0];
if (!$check) {
@@ -628,7 +636,7 @@ if ($p['hash']) {
$message = "Generating hashes for all files";
}
- echo ProgressBar::start(count($fx),$message);
+ echo ProgressBar::start(count($fx),$message." (".stepString().")");
foreach ($fx as $array) {
$fid = $array[0];
@@ -698,14 +706,15 @@ $p['spotlight'] = 1;
if ($p['spotlight']) {
- echo ProgressBar::start($passed_total,"Spotlight");
+ echo ProgressBar::start($passed_total,"Spotlight (".stepString().")");
$dbo->exec("CREATE TABLE mdls (".implode(",",$cbuild).")");
foreach ($files as $splFileInfo) {
- $pid = md5($splFileInfo->getPathname());
- $shellpath = escapeshellarg($splFileInfo->getPathname());
+ $path = $splFileInfo->getPathname();
+ $pid = md5($path);
+ $shellpath = escapeshellarg($path);
$mdls = shell_exec("mdls -plist - ".$shellpath." 2>&1");
if (substr_count(@$mdls,"\n") < 2) { continue; }
@@ -737,7 +746,7 @@ if ($p['spotlight']) {
}
$stmt->execute();
- echo ProgressBar::next();
+ echo ProgressBar::next(true);
}
@@ -798,12 +807,10 @@ $stmt->execute();
$j = 0;
-echo ProgressBar::start($passed_total,"Skimming");
+echo ProgressBar::start($passed_total, "Skimming");
foreach ($files as $splFileInfo) {
- echo "\n";
-
// DB
$stmt = $dbo->prepare("INSERT INTO files VALUES (:pid, :fid, :Pathname, :Path, :Filename, :Extension, :Type, :Size, :Inode, :Perms, :Owner, :ATime, :CTime, :MTime, :LinkTarget, :RealPath, :stat, :items, :newest, :gfi_type, :gfi_attr, :gfi_created, :has_exif, :has_mediainfo, :has_hash, :thumb_filename, :thumb_width, :thumb_height, :has_contents, :contents_filename)");
@@ -852,8 +859,6 @@ foreach ($files as $splFileInfo) {
$stmt->BindValue(":CTime",$stx[$j][2]);
}
-
- echo shortlabel($pathname,50);
// ------------------------------------------------ //
@@ -1005,8 +1010,7 @@ foreach ($files as $splFileInfo) {
}
- echo "\n";
- echo ProgressBar::next();
+ echo ProgressBar::next($filename);
$j++;
}
diff --git a/functions.php b/functions.php
index fdedac5..d823b91 100755
--- a/functions.php
+++ b/functions.php
@@ -5,11 +5,31 @@
class ProgressBar {
- protected static $done = 0;
- protected static $total = 0;
+ protected static $time_start;
+ protected static $time_remain;
+ protected static $time_update;
+ protected static $total;
+ protected static $done;
+ protected static $message;
public static function display($message = null) {
- $string = "PROGRESS:".floor((self::$done/self::$total)*100);
+
+ $progress = self::$done/self::$total;
+ $string = "PROGRESS:".round($progress*100,2);
+
+ if ($message === true) {
+ $message = self::$message;
+ }
+
+ if ($message) {
+ if (time()-self::$time_update) { // only update the remaining time every 1 seconds
+ $seconds = time() - self::$time_start;
+ self::$time_remain = floor($seconds/$progress)-$seconds;
+ self::$time_update = time();
+ }
+ $message = gmdate("H:i:s",self::$time_remain)." | ".$message;
+ }
+
if ($message) {
return "\n".$string."\n".$message;
} elseif (!strpos(__FILE__,".app")) {
@@ -17,10 +37,14 @@ class ProgressBar {
} else {
return "\n".$string;
}
+
}
public static function start($total, $message = null) {
+ self::$done = 0;
self::$total = $total;
+ self::$time_start = time();
+ self::$message = $message;
return $message."\n".self::display();
}
@@ -30,6 +54,8 @@ class ProgressBar {
}
public static function finish() {
+ global $wopt_currstep;
+ $wopt_currstep++;
self::$done = 0;
return "\n";
}
@@ -50,11 +76,14 @@ function getParents($zpath, $pathname) {
}
*/
-function stringPrint($string) {
- echo $string.@str_repeat(" ", (10-strlen($string)));
+function stepString() {
+ global $wopt_steps;
+ global $wopt_currstep;
+ return "Step ".$wopt_currstep." of ".$wopt_steps;
}
-function shortlabel($pathname, $max = 99, $pad = false) {
+
+function shortlabel($pathname, $max = 99) {
$basename = basename($pathname);
$suffix = "(...).".pathinfo($basename,PATHINFO_EXTENSION);
if (strlen($basename) > $max) {
@@ -62,9 +91,6 @@ function shortlabel($pathname, $max = 99, $pad = false) {
} else {
$return = $basename;
}
- if ($pad) {
- $return = $return.@str_repeat(" ", ($max-strlen($return)));
- }
return $return;
}
diff --git a/web/lib/debug.php b/web/lib/debug.php
index e9000b6..4597274 100644
--- a/web/lib/debug.php
+++ b/web/lib/debug.php
@@ -185,7 +185,7 @@
else
{
$collapse = !($level+1<=1 || $title===true || (is_string($title) && substr($title,0,1)=='*'));
- $collapse = false;
+ $collapse = false; // hack
$result .= "\n
\n
".($collapse?'+':'-')."
";
}
$result .= debug ($value, $title, $plain, $limit, $level+1);
diff --git a/web/lib/powerange/powerange.css b/web/lib/powerange/powerange.css
new file mode 100644
index 0000000..7aebfa7
--- /dev/null
+++ b/web/lib/powerange/powerange.css
@@ -0,0 +1,119 @@
+/**
+ *
+ * Main stylesheet for Powerange.
+ * http://abpetkov.github.io/powerange/
+ *
+ */
+
+/**
+ * Horizontal slider style (default).
+ */
+
+.range-bar {
+ background-color: #a9acb1;
+ border-radius: 15px;
+ display: block;
+ height: 4px;
+ position: relative;
+ width: 100%;
+}
+
+.range-quantity {
+ background-color: #017afd;
+ border-radius: 15px;
+ display: block;
+ height: 100%;
+ width: 0;
+}
+
+.range-handle {
+ background-color: #fff;
+ border-radius: 100%;
+ cursor: move;
+ height: 30px;
+ left: 0;
+ top: -13px;
+ position: absolute;
+ width: 30px;
+
+ -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
+}
+
+.range-min,
+.range-max {
+ color: #181819;
+ font-size: 12px;
+ height: 20px;
+ padding-top: 4px;
+ position: absolute;
+ text-align: center;
+ top: -9px;
+ width: 24px;
+}
+
+.range-min {
+ left: -30px;
+}
+
+.range-max {
+ right: -30px;
+}
+
+/**
+ * Vertical slider style.
+ */
+
+.vertical {
+ height: 100%;
+ width: 4px;
+}
+
+.vertical .range-quantity {
+ bottom: 0;
+ height: 0;
+ position: absolute;
+ width: 100%;
+}
+
+.vertical .range-handle {
+ bottom: 0;
+ left: -13px;
+ top: auto;
+}
+
+.vertical .range-min,
+.vertical .range-max {
+ left: -10px;
+ right: auto;
+ top: auto;
+}
+
+.vertical .range-min {
+ bottom: -30px;
+}
+
+.vertical .range-max {
+ top: -30px;
+}
+
+/**
+ * Style for disabling text selection on handle move.
+ */
+
+.unselectable {
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+/**
+ * Style for handle cursor on disabled slider.
+ */
+
+.range-disabled {
+ cursor: default;
+}
\ No newline at end of file
diff --git a/web/lib/powerange/powerange.js b/web/lib/powerange/powerange.js
new file mode 100644
index 0000000..be968e8
--- /dev/null
+++ b/web/lib/powerange/powerange.js
@@ -0,0 +1,1869 @@
+;(function(){
+
+/**
+ * Require the given path.
+ *
+ * @param {String} path
+ * @return {Object} exports
+ * @api public
+ */
+
+function require(path, parent, orig) {
+ var resolved = require.resolve(path);
+
+ // lookup failed
+ if (null == resolved) {
+ orig = orig || path;
+ parent = parent || 'root';
+ var err = new Error('Failed to require "' + orig + '" from "' + parent + '"');
+ err.path = orig;
+ err.parent = parent;
+ err.require = true;
+ throw err;
+ }
+
+ var module = require.modules[resolved];
+
+ // perform real require()
+ // by invoking the module's
+ // registered function
+ if (!module._resolving && !module.exports) {
+ var mod = {};
+ mod.exports = {};
+ mod.client = mod.component = true;
+ module._resolving = true;
+ module.call(this, mod.exports, require.relative(resolved), mod);
+ delete module._resolving;
+ module.exports = mod.exports;
+ }
+
+ return module.exports;
+}
+
+/**
+ * Registered modules.
+ */
+
+require.modules = {};
+
+/**
+ * Registered aliases.
+ */
+
+require.aliases = {};
+
+/**
+ * Resolve `path`.
+ *
+ * Lookup:
+ *
+ * - PATH/index.js
+ * - PATH.js
+ * - PATH
+ *
+ * @param {String} path
+ * @return {String} path or null
+ * @api private
+ */
+
+require.resolve = function(path) {
+ if (path.charAt(0) === '/') path = path.slice(1);
+
+ var paths = [
+ path,
+ path + '.js',
+ path + '.json',
+ path + '/index.js',
+ path + '/index.json'
+ ];
+
+ for (var i = 0; i < paths.length; i++) {
+ var path = paths[i];
+ if (require.modules.hasOwnProperty(path)) return path;
+ if (require.aliases.hasOwnProperty(path)) return require.aliases[path];
+ }
+};
+
+/**
+ * Normalize `path` relative to the current path.
+ *
+ * @param {String} curr
+ * @param {String} path
+ * @return {String}
+ * @api private
+ */
+
+require.normalize = function(curr, path) {
+ var segs = [];
+
+ if ('.' != path.charAt(0)) return path;
+
+ curr = curr.split('/');
+ path = path.split('/');
+
+ for (var i = 0; i < path.length; ++i) {
+ if ('..' == path[i]) {
+ curr.pop();
+ } else if ('.' != path[i] && '' != path[i]) {
+ segs.push(path[i]);
+ }
+ }
+
+ return curr.concat(segs).join('/');
+};
+
+/**
+ * Register module at `path` with callback `definition`.
+ *
+ * @param {String} path
+ * @param {Function} definition
+ * @api private
+ */
+
+require.register = function(path, definition) {
+ require.modules[path] = definition;
+};
+
+/**
+ * Alias a module definition.
+ *
+ * @param {String} from
+ * @param {String} to
+ * @api private
+ */
+
+require.alias = function(from, to) {
+ if (!require.modules.hasOwnProperty(from)) {
+ throw new Error('Failed to alias "' + from + '", it does not exist');
+ }
+ require.aliases[to] = from;
+};
+
+/**
+ * Return a require function relative to the `parent` path.
+ *
+ * @param {String} parent
+ * @return {Function}
+ * @api private
+ */
+
+require.relative = function(parent) {
+ var p = require.normalize(parent, '..');
+
+ /**
+ * lastIndexOf helper.
+ */
+
+ function lastIndexOf(arr, obj) {
+ var i = arr.length;
+ while (i--) {
+ if (arr[i] === obj) return i;
+ }
+ return -1;
+ }
+
+ /**
+ * The relative require() itself.
+ */
+
+ function localRequire(path) {
+ var resolved = localRequire.resolve(path);
+ return require(resolved, parent, path);
+ }
+
+ /**
+ * Resolve relative to the parent.
+ */
+
+ localRequire.resolve = function(path) {
+ var c = path.charAt(0);
+ if ('/' == c) return path.slice(1);
+ if ('.' == c) return require.normalize(p, path);
+
+ // resolve deps by returning
+ // the dep in the nearest "deps"
+ // directory
+ var segs = parent.split('/');
+ var i = lastIndexOf(segs, 'deps') + 1;
+ if (!i) i = 0;
+ path = segs.slice(0, i + 1).join('/') + '/deps/' + path;
+ return path;
+ };
+
+ /**
+ * Check if module is defined at `path`.
+ */
+
+ localRequire.exists = function(path) {
+ return require.modules.hasOwnProperty(localRequire.resolve(path));
+ };
+
+ return localRequire;
+};
+require.register("component-event/index.js", function(exports, require, module){
+var bind = window.addEventListener ? 'addEventListener' : 'attachEvent',
+ unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent',
+ prefix = bind !== 'addEventListener' ? 'on' : '';
+
+/**
+ * Bind `el` event `type` to `fn`.
+ *
+ * @param {Element} el
+ * @param {String} type
+ * @param {Function} fn
+ * @param {Boolean} capture
+ * @return {Function}
+ * @api public
+ */
+
+exports.bind = function(el, type, fn, capture){
+ el[bind](prefix + type, fn, capture || false);
+ return fn;
+};
+
+/**
+ * Unbind `el` event `type`'s callback `fn`.
+ *
+ * @param {Element} el
+ * @param {String} type
+ * @param {Function} fn
+ * @param {Boolean} capture
+ * @return {Function}
+ * @api public
+ */
+
+exports.unbind = function(el, type, fn, capture){
+ el[unbind](prefix + type, fn, capture || false);
+ return fn;
+};
+});
+require.register("component-query/index.js", function(exports, require, module){
+function one(selector, el) {
+ return el.querySelector(selector);
+}
+
+exports = module.exports = function(selector, el){
+ el = el || document;
+ return one(selector, el);
+};
+
+exports.all = function(selector, el){
+ el = el || document;
+ return el.querySelectorAll(selector);
+};
+
+exports.engine = function(obj){
+ if (!obj.one) throw new Error('.one callback required');
+ if (!obj.all) throw new Error('.all callback required');
+ one = obj.one;
+ exports.all = obj.all;
+ return exports;
+};
+
+});
+require.register("component-matches-selector/index.js", function(exports, require, module){
+/**
+ * Module dependencies.
+ */
+
+var query = require('query');
+
+/**
+ * Element prototype.
+ */
+
+var proto = Element.prototype;
+
+/**
+ * Vendor function.
+ */
+
+var vendor = proto.matches
+ || proto.webkitMatchesSelector
+ || proto.mozMatchesSelector
+ || proto.msMatchesSelector
+ || proto.oMatchesSelector;
+
+/**
+ * Expose `match()`.
+ */
+
+module.exports = match;
+
+/**
+ * Match `el` to `selector`.
+ *
+ * @param {Element} el
+ * @param {String} selector
+ * @return {Boolean}
+ * @api public
+ */
+
+function match(el, selector) {
+ if (vendor) return vendor.call(el, selector);
+ var nodes = query.all(selector, el.parentNode);
+ for (var i = 0; i < nodes.length; ++i) {
+ if (nodes[i] == el) return true;
+ }
+ return false;
+}
+
+});
+require.register("discore-closest/index.js", function(exports, require, module){
+var matches = require('matches-selector')
+
+module.exports = function (element, selector, checkYoSelf, root) {
+ element = checkYoSelf ? {parentNode: element} : element
+
+ root = root || document
+
+ // Make sure `element !== document` and `element != null`
+ // otherwise we get an illegal invocation
+ while ((element = element.parentNode) && element !== document) {
+ if (matches(element, selector))
+ return element
+ // After `matches` on the edge case that
+ // the selector matches the root
+ // (when the root is not the document)
+ if (element === root)
+ return
+ }
+}
+});
+require.register("component-delegate/index.js", function(exports, require, module){
+/**
+ * Module dependencies.
+ */
+
+var closest = require('closest')
+ , event = require('event');
+
+/**
+ * Delegate event `type` to `selector`
+ * and invoke `fn(e)`. A callback function
+ * is returned which may be passed to `.unbind()`.
+ *
+ * @param {Element} el
+ * @param {String} selector
+ * @param {String} type
+ * @param {Function} fn
+ * @param {Boolean} capture
+ * @return {Function}
+ * @api public
+ */
+
+exports.bind = function(el, selector, type, fn, capture){
+ return event.bind(el, type, function(e){
+ var target = e.target || e.srcElement;
+ e.delegateTarget = closest(target, selector, true, el);
+ if (e.delegateTarget) fn.call(el, e);
+ }, capture);
+};
+
+/**
+ * Unbind event `type`'s callback `fn`.
+ *
+ * @param {Element} el
+ * @param {String} type
+ * @param {Function} fn
+ * @param {Boolean} capture
+ * @api public
+ */
+
+exports.unbind = function(el, type, fn, capture){
+ event.unbind(el, type, fn, capture);
+};
+
+});
+require.register("component-events/index.js", function(exports, require, module){
+
+/**
+ * Module dependencies.
+ */
+
+var events = require('event');
+var delegate = require('delegate');
+
+/**
+ * Expose `Events`.
+ */
+
+module.exports = Events;
+
+/**
+ * Initialize an `Events` with the given
+ * `el` object which events will be bound to,
+ * and the `obj` which will receive method calls.
+ *
+ * @param {Object} el
+ * @param {Object} obj
+ * @api public
+ */
+
+function Events(el, obj) {
+ if (!(this instanceof Events)) return new Events(el, obj);
+ if (!el) throw new Error('element required');
+ if (!obj) throw new Error('object required');
+ this.el = el;
+ this.obj = obj;
+ this._events = {};
+}
+
+/**
+ * Subscription helper.
+ */
+
+Events.prototype.sub = function(event, method, cb){
+ this._events[event] = this._events[event] || {};
+ this._events[event][method] = cb;
+};
+
+/**
+ * Bind to `event` with optional `method` name.
+ * When `method` is undefined it becomes `event`
+ * with the "on" prefix.
+ *
+ * Examples:
+ *
+ * Direct event handling:
+ *
+ * events.bind('click') // implies "onclick"
+ * events.bind('click', 'remove')
+ * events.bind('click', 'sort', 'asc')
+ *
+ * Delegated event handling:
+ *
+ * events.bind('click li > a')
+ * events.bind('click li > a', 'remove')
+ * events.bind('click a.sort-ascending', 'sort', 'asc')
+ * events.bind('click a.sort-descending', 'sort', 'desc')
+ *
+ * @param {String} event
+ * @param {String|function} [method]
+ * @return {Function} callback
+ * @api public
+ */
+
+Events.prototype.bind = function(event, method){
+ var e = parse(event);
+ var el = this.el;
+ var obj = this.obj;
+ var name = e.name;
+ var method = method || 'on' + name;
+ var args = [].slice.call(arguments, 2);
+
+ // callback
+ function cb(){
+ var a = [].slice.call(arguments).concat(args);
+ obj[method].apply(obj, a);
+ }
+
+ // bind
+ if (e.selector) {
+ cb = delegate.bind(el, e.selector, name, cb);
+ } else {
+ events.bind(el, name, cb);
+ }
+
+ // subscription for unbinding
+ this.sub(name, method, cb);
+
+ return cb;
+};
+
+/**
+ * Unbind a single binding, all bindings for `event`,
+ * or all bindings within the manager.
+ *
+ * Examples:
+ *
+ * Unbind direct handlers:
+ *
+ * events.unbind('click', 'remove')
+ * events.unbind('click')
+ * events.unbind()
+ *
+ * Unbind delegate handlers:
+ *
+ * events.unbind('click', 'remove')
+ * events.unbind('click')
+ * events.unbind()
+ *
+ * @param {String|Function} [event]
+ * @param {String|Function} [method]
+ * @api public
+ */
+
+Events.prototype.unbind = function(event, method){
+ if (0 == arguments.length) return this.unbindAll();
+ if (1 == arguments.length) return this.unbindAllOf(event);
+
+ // no bindings for this event
+ var bindings = this._events[event];
+ if (!bindings) return;
+
+ // no bindings for this method
+ var cb = bindings[method];
+ if (!cb) return;
+
+ events.unbind(this.el, event, cb);
+};
+
+/**
+ * Unbind all events.
+ *
+ * @api private
+ */
+
+Events.prototype.unbindAll = function(){
+ for (var event in this._events) {
+ this.unbindAllOf(event);
+ }
+};
+
+/**
+ * Unbind all events for `event`.
+ *
+ * @param {String} event
+ * @api private
+ */
+
+Events.prototype.unbindAllOf = function(event){
+ var bindings = this._events[event];
+ if (!bindings) return;
+
+ for (var method in bindings) {
+ this.unbind(event, method);
+ }
+};
+
+/**
+ * Parse `event`.
+ *
+ * @param {String} event
+ * @return {Object}
+ * @api private
+ */
+
+function parse(event) {
+ var parts = event.split(/ +/);
+ return {
+ name: parts.shift(),
+ selector: parts.join(' ')
+ }
+}
+
+});
+require.register("component-indexof/index.js", function(exports, require, module){
+module.exports = function(arr, obj){
+ if (arr.indexOf) return arr.indexOf(obj);
+ for (var i = 0; i < arr.length; ++i) {
+ if (arr[i] === obj) return i;
+ }
+ return -1;
+};
+
+});
+require.register("component-classes/index.js", function(exports, require, module){
+/**
+ * Module dependencies.
+ */
+
+var index = require('indexof');
+
+/**
+ * Whitespace regexp.
+ */
+
+var re = /\s+/;
+
+/**
+ * toString reference.
+ */
+
+var toString = Object.prototype.toString;
+
+/**
+ * Wrap `el` in a `ClassList`.
+ *
+ * @param {Element} el
+ * @return {ClassList}
+ * @api public
+ */
+
+module.exports = function(el){
+ return new ClassList(el);
+};
+
+/**
+ * Initialize a new ClassList for `el`.
+ *
+ * @param {Element} el
+ * @api private
+ */
+
+function ClassList(el) {
+ if (!el) throw new Error('A DOM element reference is required');
+ this.el = el;
+ this.list = el.classList;
+}
+
+/**
+ * Add class `name` if not already present.
+ *
+ * @param {String} name
+ * @return {ClassList}
+ * @api public
+ */
+
+ClassList.prototype.add = function(name){
+ // classList
+ if (this.list) {
+ this.list.add(name);
+ return this;
+ }
+
+ // fallback
+ var arr = this.array();
+ var i = index(arr, name);
+ if (!~i) arr.push(name);
+ this.el.className = arr.join(' ');
+ return this;
+};
+
+/**
+ * Remove class `name` when present, or
+ * pass a regular expression to remove
+ * any which match.
+ *
+ * @param {String|RegExp} name
+ * @return {ClassList}
+ * @api public
+ */
+
+ClassList.prototype.remove = function(name){
+ if ('[object RegExp]' == toString.call(name)) {
+ return this.removeMatching(name);
+ }
+
+ // classList
+ if (this.list) {
+ this.list.remove(name);
+ return this;
+ }
+
+ // fallback
+ var arr = this.array();
+ var i = index(arr, name);
+ if (~i) arr.splice(i, 1);
+ this.el.className = arr.join(' ');
+ return this;
+};
+
+/**
+ * Remove all classes matching `re`.
+ *
+ * @param {RegExp} re
+ * @return {ClassList}
+ * @api private
+ */
+
+ClassList.prototype.removeMatching = function(re){
+ var arr = this.array();
+ for (var i = 0; i < arr.length; i++) {
+ if (re.test(arr[i])) {
+ this.remove(arr[i]);
+ }
+ }
+ return this;
+};
+
+/**
+ * Toggle class `name`, can force state via `force`.
+ *
+ * For browsers that support classList, but do not support `force` yet,
+ * the mistake will be detected and corrected.
+ *
+ * @param {String} name
+ * @param {Boolean} force
+ * @return {ClassList}
+ * @api public
+ */
+
+ClassList.prototype.toggle = function(name, force){
+ // classList
+ if (this.list) {
+ if ("undefined" !== typeof force) {
+ if (force !== this.list.toggle(name, force)) {
+ this.list.toggle(name); // toggle again to correct
+ }
+ } else {
+ this.list.toggle(name);
+ }
+ return this;
+ }
+
+ // fallback
+ if ("undefined" !== typeof force) {
+ if (!force) {
+ this.remove(name);
+ } else {
+ this.add(name);
+ }
+ } else {
+ if (this.has(name)) {
+ this.remove(name);
+ } else {
+ this.add(name);
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Return an array of classes.
+ *
+ * @return {Array}
+ * @api public
+ */
+
+ClassList.prototype.array = function(){
+ var str = this.el.className.replace(/^\s+|\s+$/g, '');
+ var arr = str.split(re);
+ if ('' === arr[0]) arr.shift();
+ return arr;
+};
+
+/**
+ * Check if class `name` is present.
+ *
+ * @param {String} name
+ * @return {ClassList}
+ * @api public
+ */
+
+ClassList.prototype.has =
+ClassList.prototype.contains = function(name){
+ return this.list
+ ? this.list.contains(name)
+ : !! ~index(this.array(), name);
+};
+
+});
+require.register("component-emitter/index.js", function(exports, require, module){
+
+/**
+ * Expose `Emitter`.
+ */
+
+module.exports = Emitter;
+
+/**
+ * Initialize a new `Emitter`.
+ *
+ * @api public
+ */
+
+function Emitter(obj) {
+ if (obj) return mixin(obj);
+};
+
+/**
+ * Mixin the emitter properties.
+ *
+ * @param {Object} obj
+ * @return {Object}
+ * @api private
+ */
+
+function mixin(obj) {
+ for (var key in Emitter.prototype) {
+ obj[key] = Emitter.prototype[key];
+ }
+ return obj;
+}
+
+/**
+ * Listen on the given `event` with `fn`.
+ *
+ * @param {String} event
+ * @param {Function} fn
+ * @return {Emitter}
+ * @api public
+ */
+
+Emitter.prototype.on =
+Emitter.prototype.addEventListener = function(event, fn){
+ this._callbacks = this._callbacks || {};
+ (this._callbacks[event] = this._callbacks[event] || [])
+ .push(fn);
+ return this;
+};
+
+/**
+ * Adds an `event` listener that will be invoked a single
+ * time then automatically removed.
+ *
+ * @param {String} event
+ * @param {Function} fn
+ * @return {Emitter}
+ * @api public
+ */
+
+Emitter.prototype.once = function(event, fn){
+ var self = this;
+ this._callbacks = this._callbacks || {};
+
+ function on() {
+ self.off(event, on);
+ fn.apply(this, arguments);
+ }
+
+ on.fn = fn;
+ this.on(event, on);
+ return this;
+};
+
+/**
+ * Remove the given callback for `event` or all
+ * registered callbacks.
+ *
+ * @param {String} event
+ * @param {Function} fn
+ * @return {Emitter}
+ * @api public
+ */
+
+Emitter.prototype.off =
+Emitter.prototype.removeListener =
+Emitter.prototype.removeAllListeners =
+Emitter.prototype.removeEventListener = function(event, fn){
+ this._callbacks = this._callbacks || {};
+
+ // all
+ if (0 == arguments.length) {
+ this._callbacks = {};
+ return this;
+ }
+
+ // specific event
+ var callbacks = this._callbacks[event];
+ if (!callbacks) return this;
+
+ // remove all handlers
+ if (1 == arguments.length) {
+ delete this._callbacks[event];
+ return this;
+ }
+
+ // remove specific handler
+ var cb;
+ for (var i = 0; i < callbacks.length; i++) {
+ cb = callbacks[i];
+ if (cb === fn || cb.fn === fn) {
+ callbacks.splice(i, 1);
+ break;
+ }
+ }
+ return this;
+};
+
+/**
+ * Emit `event` with the given args.
+ *
+ * @param {String} event
+ * @param {Mixed} ...
+ * @return {Emitter}
+ */
+
+Emitter.prototype.emit = function(event){
+ this._callbacks = this._callbacks || {};
+ var args = [].slice.call(arguments, 1)
+ , callbacks = this._callbacks[event];
+
+ if (callbacks) {
+ callbacks = callbacks.slice(0);
+ for (var i = 0, len = callbacks.length; i < len; ++i) {
+ callbacks[i].apply(this, args);
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Return array of callbacks for `event`.
+ *
+ * @param {String} event
+ * @return {Array}
+ * @api public
+ */
+
+Emitter.prototype.listeners = function(event){
+ this._callbacks = this._callbacks || {};
+ return this._callbacks[event] || [];
+};
+
+/**
+ * Check if this emitter has `event` handlers.
+ *
+ * @param {String} event
+ * @return {Boolean}
+ * @api public
+ */
+
+Emitter.prototype.hasListeners = function(event){
+ return !! this.listeners(event).length;
+};
+
+});
+require.register("ui-component-mouse/index.js", function(exports, require, module){
+
+/**
+ * dependencies.
+ */
+
+var emitter = require('emitter')
+ , event = require('event');
+
+/**
+ * export `Mouse`
+ */
+
+module.exports = function(el, obj){
+ return new Mouse(el, obj);
+};
+
+/**
+ * initialize new `Mouse`.
+ *
+ * @param {Element} el
+ * @param {Object} obj
+ */
+
+function Mouse(el, obj){
+ this.obj = obj || {};
+ this.el = el;
+}
+
+/**
+ * mixin emitter.
+ */
+
+emitter(Mouse.prototype);
+
+/**
+ * bind mouse.
+ *
+ * @return {Mouse}
+ */
+
+Mouse.prototype.bind = function(){
+ var obj = this.obj
+ , self = this;
+
+ // up
+ function up(e){
+ obj.onmouseup && obj.onmouseup(e);
+ event.unbind(document, 'mousemove', move);
+ event.unbind(document, 'mouseup', up);
+ self.emit('up', e);
+ }
+
+ // move
+ function move(e){
+ obj.onmousemove && obj.onmousemove(e);
+ self.emit('move', e);
+ }
+
+ // down
+ self.down = function(e){
+ obj.onmousedown && obj.onmousedown(e);
+ event.bind(document, 'mouseup', up);
+ event.bind(document, 'mousemove', move);
+ self.emit('down', e);
+ };
+
+ // bind all.
+ event.bind(this.el, 'mousedown', self.down);
+
+ return this;
+};
+
+/**
+ * unbind mouse.
+ *
+ * @return {Mouse}
+ */
+
+Mouse.prototype.unbind = function(){
+ event.unbind(this.el, 'mousedown', this.down);
+ this.down = null;
+};
+
+});
+require.register("abpetkov-percentage-calc/percentage-calc.js", function(exports, require, module){
+
+/**
+ * Percentage-Calc 0.0.1
+ * https://github.com/abpetkov/percentage-calc
+ *
+ * Authored by Alexander Petkov
+ * https://github.com/abpetkov
+ *
+ * Copyright 2014, Alexander Petkov
+ * License: The MIT License (MIT)
+ * http://opensource.org/licenses/MIT
+ *
+ */
+
+/**
+ * Check if number.
+ *
+ * @param {Number} num
+ * @returns {Boolean}
+ * @api public
+ */
+
+exports.isNumber = function(num) {
+ return (typeof num === 'number') ? true : false;
+};
+
+/**
+ * Calculate percentage of a number.
+ *
+ * @param {Number} perc
+ * @param {Number} num
+ * @returns {Number} result
+ * @api public
+ */
+
+exports.of = function(perc, num) {
+ if (exports.isNumber(perc) && exports.isNumber(num)) return (perc / 100) * num;
+};
+
+/**
+ * Calculate percentage of a number out ot another number.
+ *
+ * @param {Number} part
+ * @param {Number} target
+ * @returns {Number} result
+ * @api public
+ */
+
+exports.from = function(part, target) {
+ if (exports.isNumber(part) && exports.isNumber(target)) return (part / target) * 100;
+};
+});
+require.register("abpetkov-closest-num/closest-num.js", function(exports, require, module){
+/**
+ * Closest-num 0.0.1
+ * https://github.com/abpetkov/closest-num
+ *
+ * Author: Alexander Petkov
+ * https://github.com/abpetkov
+ *
+ * Copyright 2014, Alexander Petkov
+ * License: The MIT License (MIT)
+ * http://opensource.org/licenses/MIT
+ *
+ */
+
+/**
+ * Get closest number in array.
+ *
+ * @param {Number} target
+ * @param {Array} points
+ * @returns {Number} closest
+ * @api private
+ */
+
+exports.find = function(target, points) {
+ var diff = null
+ , current = null
+ , closest = points[0];
+
+ for (i = 0; i < points.length; i++) {
+ diff = Math.abs(target - closest);
+ current = Math.abs(target - points[i]);
+ if (current < diff) closest = points[i];
+ }
+
+ return closest;
+};
+});
+require.register("vesln-super/lib/super.js", function(exports, require, module){
+/**
+ * slice
+ */
+
+var slice = Array.prototype.slice;
+
+/**
+ * Primary export
+ */
+
+var exports = module.exports = super_;
+
+/**
+ * ### _super (dest, orig)
+ *
+ * Inherits the prototype methods or merges objects.
+ * This is the primary export and it is recommended
+ * that it be imported as `inherits` in node to match
+ * the auto imported browser interface.
+ *
+ * var inherits = require('super');
+ *
+ * @param {Object|Function} destination object
+ * @param {Object|Function} source object
+ * @name _super
+ * @api public
+ */
+
+function super_() {
+ var args = slice.call(arguments);
+ if (!args.length) return;
+ if (typeof args[0] !== 'function') return exports.merge(args);
+ exports.inherits.apply(null, args);
+};
+
+/**
+ * ### extend (proto[, klass])
+ *
+ * Provide `.extend` mechanism to allow extenion without
+ * needing to use dependancy.
+ *
+ * function Bar () {
+ * this._konstructed = true;
+ * }
+ *
+ * Bar.extend = inherits.extend;
+ *
+ * var Fu = Bar.extend({
+ * initialize: function () {
+ * this._initialized = true;
+ * }
+ * });
+ *
+ * var fu = new Fu();
+ * fu.should.be.instanceof(Fu); // true
+ * fu.should.be.instanceof(Bar); // true
+ *
+ * @param {Object} properties/methods to add to new prototype
+ * @param {Object} properties/methods to add to new class
+ * @returns {Object} new constructor
+ * @name extend
+ * @api public
+ */
+
+exports.extend = function(proto, klass) {
+ var self = this
+ , child = function () { return self.apply(this, arguments); };
+ exports.merge([ child, this ]);
+ exports.inherits(child, this);
+ if (proto) exports.merge([ child.prototype, proto ]);
+ if (klass) exports.merge([ child, klass ]);
+ child.extend = this.extend; // prevent overwrite
+ return child;
+};
+
+/**
+ * ### inherits (ctor, superCtor)
+ *
+ * Inherit the prototype methods from on contructor
+ * to another.
+ *
+ * @param {Function} destination
+ * @param {Function} source
+ * @api private
+ */
+
+exports.inherits = function(ctor, superCtor) {
+ ctor.super_ = superCtor;
+ if (Object.create) {
+ ctor.prototype = Object.create(superCtor.prototype,
+ { constructor: {
+ value: ctor
+ , enumerable: false
+ , writable: true
+ , configurable: true
+ }
+ });
+ } else {
+ ctor.prototype = new superCtor();
+ ctor.prototype.constructor = ctor;
+ }
+}
+
+/**
+ * Extends multiple objects.
+ *
+ * @param {Array} array of objects
+ * @api private
+ */
+
+exports.merge = function (arr) {
+ var main = arr.length === 2 ? arr.shift() : {};
+ var obj = null;
+
+ for (var i = 0, len = arr.length; i < len; i++) {
+ obj = arr[i];
+ for (var p in obj) {
+ if (!obj.hasOwnProperty(p)) continue;
+ main[p] = obj[p];
+ }
+ }
+
+ return main;
+};
+
+});
+require.register("powerange/lib/powerange.js", function(exports, require, module){
+/**
+ * Require classes.
+ */
+
+var Main = require('./main')
+ , Horizontal = require('./horizontal')
+ , Vertical = require('./vertical');
+
+/**
+ * Set default values.
+ *
+ * @api public
+ */
+
+var defaults = {
+ callback: function() {}
+ , decimal: false
+ , disable: false
+ , disableOpacity: 0.5
+ , hideRange: false
+ , klass: ''
+ , min: 0
+ , max: 100
+ , start: null
+ , step: null
+ , vertical: false
+};
+
+/**
+ * Expose proper type of `Powerange`.
+ */
+
+module.exports = function(element, options) {
+ options = options || {};
+
+ for (var i in defaults) {
+ if (options[i] == null) {
+ options[i] = defaults[i];
+ }
+ }
+
+ if (options.vertical) {
+ return new Vertical(element, options);
+ } else {
+ return new Horizontal(element, options);
+ }
+};
+});
+require.register("powerange/lib/main.js", function(exports, require, module){
+/**
+ * External dependencies.
+ *
+ */
+
+var mouse = require('mouse')
+ , events = require('events')
+ , classes = require('classes')
+ , percentage = require('percentage-calc');
+
+/**
+ * Expose `Powerange`.
+ */
+
+module.exports = Powerange;
+
+/**
+ * Create Powerange object.
+ *
+ * @constructor
+ * @param {Object} element
+ * @param {Object} options
+ * @api public
+ */
+
+function Powerange(element, options) {
+ if (!(this instanceof Powerange)) return new Powerange(element, options);
+
+ this.element = element;
+ this.options = options || {};
+ this.slider = this.create('span', 'range-bar');
+
+ if (this.element !== null && this.element.type === 'text') this.init();
+}
+
+/**
+ * Bind events on handle element.
+ *
+ * @api private
+ */
+
+Powerange.prototype.bindEvents = function () {
+ this.handle = this.slider.querySelector('.range-handle');
+ this.touch = events(this.handle, this);
+ this.touch.bind('touchstart', 'onmousedown');
+ this.touch.bind('touchmove', 'onmousemove');
+ this.touch.bind('touchend', 'onmouseup');
+ this.mouse = mouse(this.handle, this);
+ this.mouse.bind();
+};
+
+/**
+ * Hide the target element.
+ *
+ * @api private
+ */
+
+Powerange.prototype.hide = function() {
+ this.element.style.display = 'none';
+};
+
+/**
+ * Append the target after the element.
+ *
+ * @api private
+ */
+
+Powerange.prototype.append = function() {
+ var slider = this.generate();
+ this.insertAfter(this.element, slider);
+};
+
+/**
+ * Generate the appropriate type of slider.
+ *
+ * @returns {Object} this.slider
+ * @api private
+ */
+
+Powerange.prototype.generate = function() {
+ var elems = {
+ 'handle': {
+ 'type': 'span'
+ , 'selector': 'range-handle'
+ }
+ , 'min': {
+ 'type': 'span'
+ , 'selector': 'range-min'
+ }
+ , 'max': {
+ 'type': 'span'
+ , 'selector': 'range-max'
+ }
+ , 'quantity': {
+ 'type': 'span'
+ , 'selector': 'range-quantity'
+ }
+ };
+
+ for (var key in elems) {
+ if (elems.hasOwnProperty(key)) {
+ var temp = this.create(elems[key].type, elems[key].selector);
+ this.slider.appendChild(temp);
+ }
+ }
+
+ return this.slider;
+};
+
+/**
+ * Create HTML element.
+ *
+ * @param {String} type
+ * @param {String} name
+ * @returns {Object} elem
+ * @api private
+ */
+
+Powerange.prototype.create = function(type, name) {
+ var elem = document.createElement(type);
+ elem.className = name;
+
+ return elem;
+};
+
+/**
+ * Insert element after another element.
+ *
+ * @param {Object} reference
+ * @param {Object} target
+ * @api private
+ */
+
+Powerange.prototype.insertAfter = function(reference, target) {
+ reference.parentNode.insertBefore(target, reference.nextSibling);
+};
+
+/**
+ * Add an additional class for extra customization.
+ *
+ * @param {String} klass
+ * @api private
+ */
+
+Powerange.prototype.extraClass = function(klass) {
+ if (this.options.klass) classes(this.slider).add(klass);
+};
+
+/**
+ * Set min and max values.
+ *
+ * @param {Number} min
+ * @param {Number} max
+ * @api private
+ */
+
+Powerange.prototype.setRange = function(min, max) {
+ if (typeof min === 'number' && typeof max === 'number' && !this.options.hideRange) {
+ this.slider.querySelector('.range-min').innerHTML = min;
+ this.slider.querySelector('.range-max').innerHTML = max;
+ }
+};
+
+/**
+ * Set slider current value.
+ *
+ * @param {Number} offset
+ * @param {Number} size
+ * @api private
+ */
+
+Powerange.prototype.setValue = function (offset, size) {
+ var part = percentage.from(parseFloat(offset), size)
+ , value = percentage.of(part, this.options.max - this.options.min) + this.options.min
+ , changed = false;
+
+ value = (this.options.decimal) ? (Math.round(value * 100) / 100) : Math.round(value);
+ changed = (this.element.value != value) ? true : false;
+
+ this.element.value = value;
+ this.options.callback();
+ if (changed) this.changeEvent();
+};
+
+/**
+ * Set step.
+ *
+ * @param {Number} sliderSize
+ * @param {Number} handleSize
+ * @returns {Array} this.steps
+ * @api private
+ */
+
+Powerange.prototype.step = function(sliderSize, handleSize) {
+ var dimension = sliderSize - handleSize
+ , part = percentage.from(this.checkStep(this.options.step), this.options.max - this.options.min)
+ , interval = percentage.of(part, dimension)
+ , steps = [];
+
+ for (i = 0; i <= dimension; i += interval) {
+ steps.push(i);
+ }
+
+ this.steps = steps;
+
+ return this.steps;
+};
+
+/**
+ * Check values.
+ *
+ * @param {Number} start
+ * @api private
+ */
+
+Powerange.prototype.checkValues = function(start) {
+ if (start < this.options.min) this.options.start = this.options.min;
+ if (start > this.options.max) this.options.start = this.options.max;
+ if (this.options.min >= this.options.max) this.options.min = this.options.max;
+};
+
+/**
+ * Make sure `step` is positive.
+ *
+ * @param {Number} value
+ * @returns {Number} this.options.step
+ * @api private
+ */
+
+Powerange.prototype.checkStep = function(value) {
+ if (value < 0) value = Math.abs(value);
+ this.options.step = value;
+ return this.options.step;
+};
+
+/**
+ * Disable range slider.
+ *
+ * @api private
+ */
+
+Powerange.prototype.disable = function() {
+ if (this.options.min == this.options.max || this.options.min > this.options.max || this.options.disable) {
+ this.mouse.unbind();
+ this.touch.unbind();
+ this.slider.style.opacity = this.options.disableOpacity;
+ classes(this.handle).add('range-disabled');
+ }
+};
+
+/**
+ * Make element unselectable.
+ *
+ * @param {Object} element
+ * @param {Boolean} set
+ * @api private
+ */
+
+Powerange.prototype.unselectable = function(element, set) {
+ if (!classes(this.slider).has('unselectable') && set === true) {
+ classes(this.slider).add('unselectable');
+ } else {
+ classes(this.slider).remove('unselectable');
+ }
+};
+
+/**
+ * Handle the onchange event.
+ *
+ * @param {Boolean} state
+ * @api private
+ */
+
+Powerange.prototype.changeEvent = function(state) {
+ if (typeof Event === 'function' || !document.fireEvent) {
+ var event = document.createEvent('HTMLEvents');
+ event.initEvent('change', false, true);
+ this.element.dispatchEvent(event);
+ } else {
+ this.element.fireEvent('onchange');
+ }
+};
+
+/**
+ * Initialize main class.
+ *
+ * @api private
+ */
+
+Powerange.prototype.init = function() {
+ this.hide();
+ this.append();
+ this.bindEvents();
+ this.extraClass(this.options.klass);
+ this.checkValues(this.options.start);
+ this.setRange(this.options.min, this.options.max);
+ this.disable();
+};
+});
+require.register("powerange/lib/horizontal.js", function(exports, require, module){
+/**
+ * External dependencies.
+ *
+ */
+
+var inherits = require('super')
+ , closest = require('closest-num')
+ , percentage = require('percentage-calc');
+
+/**
+ * Require main class.
+ */
+
+var Powerange = require('./main');
+
+/**
+ * Expose `Horizontal`.
+ */
+
+module.exports = Horizontal;
+
+/**
+ * Create horizontal slider object.
+ *
+ * @api public
+ */
+
+function Horizontal() {
+ Powerange.apply(this, arguments);
+ if (this.options.step) this.step(this.slider.offsetWidth, this.handle.offsetWidth);
+ this.setStart(this.options.start);
+}
+
+/**
+ * Inherit the main class.
+ */
+
+inherits(Horizontal, Powerange);
+
+/**
+ * Set horizontal slider position.
+ *
+ * @param {Number} start
+ * @api private
+ */
+
+Horizontal.prototype.setStart = function(start) {
+ var begin = (start === null) ? this.options.min : start
+ , part = percentage.from(begin - this.options.min, this.options.max - this.options.min) || 0
+ , offset = percentage.of(part, this.slider.offsetWidth - this.handle.offsetWidth)
+ , position = (this.options.step) ? closest.find(offset, this.steps) : offset;
+
+ this.setPosition(position);
+ this.setValue(this.handle.style.left, this.slider.offsetWidth - this.handle.offsetWidth);
+};
+
+/**
+ * Set horizontal slider current position.
+ *
+ * @param {Number} val
+ * @api private
+ */
+
+Horizontal.prototype.setPosition = function(val) {
+ this.handle.style.left = val + 'px';
+ this.slider.querySelector('.range-quantity').style.width = val + 'px';
+};
+
+/**
+ * On slider mouse down.
+ *
+ * @param {Object} e
+ * @api private
+ */
+
+Horizontal.prototype.onmousedown = function(e) {
+ if (e.touches) e = e.touches[0];
+ this.startX = e.clientX;
+ this.handleOffsetX = this.handle.offsetLeft;
+ this.restrictHandleX = this.slider.offsetWidth - this.handle.offsetWidth;
+ this.unselectable(this.slider, true);
+};
+
+/**
+ * On slider mouse move.
+ *
+ * @param {Object} e
+ * @api private
+ */
+
+Horizontal.prototype.onmousemove = function(e) {
+ e.preventDefault();
+ if (e.touches) e = e.touches[0];
+
+ var leftOffset = this.handleOffsetX + e.clientX - this.startX
+ , position = (this.steps) ? closest.find(leftOffset, this.steps) : leftOffset;
+
+ if (leftOffset <= 0) {
+ this.setPosition(0);
+ } else if (leftOffset >= this.restrictHandleX) {
+ this.setPosition(this.restrictHandleX);
+ } else {
+ this.setPosition(position);
+ }
+
+ this.setValue(this.handle.style.left, this.slider.offsetWidth - this.handle.offsetWidth);
+};
+
+/**
+ * On mouse up.
+ *
+ * @param {Object} e
+ * @api private
+ */
+
+Horizontal.prototype.onmouseup = function(e) {
+ this.unselectable(this.slider, false);
+};
+});
+require.register("powerange/lib/vertical.js", function(exports, require, module){
+/**
+ * External dependencies.
+ *
+ */
+
+var inherits = require('super')
+ , classes = require('classes')
+ , closest = require('closest-num')
+ , percentage = require('percentage-calc');
+
+/**
+ * Require main class.
+ */
+
+var Powerange = require('./main');
+
+/**
+ * Expose `Vertical`.
+ */
+
+module.exports = Vertical;
+
+/**
+ * Create vertical slider object.
+ *
+ * @api public
+ */
+
+function Vertical() {
+ Powerange.apply(this, arguments);
+ classes(this.slider).add('vertical');
+ if (this.options.step) this.step(this.slider.offsetHeight, this.handle.offsetHeight);
+ this.setStart(this.options.start);
+}
+
+/**
+ * Inherit the main class.
+ */
+
+inherits(Vertical, Powerange);
+
+/**
+ * Set vertical slider position.
+ *
+ * @param {Number} start
+ * @api private
+ */
+
+Vertical.prototype.setStart = function(start) {
+ var begin = (start === null) ? this.options.min : start
+ , part = percentage.from(begin - this.options.min, this.options.max - this.options.min) || 0
+ , offset = percentage.of(part, this.slider.offsetHeight - this.handle.offsetHeight)
+ , position = (this.options.step) ? closest.find(offset, this.steps) : offset;
+
+ this.setPosition(position);
+ this.setValue(this.handle.style.bottom, this.slider.offsetHeight - this.handle.offsetHeight);
+};
+
+/**
+ * Set vertical slider current position.
+ *
+ * @param {Number} val
+ * @api private
+ */
+
+Vertical.prototype.setPosition = function(val) {
+ this.handle.style.bottom = val + 'px';
+ this.slider.querySelector('.range-quantity').style.height = val + 'px';
+};
+
+/**
+ * On mouse down.
+ *
+ * @param {Object} e
+ * @api private
+ */
+
+Vertical.prototype.onmousedown = function(e) {
+ if (e.touches) e = e.touches[0];
+ this.startY = e.clientY;
+ this.handleOffsetY = this.slider.offsetHeight - this.handle.offsetHeight - this.handle.offsetTop;
+ this.restrictHandleY = this.slider.offsetHeight - this.handle.offsetHeight;
+ this.unselectable(this.slider, true);
+};
+
+/**
+ * On vertical slider mouse move.
+ *
+ * @param {Object} e
+ * @api private
+ */
+
+Vertical.prototype.onmousemove = function(e) {
+ e.preventDefault();
+ if (e.touches) e = e.touches[0];
+
+ var bottomOffset = this.handleOffsetY + this.startY - e.clientY
+ , position = (this.steps) ? closest.find(bottomOffset, this.steps) : bottomOffset;
+
+ if (bottomOffset <= 0) {
+ this.setPosition(0);
+ } else if (bottomOffset >= this.restrictHandleY) {
+ this.setPosition(this.restrictHandleY);
+ } else {
+ this.setPosition(position);
+ }
+
+ this.setValue(this.handle.style.bottom, this.slider.offsetHeight - this.handle.offsetHeight);
+};
+
+/**
+ * On mouse up.
+ *
+ * @param {Object} e
+ * @api private
+ */
+
+Vertical.prototype.onmouseup = function(e) {
+ this.unselectable(this.slider, false);
+};
+});
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+require.alias("component-events/index.js", "powerange/deps/events/index.js");
+require.alias("component-events/index.js", "events/index.js");
+require.alias("component-event/index.js", "component-events/deps/event/index.js");
+
+require.alias("component-delegate/index.js", "component-events/deps/delegate/index.js");
+require.alias("discore-closest/index.js", "component-delegate/deps/closest/index.js");
+require.alias("discore-closest/index.js", "component-delegate/deps/closest/index.js");
+require.alias("component-matches-selector/index.js", "discore-closest/deps/matches-selector/index.js");
+require.alias("component-query/index.js", "component-matches-selector/deps/query/index.js");
+
+require.alias("discore-closest/index.js", "discore-closest/index.js");
+require.alias("component-event/index.js", "component-delegate/deps/event/index.js");
+
+require.alias("component-classes/index.js", "powerange/deps/classes/index.js");
+require.alias("component-classes/index.js", "classes/index.js");
+require.alias("component-indexof/index.js", "component-classes/deps/indexof/index.js");
+
+require.alias("ui-component-mouse/index.js", "powerange/deps/mouse/index.js");
+require.alias("ui-component-mouse/index.js", "mouse/index.js");
+require.alias("component-emitter/index.js", "ui-component-mouse/deps/emitter/index.js");
+
+require.alias("component-event/index.js", "ui-component-mouse/deps/event/index.js");
+
+require.alias("abpetkov-percentage-calc/percentage-calc.js", "powerange/deps/percentage-calc/percentage-calc.js");
+require.alias("abpetkov-percentage-calc/percentage-calc.js", "powerange/deps/percentage-calc/index.js");
+require.alias("abpetkov-percentage-calc/percentage-calc.js", "percentage-calc/index.js");
+require.alias("abpetkov-percentage-calc/percentage-calc.js", "abpetkov-percentage-calc/index.js");
+require.alias("abpetkov-closest-num/closest-num.js", "powerange/deps/closest-num/closest-num.js");
+require.alias("abpetkov-closest-num/closest-num.js", "powerange/deps/closest-num/index.js");
+require.alias("abpetkov-closest-num/closest-num.js", "closest-num/index.js");
+require.alias("abpetkov-closest-num/closest-num.js", "abpetkov-closest-num/index.js");
+require.alias("vesln-super/lib/super.js", "powerange/deps/super/lib/super.js");
+require.alias("vesln-super/lib/super.js", "powerange/deps/super/index.js");
+require.alias("vesln-super/lib/super.js", "super/index.js");
+require.alias("vesln-super/lib/super.js", "vesln-super/index.js");
+require.alias("powerange/lib/powerange.js", "powerange/index.js");if (typeof exports == "object") {
+ module.exports = require("powerange");
+} else if (typeof define == "function" && define.amd) {
+ define([], function(){ return require("powerange"); });
+} else {
+ this["Powerange"] = require("powerange");
+}})();
diff --git a/web/lib/powerange/powerange.min.css b/web/lib/powerange/powerange.min.css
new file mode 100644
index 0000000..c6c7a43
--- /dev/null
+++ b/web/lib/powerange/powerange.min.css
@@ -0,0 +1 @@
+.range-bar{background-color:#a9acb1;border-radius:15px;display:block;height:4px;position:relative;width:100%}.range-quantity{background-color:#017afd;border-radius:15px;display:block;height:100%;width:0}.range-handle{background-color:#fff;border-radius:100%;cursor:move;height:30px;left:0;top:-13px;position:absolute;width:30px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,.4);box-shadow:0 1px 3px rgba(0,0,0,.4)}.range-min,.range-max{color:#181819;font-size:12px;height:20px;padding-top:4px;position:absolute;text-align:center;top:-9px;width:24px}.range-min{left:-30px}.range-max{right:-30px}.vertical{height:100%;width:4px}.vertical .range-quantity{bottom:0;height:0;position:absolute;width:100%}.vertical .range-handle{bottom:0;left:-13px;top:auto}.vertical .range-min,.vertical .range-max{left:-10px;right:auto;top:auto}.vertical .range-min{bottom:-30px}.vertical .range-max{top:-30px}.unselectable{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.range-disabled{cursor:default}
\ No newline at end of file
diff --git a/web/lib/powerange/powerange.min.js b/web/lib/powerange/powerange.min.js
new file mode 100644
index 0000000..31cb9fd
--- /dev/null
+++ b/web/lib/powerange/powerange.min.js
@@ -0,0 +1 @@
+(function(){function e(t,s,n){var i=e.resolve(t);if(null==i){n=n||t,s=s||"root";var o=Error('Failed to require "'+n+'" from "'+s+'"');throw o.path=n,o.parent=s,o.require=!0,o}var r=e.modules[i];if(!r._resolving&&!r.exports){var a={};a.exports={},a.client=a.component=!0,r._resolving=!0,r.call(this,a.exports,e.relative(i),a),delete r._resolving,r.exports=a.exports}return r.exports}e.modules={},e.aliases={},e.resolve=function(t){"/"===t.charAt(0)&&(t=t.slice(1));for(var s=[t,t+".js",t+".json",t+"/index.js",t+"/index.json"],n=0;s.length>n;n++){var t=s[n];if(e.modules.hasOwnProperty(t))return t;if(e.aliases.hasOwnProperty(t))return e.aliases[t]}},e.normalize=function(e,t){var s=[];if("."!=t.charAt(0))return t;e=e.split("/"),t=t.split("/");for(var n=0;t.length>n;++n)".."==t[n]?e.pop():"."!=t[n]&&""!=t[n]&&s.push(t[n]);return e.concat(s).join("/")},e.register=function(t,s){e.modules[t]=s},e.alias=function(t,s){if(!e.modules.hasOwnProperty(t))throw Error('Failed to alias "'+t+'", it does not exist');e.aliases[s]=t},e.relative=function(t){function s(e,t){for(var s=e.length;s--;)if(e[s]===t)return s;return-1}function n(s){var i=n.resolve(s);return e(i,t,s)}var i=e.normalize(t,"..");return n.resolve=function(n){var o=n.charAt(0);if("/"==o)return n.slice(1);if("."==o)return e.normalize(i,n);var r=t.split("/"),a=s(r,"deps")+1;return a||(a=0),n=r.slice(0,a+1).join("/")+"/deps/"+n},n.exists=function(t){return e.modules.hasOwnProperty(n.resolve(t))},n},e.register("component-event/index.js",function(e){var t=window.addEventListener?"addEventListener":"attachEvent",s=window.removeEventListener?"removeEventListener":"detachEvent",n="addEventListener"!==t?"on":"";e.bind=function(e,s,i,o){return e[t](n+s,i,o||!1),i},e.unbind=function(e,t,i,o){return e[s](n+t,i,o||!1),i}}),e.register("component-query/index.js",function(e,t,s){function n(e,t){return t.querySelector(e)}e=s.exports=function(e,t){return t=t||document,n(e,t)},e.all=function(e,t){return t=t||document,t.querySelectorAll(e)},e.engine=function(t){if(!t.one)throw Error(".one callback required");if(!t.all)throw Error(".all callback required");return n=t.one,e.all=t.all,e}}),e.register("component-matches-selector/index.js",function(e,t,s){function n(e,t){if(r)return r.call(e,t);for(var s=i.all(t,e.parentNode),n=0;s.length>n;++n)if(s[n]==e)return!0;return!1}var i=t("query"),o=Element.prototype,r=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.msMatchesSelector||o.oMatchesSelector;s.exports=n}),e.register("discore-closest/index.js",function(e,t,s){var n=t("matches-selector");s.exports=function(e,t,s,i){for(e=s?{parentNode:e}:e,i=i||document;(e=e.parentNode)&&e!==document;){if(n(e,t))return e;if(e===i)return}}}),e.register("component-delegate/index.js",function(e,t){var s=t("closest"),n=t("event");e.bind=function(e,t,i,o,r){return n.bind(e,i,function(n){var i=n.target||n.srcElement;n.delegateTarget=s(i,t,!0,e),n.delegateTarget&&o.call(e,n)},r)},e.unbind=function(e,t,s,i){n.unbind(e,t,s,i)}}),e.register("component-events/index.js",function(e,t,s){function n(e,t){if(!(this instanceof n))return new n(e,t);if(!e)throw Error("element required");if(!t)throw Error("object required");this.el=e,this.obj=t,this._events={}}function i(e){var t=e.split(/ +/);return{name:t.shift(),selector:t.join(" ")}}var o=t("event"),r=t("delegate");s.exports=n,n.prototype.sub=function(e,t,s){this._events[e]=this._events[e]||{},this._events[e][t]=s},n.prototype.bind=function(e,t){function s(){var e=[].slice.call(arguments).concat(h);l[t].apply(l,e)}var n=i(e),a=this.el,l=this.obj,c=n.name,t=t||"on"+c,h=[].slice.call(arguments,2);return n.selector?s=r.bind(a,n.selector,c,s):o.bind(a,c,s),this.sub(c,t,s),s},n.prototype.unbind=function(e,t){if(0==arguments.length)return this.unbindAll();if(1==arguments.length)return this.unbindAllOf(e);var s=this._events[e];if(s){var n=s[t];n&&o.unbind(this.el,e,n)}},n.prototype.unbindAll=function(){for(var e in this._events)this.unbindAllOf(e)},n.prototype.unbindAllOf=function(e){var t=this._events[e];if(t)for(var s in t)this.unbind(e,s)}}),e.register("component-indexof/index.js",function(e,t,s){s.exports=function(e,t){if(e.indexOf)return e.indexOf(t);for(var s=0;e.length>s;++s)if(e[s]===t)return s;return-1}}),e.register("component-classes/index.js",function(e,t,s){function n(e){if(!e)throw Error("A DOM element reference is required");this.el=e,this.list=e.classList}var i=t("indexof"),o=/\s+/,r=Object.prototype.toString;s.exports=function(e){return new n(e)},n.prototype.add=function(e){if(this.list)return this.list.add(e),this;var t=this.array(),s=i(t,e);return~s||t.push(e),this.el.className=t.join(" "),this},n.prototype.remove=function(e){if("[object RegExp]"==r.call(e))return this.removeMatching(e);if(this.list)return this.list.remove(e),this;var t=this.array(),s=i(t,e);return~s&&t.splice(s,1),this.el.className=t.join(" "),this},n.prototype.removeMatching=function(e){for(var t=this.array(),s=0;t.length>s;s++)e.test(t[s])&&this.remove(t[s]);return this},n.prototype.toggle=function(e,t){return this.list?(t!==void 0?t!==this.list.toggle(e,t)&&this.list.toggle(e):this.list.toggle(e),this):(t!==void 0?t?this.add(e):this.remove(e):this.has(e)?this.remove(e):this.add(e),this)},n.prototype.array=function(){var e=this.el.className.replace(/^\s+|\s+$/g,""),t=e.split(o);return""===t[0]&&t.shift(),t},n.prototype.has=n.prototype.contains=function(e){return this.list?this.list.contains(e):!!~i(this.array(),e)}}),e.register("component-emitter/index.js",function(e,t,s){function n(e){return e?i(e):void 0}function i(e){for(var t in n.prototype)e[t]=n.prototype[t];return e}s.exports=n,n.prototype.on=n.prototype.addEventListener=function(e,t){return this._callbacks=this._callbacks||{},(this._callbacks[e]=this._callbacks[e]||[]).push(t),this},n.prototype.once=function(e,t){function s(){n.off(e,s),t.apply(this,arguments)}var n=this;return this._callbacks=this._callbacks||{},s.fn=t,this.on(e,s),this},n.prototype.off=n.prototype.removeListener=n.prototype.removeAllListeners=n.prototype.removeEventListener=function(e,t){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var s=this._callbacks[e];if(!s)return this;if(1==arguments.length)return delete this._callbacks[e],this;for(var n,i=0;s.length>i;i++)if(n=s[i],n===t||n.fn===t){s.splice(i,1);break}return this},n.prototype.emit=function(e){this._callbacks=this._callbacks||{};var t=[].slice.call(arguments,1),s=this._callbacks[e];if(s){s=s.slice(0);for(var n=0,i=s.length;i>n;++n)s[n].apply(this,t)}return this},n.prototype.listeners=function(e){return this._callbacks=this._callbacks||{},this._callbacks[e]||[]},n.prototype.hasListeners=function(e){return!!this.listeners(e).length}}),e.register("ui-component-mouse/index.js",function(e,t,s){function n(e,t){this.obj=t||{},this.el=e}var i=t("emitter"),o=t("event");s.exports=function(e,t){return new n(e,t)},i(n.prototype),n.prototype.bind=function(){function e(i){s.onmouseup&&s.onmouseup(i),o.unbind(document,"mousemove",t),o.unbind(document,"mouseup",e),n.emit("up",i)}function t(e){s.onmousemove&&s.onmousemove(e),n.emit("move",e)}var s=this.obj,n=this;return n.down=function(i){s.onmousedown&&s.onmousedown(i),o.bind(document,"mouseup",e),o.bind(document,"mousemove",t),n.emit("down",i)},o.bind(this.el,"mousedown",n.down),this},n.prototype.unbind=function(){o.unbind(this.el,"mousedown",this.down),this.down=null}}),e.register("abpetkov-percentage-calc/percentage-calc.js",function(e){e.isNumber=function(e){return"number"==typeof e?!0:!1},e.of=function(t,s){return e.isNumber(t)&&e.isNumber(s)?t/100*s:void 0},e.from=function(t,s){return e.isNumber(t)&&e.isNumber(s)?100*(t/s):void 0}}),e.register("abpetkov-closest-num/closest-num.js",function(e){e.find=function(e,t){var s=null,n=null,o=t[0];for(i=0;t.length>i;i++)s=Math.abs(e-o),n=Math.abs(e-t[i]),s>n&&(o=t[i]);return o}}),e.register("vesln-super/lib/super.js",function(e,t,s){function n(){var t=i.call(arguments);if(t.length)return"function"!=typeof t[0]?e.merge(t):(e.inherits.apply(null,t),void 0)}var i=Array.prototype.slice,e=s.exports=n;e.extend=function(t,s){var n=this,i=function(){return n.apply(this,arguments)};return e.merge([i,this]),e.inherits(i,this),t&&e.merge([i.prototype,t]),s&&e.merge([i,s]),i.extend=this.extend,i},e.inherits=function(e,t){e.super_=t,Object.create?e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}):(e.prototype=new t,e.prototype.constructor=e)},e.merge=function(e){for(var t=2===e.length?e.shift():{},s=null,n=0,i=e.length;i>n;n++){s=e[n];for(var o in s)s.hasOwnProperty(o)&&(t[o]=s[o])}return t}}),e.register("powerange/lib/powerange.js",function(e,t,s){var n=(t("./main"),t("./horizontal")),i=t("./vertical"),o={callback:function(){},decimal:!1,disable:!1,disableOpacity:.5,hideRange:!1,klass:"",min:0,max:100,start:null,step:null,vertical:!1};s.exports=function(e,t){t=t||{};for(var s in o)null==t[s]&&(t[s]=o[s]);return t.vertical?new i(e,t):new n(e,t)}}),e.register("powerange/lib/main.js",function(e,t,s){function n(e,t){return this instanceof n?(this.element=e,this.options=t||{},this.slider=this.create("span","range-bar"),null!==this.element&&"text"===this.element.type&&this.init(),void 0):new n(e,t)}var o=t("mouse"),r=t("events"),a=t("classes"),l=t("percentage-calc");s.exports=n,n.prototype.bindEvents=function(){this.handle=this.slider.querySelector(".range-handle"),this.touch=r(this.handle,this),this.touch.bind("touchstart","onmousedown"),this.touch.bind("touchmove","onmousemove"),this.touch.bind("touchend","onmouseup"),this.mouse=o(this.handle,this),this.mouse.bind()},n.prototype.hide=function(){this.element.style.display="none"},n.prototype.append=function(){var e=this.generate();this.insertAfter(this.element,e)},n.prototype.generate=function(){var e={handle:{type:"span",selector:"range-handle"},min:{type:"span",selector:"range-min"},max:{type:"span",selector:"range-max"},quantity:{type:"span",selector:"range-quantity"}};for(var t in e)if(e.hasOwnProperty(t)){var s=this.create(e[t].type,e[t].selector);this.slider.appendChild(s)}return this.slider},n.prototype.create=function(e,t){var s=document.createElement(e);return s.className=t,s},n.prototype.insertAfter=function(e,t){e.parentNode.insertBefore(t,e.nextSibling)},n.prototype.extraClass=function(e){this.options.klass&&a(this.slider).add(e)},n.prototype.setRange=function(e,t){"number"!=typeof e||"number"!=typeof t||this.options.hideRange||(this.slider.querySelector(".range-min").innerHTML=e,this.slider.querySelector(".range-max").innerHTML=t)},n.prototype.setValue=function(e,t){var s=l.from(parseFloat(e),t),n=l.of(s,this.options.max-this.options.min)+this.options.min,i=!1;n=this.options.decimal?Math.round(100*n)/100:Math.round(n),i=this.element.value!=n?!0:!1,this.element.value=n,this.options.callback(),i&&this.changeEvent()},n.prototype.step=function(e,t){var s=e-t,n=l.from(this.checkStep(this.options.step),this.options.max-this.options.min),o=l.of(n,s),r=[];for(i=0;s>=i;i+=o)r.push(i);return this.steps=r,this.steps},n.prototype.checkValues=function(e){this.options.min>e&&(this.options.start=this.options.min),e>this.options.max&&(this.options.start=this.options.max),this.options.min>=this.options.max&&(this.options.min=this.options.max)},n.prototype.checkStep=function(e){return 0>e&&(e=Math.abs(e)),this.options.step=e,this.options.step},n.prototype.disable=function(){(this.options.min==this.options.max||this.options.min>this.options.max||this.options.disable)&&(this.mouse.unbind(),this.touch.unbind(),this.slider.style.opacity=this.options.disableOpacity,a(this.handle).add("range-disabled"))},n.prototype.unselectable=function(e,t){a(this.slider).has("unselectable")||t!==!0?a(this.slider).remove("unselectable"):a(this.slider).add("unselectable")},n.prototype.changeEvent=function(){if("function"!=typeof Event&&document.fireEvent)this.element.fireEvent("onchange");else{var e=document.createEvent("HTMLEvents");e.initEvent("change",!1,!0),this.element.dispatchEvent(e)}},n.prototype.init=function(){this.hide(),this.append(),this.bindEvents(),this.extraClass(this.options.klass),this.checkValues(this.options.start),this.setRange(this.options.min,this.options.max),this.disable()}}),e.register("powerange/lib/horizontal.js",function(e,t,s){function n(){a.apply(this,arguments),this.options.step&&this.step(this.slider.offsetWidth,this.handle.offsetWidth),this.setStart(this.options.start)}var i=t("super"),o=t("closest-num"),r=t("percentage-calc"),a=t("./main");s.exports=n,i(n,a),n.prototype.setStart=function(e){var t=null===e?this.options.min:e,s=r.from(t-this.options.min,this.options.max-this.options.min)||0,n=r.of(s,this.slider.offsetWidth-this.handle.offsetWidth),i=this.options.step?o.find(n,this.steps):n;this.setPosition(i),this.setValue(this.handle.style.left,this.slider.offsetWidth-this.handle.offsetWidth)},n.prototype.setPosition=function(e){this.handle.style.left=e+"px",this.slider.querySelector(".range-quantity").style.width=e+"px"},n.prototype.onmousedown=function(e){e.touches&&(e=e.touches[0]),this.startX=e.clientX,this.handleOffsetX=this.handle.offsetLeft,this.restrictHandleX=this.slider.offsetWidth-this.handle.offsetWidth,this.unselectable(this.slider,!0)},n.prototype.onmousemove=function(e){e.preventDefault(),e.touches&&(e=e.touches[0]);var t=this.handleOffsetX+e.clientX-this.startX,s=this.steps?o.find(t,this.steps):t;0>=t?this.setPosition(0):t>=this.restrictHandleX?this.setPosition(this.restrictHandleX):this.setPosition(s),this.setValue(this.handle.style.left,this.slider.offsetWidth-this.handle.offsetWidth)},n.prototype.onmouseup=function(){this.unselectable(this.slider,!1)}}),e.register("powerange/lib/vertical.js",function(e,t,s){function n(){l.apply(this,arguments),o(this.slider).add("vertical"),this.options.step&&this.step(this.slider.offsetHeight,this.handle.offsetHeight),this.setStart(this.options.start)}var i=t("super"),o=t("classes"),r=t("closest-num"),a=t("percentage-calc"),l=t("./main");s.exports=n,i(n,l),n.prototype.setStart=function(e){var t=null===e?this.options.min:e,s=a.from(t-this.options.min,this.options.max-this.options.min)||0,n=a.of(s,this.slider.offsetHeight-this.handle.offsetHeight),i=this.options.step?r.find(n,this.steps):n;this.setPosition(i),this.setValue(this.handle.style.bottom,this.slider.offsetHeight-this.handle.offsetHeight)},n.prototype.setPosition=function(e){this.handle.style.bottom=e+"px",this.slider.querySelector(".range-quantity").style.height=e+"px"},n.prototype.onmousedown=function(e){e.touches&&(e=e.touches[0]),this.startY=e.clientY,this.handleOffsetY=this.slider.offsetHeight-this.handle.offsetHeight-this.handle.offsetTop,this.restrictHandleY=this.slider.offsetHeight-this.handle.offsetHeight,this.unselectable(this.slider,!0)},n.prototype.onmousemove=function(e){e.preventDefault(),e.touches&&(e=e.touches[0]);var t=this.handleOffsetY+this.startY-e.clientY,s=this.steps?r.find(t,this.steps):t;0>=t?this.setPosition(0):t>=this.restrictHandleY?this.setPosition(this.restrictHandleY):this.setPosition(s),this.setValue(this.handle.style.bottom,this.slider.offsetHeight-this.handle.offsetHeight)},n.prototype.onmouseup=function(){this.unselectable(this.slider,!1)}}),e.alias("component-events/index.js","powerange/deps/events/index.js"),e.alias("component-events/index.js","events/index.js"),e.alias("component-event/index.js","component-events/deps/event/index.js"),e.alias("component-delegate/index.js","component-events/deps/delegate/index.js"),e.alias("discore-closest/index.js","component-delegate/deps/closest/index.js"),e.alias("discore-closest/index.js","component-delegate/deps/closest/index.js"),e.alias("component-matches-selector/index.js","discore-closest/deps/matches-selector/index.js"),e.alias("component-query/index.js","component-matches-selector/deps/query/index.js"),e.alias("discore-closest/index.js","discore-closest/index.js"),e.alias("component-event/index.js","component-delegate/deps/event/index.js"),e.alias("component-classes/index.js","powerange/deps/classes/index.js"),e.alias("component-classes/index.js","classes/index.js"),e.alias("component-indexof/index.js","component-classes/deps/indexof/index.js"),e.alias("ui-component-mouse/index.js","powerange/deps/mouse/index.js"),e.alias("ui-component-mouse/index.js","mouse/index.js"),e.alias("component-emitter/index.js","ui-component-mouse/deps/emitter/index.js"),e.alias("component-event/index.js","ui-component-mouse/deps/event/index.js"),e.alias("abpetkov-percentage-calc/percentage-calc.js","powerange/deps/percentage-calc/percentage-calc.js"),e.alias("abpetkov-percentage-calc/percentage-calc.js","powerange/deps/percentage-calc/index.js"),e.alias("abpetkov-percentage-calc/percentage-calc.js","percentage-calc/index.js"),e.alias("abpetkov-percentage-calc/percentage-calc.js","abpetkov-percentage-calc/index.js"),e.alias("abpetkov-closest-num/closest-num.js","powerange/deps/closest-num/closest-num.js"),e.alias("abpetkov-closest-num/closest-num.js","powerange/deps/closest-num/index.js"),e.alias("abpetkov-closest-num/closest-num.js","closest-num/index.js"),e.alias("abpetkov-closest-num/closest-num.js","abpetkov-closest-num/index.js"),e.alias("vesln-super/lib/super.js","powerange/deps/super/lib/super.js"),e.alias("vesln-super/lib/super.js","powerange/deps/super/index.js"),e.alias("vesln-super/lib/super.js","super/index.js"),e.alias("vesln-super/lib/super.js","vesln-super/index.js"),e.alias("powerange/lib/powerange.js","powerange/index.js"),"object"==typeof exports?module.exports=e("powerange"):"function"==typeof define&&define.amd?define([],function(){return e("powerange")}):this.Powerange=e("powerange")})();
\ No newline at end of file
diff --git a/web/lib/ref/ref.css b/web/lib/ref/ref.css
new file mode 100644
index 0000000..7b2048d
--- /dev/null
+++ b/web/lib/ref/ref.css
@@ -0,0 +1,511 @@
+.ref{
+ font: normal normal 12px/18px Consolas, "Liberation Mono", Menlo, "Courier New", Courier, monospace;
+ color: #333;
+}
+
+/* reset default styles for these elements */
+.ref i,
+.ref span,
+.ref r,
+.ref a{
+ font-style: inherit;
+ font-weight: inherit;
+ margin: 0;
+ padding: 0;
+ text-align: left;
+ display: inline;
+ text-decoration: inherit;
+ white-space: normal;
+ background: none;
+}
+
+/* meta content (used to generate tooltips) */
+.ref > div,
+.ref > t{
+ display: none;
+}
+
+/* show help cursor when mouse is over an entity with a tooltip */
+.ref [data-tip],
+.ref [h]{
+ cursor: help;
+}
+
+/* pointer if inside a link */
+.ref a > [data-tip],
+.ref a > [h]{
+ cursor: pointer;
+}
+
+/* links */
+.ref a{
+ color: inherit;
+ border-bottom: 1px dotted transparent;
+ border-color: inherit;
+}
+
+/* tooltip; note that the js overrides top/left properties, this is why we use margin */
+#rTip{
+ display: none;
+ position: absolute;
+ z-index: 99999;
+ font-size: 12px;
+ white-space: pre;
+ text-align: left;
+ text-shadow: 0 -1px 0 #191919;
+ line-height: 16px;
+ background: #222;
+ color: #888;
+ border: 0;
+ border-radius: 4px;
+ opacity: 0.90;
+ box-shadow:0 0 4px rgba(0,0,0, 0.25);
+ -webkit-transition: opacity .25s, margin .25s;
+ transition: opacity .25s, margin .25s;
+}
+
+#rTip.visible{
+ display: table;
+ margin: 10px 0 0 15px;
+}
+
+#rTip.visible.fadingOut{
+ opacity: 0;
+ margin: 20px 0 0 25px;
+}
+
+#rTip [data-cell],
+#rTip [c]{
+ padding: 2px 7px;
+}
+
+#rTip [data-title], #rTip [data-desc]{
+ padding: 8px;
+ display: block;
+ color: #ccc;
+}
+
+#rTip [data-desc]{
+ padding-top: 0px;
+ color: #777;
+}
+
+#rTip [data-cell][data-varType],
+#rTip [c][data-varType]{
+ padding: 10px;
+ background: #333;
+ box-shadow: inset -1px 0 0 #444;
+ border-right:1px solid #111;
+ border-top-left-radius: 4px;
+ border-bottom-left-radius: 4px;
+}
+
+#rTip [data-cell][data-sub],
+#rTip [c][data-sub]{
+ padding: 8px 10px 10px 10px;
+ background: #333;
+ box-shadow: inset 0 1px 0 #444;
+ border-top:1px solid #111;
+ border-bottom-right-radius: 4px;
+ border-bottom-left-radius: 4px;
+}
+
+#rTip [data-table] [data-cell]:first-child,
+#rTip [t] [c]:first-child{
+ font: bold 11px Helvetica, Arial;
+ color: #888;
+}
+
+#rTip [data-table] [data-cell]:nth-child(2),
+#rTip [t] [c]:nth-child(2){
+ color: #edd078;
+}
+
+
+
+
+/* base entity - can be nested */
+.ref span, .ref r{
+ white-space: pre;
+ display: inline;
+}
+
+/* key-value dividers, property & method modifiers etc. */
+.ref i{
+ white-space: pre;
+ color: #aaa;
+}
+
+/* source expression (input) */
+.ref [data-input]{
+ margin: 2px 0 0;
+ padding: 2px 7px 3px 4px;
+ display: block;
+ color: #ccc;
+ background-color: #333;
+ background-image: -webkit-linear-gradient(top, #444, #333);
+ background-image: linear-gradient(top, #444, #333);
+ border-radius: 4px 4px 0 0;
+ border-bottom: 1px solid #fff;
+}
+
+.ref [data-backtrace]{
+ float: right;
+}
+
+.ref [data-output]{
+ background: #f9f9f9;
+ border: 1px solid #eee;
+ border-top: 0;
+ border-radius: 0 0 4px 4px;
+ box-shadow: inset 0px 4px 4px #f3f3f3, inset 0px -8px 8px #fff;
+ padding: 2px 5px;
+ margin: 0 0 4px;
+ text-shadow: 0 1px 0 #fff;
+ display: block;
+}
+
+/* expand/collapse toggle link for groups */
+.ref [data-toggle]{
+ display: inline-block;
+ vertical-align: -3px;
+ margin-left: 2px;
+ width: 0px;
+ height: 0px;
+ border-style: solid;
+ border-width: 7px 0 7px 10px;
+ border-color: transparent transparent transparent #CC0033;
+ cursor: pointer;
+ -webkit-transition: all ease-in .15s;
+ transition: all ease-in .15s;
+}
+
+/* collapse graphic */
+.ref [data-toggle][data-exp]{
+ -webkit-transform: rotate(90deg);
+ -ms-transform: rotate(90deg);
+ transform: rotate(90deg);
+}
+
+.ref [data-group],
+.ref [g]{
+ display: none;
+}
+
+.ref [data-toggle][data-exp] ~ [data-group],
+.ref [data-toggle][data-exp] ~ [g]{
+ display: block;
+}
+
+/* group sections */
+.ref [data-table],
+.ref [t]{
+ display: table;
+}
+
+/* section titles */
+.ref [data-tHead]{
+ font: bold 11px Helvetica, Arial;
+ color: #bcbcbc;
+ text-transform: lowercase;
+ margin: 12px 0 2px 10px;
+ display: block;
+}
+
+/* emulate a table for displaying array & object members */
+/* section row */
+.ref [data-row],
+.ref [r]{
+ display: table-row;
+}
+
+/* zebra-like rows */
+.ref [data-output] [data-row]:nth-child(odd){background: #f4f4f4;}
+.ref [data-output] [data-row]:nth-child(even){background: #f9f9f9;}
+.ref [data-output] [r]:nth-child(odd){background: #f4f4f4;}
+.ref [data-output] [r]:nth-child(even){background: #f9f9f9;}
+
+/* section cells */
+.ref [data-cell],
+.ref [c]{
+ display: table-cell;
+ width: auto;
+ vertical-align: top;
+ padding: 1px 0 1px 10px;
+}
+
+/* last cell of a row (forces table to adjust width like we want to) */
+.ref [data-output] [data-table],
+.ref [data-output] [t],
+.ref [data-output] [data-cell]:last-child,
+.ref [data-output] [c]:last-child{
+ width: 100%;
+}
+
+
+
+/* tag-like appearance for boolean, null and resource types */
+.ref [data-true],
+.ref [data-false],
+.ref [data-null],
+.ref [data-unknown],
+.ref [data-resource],
+.ref [data-match],
+.ref [m]{
+ font: bold 11px Helvetica, Arial;
+ color: #fff;
+ padding: 1px 3px;
+ text-transform: lowercase;
+ text-shadow: none;
+ border-radius: 2px;
+ margin-right: 5px;
+ background-color: #eee;
+ background-image: -webkit-linear-gradient(top, rgba(255,255,255,0.1) 40%,rgba(0,0,0,0.1) 100%);
+ background-image: linear-gradient(to bottom, rgba(255,255,255,0.1) 40%,rgba(0,0,0,0.1) 100%);
+}
+
+/* string matches */
+.ref [data-match],
+.ref [m]{
+ background-color: #d78035;
+}
+
+/* boolean true */
+.ref [data-true]{
+ background-color: #339900;
+}
+
+/* boolean false */
+.ref [data-false]{
+ background-color: #CC0033;
+ color: #fff;
+}
+
+/* null value */
+.ref [data-null],
+.ref [data-unknown]{
+ background-color: #eee;
+ color: #999;
+ text-shadow: inherit;
+}
+
+/* resources */
+.ref [data-resource]{
+ background-color: #0057ae;
+}
+
+.ref [data-resourceProp]{
+ font: bold 11px Helvetica, Arial;
+ color: #999;
+}
+
+/* integer or double values */
+.ref [data-integer],
+.ref [data-double]{
+ color: #0099CC;
+}
+
+/* string values */
+.ref [data-string]{
+ background: #e8f0e1;
+ color: #669933;
+ padding: 3px 1px;
+
+ /* prevent long strings from breaking the page layout */
+ white-space: -moz-pre-wrap; /* Mozilla */
+ white-space: -hp-pre-wrap; /* HP printers */
+ white-space: -o-pre-wrap; /* Opera 7 */
+ white-space: -pre-wrap; /* Opera 4-6 */
+ white-space: pre-wrap; /* CSS 2.1 */
+ white-space: pre-line; /* CSS 3 (and 2.1 as well, actually) */
+ word-wrap: break-word; /* IE */
+ word-break: break-all;
+}
+
+.ref [data-string][data-special]{
+ background: none;
+ padding: 0;
+}
+
+.ref [data-string][data-special] i{
+ background: #faf3dc;
+ color: #d78035;
+}
+
+/* arrays & objects */
+.ref [data-array],
+.ref [data-array] ~ i,
+.ref [data-object],
+.ref [data-object] ~ i,
+.ref [data-resource] ~ i{
+ color:#CC0033;
+}
+
+.ref [data-method]{
+ font-weight: bold;
+ color: #0057ae;
+}
+
+.ref [data-const][data-inherited],
+.ref [data-prop][data-inherited]{
+ color: #999;
+}
+
+.ref [data-prop][data-private],
+.ref [data-method][data-private]{
+ color: #CC0033;
+}
+
+/* inherited methods */
+.ref [data-method][data-inherited]{
+ font-weight: bold;
+ color: #6da5de;
+}
+
+/* method arguments */
+.ref [data-param]{
+ font-weight: normal;
+ color: #333;
+}
+
+/* optional method arguments */
+.ref [data-param][data-optional]{
+ font-style: italic;
+ font-weight: normal;
+ color: #aaa;
+}
+
+/* group info prefix */
+.ref [data-gLabel],
+.ref [gl]{
+ font: bold 11px Helvetica, Arial;
+ padding: 0 3px;
+ color: #333;
+}
+
+/* tiny bubbles that indicate visibility info or class features */
+.ref [data-mod]{
+ font: bold 11px Helvetica, Arial;
+ text-shadow: none;
+ color: #fff;
+}
+
+.ref [data-input] [data-mod]{
+ color: #444;
+}
+
+.ref [data-mod] span,
+.ref [data-mod] r{
+ display: inline-block;
+ margin: 0 2px;
+ width: 14px;
+ height: 14px;
+ text-align: center;
+ border-radius: 30px;
+ line-height: 15px;
+}
+
+.ref [data-mod-interface],
+.ref [data-mod-abstract]{
+ background: #baed78;
+}
+
+.ref [data-mod-anonymous]{
+ background: #444;
+}
+
+.ref [data-mod-protected]{
+ background: #edd078;
+}
+
+.ref [data-mod-private]{
+ background: #eea8b9;
+}
+
+.ref [data-mod-iterateable]{
+ background: #d5dea5;
+}
+
+.ref [data-mod-cloneable]{
+ background: #bdd7d1;
+}
+
+.ref [data-mod-final]{
+ background: #78bded;
+}
+
+/* regular expression (colors partially match RegexBuddy and RegexPal) */
+.ref [data-regex]{
+ font-weight: bold;
+ text-shadow: none;
+ padding: 1px 0;
+ background: #e6e6e6;
+ word-wrap: break-word;
+}
+
+/* char class */
+.ref [data-regex-chr]{
+ background: #ffc080;
+ color: #694c07;
+}
+
+.ref [data-regex-chr-meta]{background: #e0a060;} /* char class: metasequence */
+.ref [data-regex-chr-range]{background: #ffcf9b;} /* char class: range-hyphen */
+
+/* metasequence */
+.ref [data-regex-meta]{
+ background: #80c0ff;
+ color: #105f8c;
+}
+
+/* group: depth 1 */
+.ref [data-regex-g1]{
+ background: #00c000;
+ color: #fff;
+}
+
+/* group: depth 2 */
+.ref [data-regex-g2]{
+ background: #c3e86c;
+ color: #648c1c;
+}
+
+/* group: depth 3 */
+.ref [data-regex-g3]{
+ background: #008000;
+ color: #fff;
+}
+
+/* group: depth 4 */
+.ref [data-regex-g4]{
+ background: #6dcb99;
+ color: #fff;
+}
+
+/* group: depth 5 */
+.ref [data-regex-g5]{
+ background: #00ff00;
+ color: #2c8e24;
+}
+
+.ref [data-error]{
+ background: #CC0033;
+ color: #fff;
+ border-radius: 0 0 4px 4px;
+ padding: 2px 5px;
+ margin: 0 0 4px;
+ display: block;
+}
+
+/* make labels and less-relevant text non-selectable */
+.ref [data-match],
+.ref [m],
+.ref [data-tHead],
+.ref [data-gLabel],
+.ref [gl],
+.ref [data-mod]{
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
diff --git a/web/lib/ref/ref.js b/web/lib/ref/ref.js
new file mode 100644
index 0000000..92184a9
--- /dev/null
+++ b/web/lib/ref/ref.js
@@ -0,0 +1,73 @@
+window.addEventListener('load', function(){
+
+ var tip = document.createElement('div'),
+ refs = document.querySelectorAll('.ref');
+
+ for(var i = 0, m = refs.length; i < m; i++){
+ var kbds = refs[i].querySelectorAll('[data-toggle]'),
+ tippable = refs[i].querySelectorAll('[data-tip],[h]'),
+ tips = refs[i].querySelectorAll('div, t');
+
+ for(var j = 0, n = kbds.length; j < n; j++){
+ if(kbds[j].parentNode !== refs[i])
+ kbds[j].onclick = function(e){
+ ('exp' in this.dataset) ? delete this.dataset.exp : this.dataset.exp = 1;
+ }
+ }
+
+ [].filter.call(tips, function(node){
+ return node.parentNode == refs[i];
+ });
+
+ for(var j = 0, n = tippable.length; j < n; j++){
+ tippable[j].tipRef = tips[tippable[j].dataset.tip] || tips[tippable[j].getAttribute('h')];
+ tippable[j].onmouseover = function(){
+ tip.className = 'ref visible';
+ tip.innerHTML = this.tipRef.innerHTML;
+ window.clearTimeout(tip.fadeOut);
+ };
+ tippable[j].onmouseout = function(){
+ tip.className = 'ref visible fadingOut';
+ tip.fadeOut = window.setTimeout(function(){
+ tip.innerHTML = '';
+ tip.className = '';
+ }, 250);
+ };
+ }
+
+ refs[i].onmousemove = function(e){
+ if(tip.className.indexOf('visible') < 0)
+ return;
+ tip.style.top = ((document.documentElement.clientHeight - e.clientY) < tip.offsetHeight + 20 ? Math.max(e.pageY - tip.offsetHeight, 0) : e.pageY) + 'px';
+ tip.style.left = ((document.documentElement.clientWidth - e.clientX) < tip.offsetWidth + 20 ? Math.max(e.pageX - tip.offsetWidth, 0) : e.pageX) + 'px';
+ };
+ }
+
+ tip.id = 'rTip';
+ document.body.appendChild(tip);
+
+ window.addEventListener('keydown', function(e){
+ if((e.keyCode != 88) || (['input', 'textarea', 'select'].indexOf(e.target.tagName.toLowerCase()) > -1))
+ return;
+
+ e.preventDefault();
+
+ if(e.ctrlKey && e.keyCode == 88){
+ var d = refs[0].style.display !== 'none' ? 'none' : 'block';
+ for(var i = 0, n = refs.length; i < n; i++)
+ refs[i].style.display = d;
+
+ return;
+ }
+
+ var kbds = document.querySelectorAll('.ref [data-toggle]'),
+ m = kbds.length,
+ partlyExp = document.querySelectorAll('.ref [data-toggle][data-exp]').length !== m;
+
+ for(var i = 0; i < m; i++)
+ partlyExp ? (kbds[i].dataset.exp = 1) : (delete kbds[i].dataset.exp);
+
+ });
+
+});
+
diff --git a/web/lib/ref/ref.php b/web/lib/ref/ref.php
new file mode 100644
index 0000000..c5b84d2
--- /dev/null
+++ b/web/lib/ref/ref.php
@@ -0,0 +1,2961 @@
+
REF';
+
+ $ref = new ref($format);
+
+ if($capture)
+ ob_start();
+
+ foreach($args as $index => $arg)
+ $ref->query($arg, $expressions ? $expressions[$index] : null);
+
+ // return the results if this function was called with the error suppression operator
+ if($capture)
+ return ob_get_clean();
+
+ // stop the script if this function was called with the bitwise not operator
+ if(in_array('~', $options, true) && ($format === 'html')){
+ print '';
+ exit(0);
+ }
+}
+
+
+
+/**
+ * Shortcut to ref, plain text mode
+ *
+ * @param mixed $args
+ * @return void|string
+ */
+function rt(){
+ $args = func_get_args();
+ $options = array();
+ $output = '';
+ $expressions = ref::getInputExpressions($options);
+ $capture = in_array('@', $options, true);
+ $ref = new ref((php_sapi_name() !== 'cli') || $capture ? 'text' : 'cliText');
+
+ if(func_num_args() !== count($expressions))
+ $expressions = null;
+
+ if(!headers_sent())
+ header('Content-Type: text/plain; charset=utf-8');
+
+ if($capture)
+ ob_start();
+
+ foreach($args as $index => $arg)
+ $ref->query($arg, $expressions ? $expressions[$index] : null);
+
+ if($capture)
+ return ob_get_clean();
+
+ if(in_array('~', $options, true))
+ exit(0);
+}
+
+
+
+/**
+ * REF is a nicer alternative to PHP's print_r() / var_dump().
+ *
+ * @version 1.0
+ * @author digitalnature - http://digitalnature.eu
+ */
+class ref{
+
+ const
+
+ MARKER_KEY = '_phpRefArrayMarker_';
+
+
+
+ protected static
+
+ /**
+ * CPU time used for processing
+ *
+ * @var array
+ */
+ $time = 0,
+
+ /**
+ * Configuration (+ default values)
+ *
+ * @var array
+ */
+ $config = array(
+
+ // initially expanded levels (for HTML mode only)
+ 'expLvl' => 1,
+
+ // depth limit (0 = no limit);
+ // this is not related to recursion
+ 'maxDepth' => 6,
+
+ // show the place where r() has been called from
+ 'showBacktrace' => true,
+
+ // display iterator contents
+ 'showIteratorContents' => false,
+
+ // display extra information about resources
+ 'showResourceInfo' => true,
+
+ // display method and parameter list on objects
+ 'showMethods' => true,
+
+ // display private properties / methods
+ 'showPrivateMembers' => false,
+
+ // peform string matches (date, file, functions, classes, json, serialized data, regex etc.)
+ // note: seriously slows down queries on large amounts of data
+ 'showStringMatches' => true,
+
+ // shortcut functions used to access the query method below;
+ // if they are namespaced, the namespace must be present as well (methods are not supported)
+ 'shortcutFunc' => array('r', 'rt'),
+
+ // custom/external formatters (as associative array: format => className)
+ 'formatters' => array(),
+
+ // stylesheet path (for HTML only);
+ // 'false' means no styles
+ 'stylePath' => '{:dir}/ref.css',
+
+ // javascript path (for HTML only);
+ // 'false' means no js
+ 'scriptPath' => '{:dir}/ref.js',
+
+ // display url info via cURL
+ 'showUrls' => false,
+
+ // stop evaluation after this amount of time (seconds)
+ 'timeout' => 10,
+
+ // whether to produce W3c-valid HTML,
+ // or unintelligible, but optimized markup that takes less space
+ 'validHtml' => false,
+ ),
+
+ /**
+ * Some environment variables
+ * used to determine feature support
+ *
+ * @var array
+ */
+ $env = array(),
+
+ /**
+ * Timeout point
+ *
+ * @var bool
+ */
+ $timeout = -1,
+
+ $debug = array(
+ 'cacheHits' => 0,
+ 'objects' => 0,
+ 'arrays' => 0,
+ 'scalars' => 0,
+ );
+
+
+ protected
+
+ /**
+ * Output formatter of this instance
+ *
+ * @var RFormatter
+ */
+ $fmt = null,
+
+ /**
+ * Start time of the current instance
+ *
+ * @var float
+ */
+ $startTime = 0,
+
+ /**
+ * Internally created objects
+ *
+ * @var SplObjectStorage
+ */
+ $intObjects = null;
+
+
+
+ /**
+ * Constructor
+ *
+ * @param string|RFormatter $format Output format ID, or formatter instance defaults to 'html'
+ */
+ public function __construct($format = 'html'){
+
+ static $didIni = false;
+
+ if(!$didIni){
+ $didIni = true;
+ foreach(array_keys(static::$config) as $key){
+ $iniVal = get_cfg_var('ref.' . $key);
+ if($iniVal !== false)
+ static::$config[$key] = $iniVal;
+ }
+
+ }
+
+ if($format instanceof RFormatter){
+ $this->fmt = $format;
+
+ }else{
+ $format = isset(static::$config['formatters'][$format]) ? static::$config['formatters'][$format] : 'R' . ucfirst($format) . 'Formatter';
+
+ if(!class_exists($format, false))
+ throw new \Exception(sprintf('%s class not found', $format));
+
+ $this->fmt = new $format();
+ }
+
+ if(static::$env)
+ return;
+
+ static::$env = array(
+
+ // php 5.4+ ?
+ 'is54' => version_compare(PHP_VERSION, '5.4') >= 0,
+
+ // php 5.4.6+ ?
+ 'is546' => version_compare(PHP_VERSION, '5.4.6') >= 0,
+
+ // php 5.6+
+ 'is56' => version_compare(PHP_VERSION, '5.6') >= 0,
+
+ // php 7.0+ ?
+ 'is7' => version_compare(PHP_VERSION, '7.0') >= 0,
+
+ // curl extension running?
+ 'curlActive' => function_exists('curl_version'),
+
+ // is the 'mbstring' extension active?
+ 'mbStr' => function_exists('mb_detect_encoding'),
+
+ // @see: https://bugs.php.net/bug.php?id=52469
+ 'supportsDate' => (strncasecmp(PHP_OS, 'WIN', 3) !== 0) || (version_compare(PHP_VERSION, '5.3.10') >= 0),
+ );
+ }
+
+
+
+ /**
+ * Enforce proper use of this class
+ *
+ * @param string $name
+ */
+ public function __get($name){
+ throw new \Exception(sprintf('No such property: %s', $name));
+ }
+
+
+
+ /**
+ * Enforce proper use of this class
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ public function __set($name, $value){
+ throw new \Exception(sprintf('Cannot set %s. Not allowed', $name));
+ }
+
+
+
+ /**
+ * Generate structured information about a variable/value/expression (subject)
+ *
+ * Output is flushed to the screen
+ *
+ * @param mixed $subject
+ * @param string $expression
+ */
+ public function query($subject, $expression = null){
+
+ if(static::$timeout > 0)
+ return;
+
+ $this->startTime = microtime(true);
+
+ $this->intObjects = new \SplObjectStorage();
+
+ $this->fmt->startRoot();
+ $this->fmt->startExp();
+ $this->evaluateExp($expression);
+ $this->fmt->endExp();
+ $this->evaluate($subject);
+ $this->fmt->endRoot();
+ $this->fmt->flush();
+
+ static::$time += microtime(true) - $this->startTime;
+ }
+
+
+
+
+ /**
+ * Executes a function the given number of times and returns the elapsed time.
+ *
+ * Keep in mind that the returned time includes function call overhead (including
+ * microtime calls) x iteration count. This is why this is better suited for
+ * determining which of two or more functions is the fastest, rather than
+ * finding out how fast is a single function.
+ *
+ * @param int $iterations Number of times the function will be executed
+ * @param callable $function Function to execute
+ * @param mixed &$output If given, last return value will be available in this variable
+ * @return double Elapsed time
+ */
+ public static function timeFunc($iterations, $function, &$output = null){
+
+ $time = 0;
+
+ for($i = 0; $i < $iterations; $i++){
+ $start = microtime(true);
+ $output = call_user_func($function);
+ $time += microtime(true) - $start;
+ }
+
+ return round($time, 4);
+ }
+
+
+
+ /**
+ * Timer utility
+ *
+ * First call of this function will start the timer.
+ * The second call will stop the timer and return the elapsed time
+ * since the timer started.
+ *
+ * Multiple timers can be controlled simultaneously by specifying a timer ID.
+ *
+ * @since 1.0
+ * @param int $id Timer ID, optional
+ * @param int $precision Precision of the result, optional
+ * @return void|double Elapsed time, or void if the timer was just started
+ */
+ public static function timer($id = 1, $precision = 4){
+
+ static
+ $timers = array();
+
+ // check if this timer was started, and display the elapsed time if so
+ if(isset($timers[$id])){
+ $elapsed = round(microtime(true) - $timers[$id], $precision);
+ unset($timers[$id]);
+ return $elapsed;
+ }
+
+ // ID doesn't exist, start new timer
+ $timers[$id] = microtime(true);
+ }
+
+
+
+ /**
+ * Parses a DocBlock comment into a data structure.
+ *
+ * @link http://pear.php.net/manual/en/standards.sample.php
+ * @param string $comment DocBlock comment (must start with /**)
+ * @param string|null $key Field to return (optional)
+ * @return array|string|null Array containing all fields, array/string with the contents of
+ * the requested field, or null if the comment is empty/invalid
+ */
+ public static function parseComment($comment, $key = null){
+
+ $description = '';
+ $tags = array();
+ $tag = null;
+ $pointer = '';
+ $padding = 0;
+ $comment = preg_split('/\r\n|\r|\n/', '* ' . trim($comment, "/* \t\n\r\0\x0B"));
+
+ // analyze each line
+ foreach($comment as $line){
+
+ // drop any wrapping spaces
+ $line = trim($line);
+
+ // drop "* "
+ if($line !== '')
+ $line = substr($line, 2);
+
+ if(strpos($line, '@') !== 0){
+
+ // preserve formatting of tag descriptions,
+ // because they may span across multiple lines
+ if($tag !== null){
+ $trimmed = trim($line);
+
+ if($padding !== 0)
+ $trimmed = static::strPad($trimmed, static::strLen($line) - $padding, ' ', STR_PAD_LEFT);
+ else
+ $padding = static::strLen($line) - static::strLen($trimmed);
+
+ $pointer .= "\n{$trimmed}";
+ continue;
+ }
+
+ // tag definitions have not started yet; assume this is part of the description text
+ $description .= "\n{$line}";
+ continue;
+ }
+
+ $padding = 0;
+ $parts = explode(' ', $line, 2);
+
+ // invalid tag? (should we include it as an empty array?)
+ if(!isset($parts[1]))
+ continue;
+
+ $tag = substr($parts[0], 1);
+ $line = ltrim($parts[1]);
+
+ // tags that have a single component (eg. link, license, author, throws...);
+ // note that @throws may have 2 components, however most people use it like "@throws ExceptionClass if whatever...",
+ // which, if broken into two values, leads to an inconsistent description sentence
+ if(!in_array($tag, array('global', 'param', 'return', 'var'))){
+ $tags[$tag][] = $line;
+ end($tags[$tag]);
+ $pointer = &$tags[$tag][key($tags[$tag])];
+ continue;
+ }
+
+ // tags with 2 or 3 components (var, param, return);
+ $parts = explode(' ', $line, 2);
+ $parts[1] = isset($parts[1]) ? ltrim($parts[1]) : null;
+ $lastIdx = 1;
+
+ // expecting 3 components on the 'param' tag: type varName varDescription
+ if($tag === 'param'){
+ $lastIdx = 2;
+ if(in_array($parts[1][0], array('&', '$'), true)){
+ $line = ltrim(array_pop($parts));
+ $parts = array_merge($parts, explode(' ', $line, 2));
+ $parts[2] = isset($parts[2]) ? ltrim($parts[2]) : null;
+ }else{
+ $parts[2] = $parts[1];
+ $parts[1] = null;
+ }
+ }
+
+ $tags[$tag][] = $parts;
+ end($tags[$tag]);
+ $pointer = &$tags[$tag][key($tags[$tag])][$lastIdx];
+ }
+
+ // split title from the description texts at the nearest 2x new-line combination
+ // (note: loose check because 0 isn't valid as well)
+ if(strpos($description, "\n\n")){
+ list($title, $description) = explode("\n\n", $description, 2);
+
+ // if we don't have 2 new lines, try to extract first sentence
+ }else{
+ // in order for a sentence to be considered valid,
+ // the next one must start with an uppercase letter
+ $sentences = preg_split('/(?<=[.?!])\s+(?=[A-Z])/', $description, 2, PREG_SPLIT_NO_EMPTY);
+
+ // failed to detect a second sentence? then assume there's only title and no description text
+ $title = isset($sentences[0]) ? $sentences[0] : $description;
+ $description = isset($sentences[1]) ? $sentences[1] : '';
+ }
+
+ $title = ltrim($title);
+ $description = ltrim($description);
+
+ $data = compact('title', 'description', 'tags');
+
+ if(!array_filter($data))
+ return null;
+
+ if($key !== null)
+ return isset($data[$key]) ? $data[$key] : null;
+
+ return $data;
+ }
+
+
+
+ /**
+ * Split a regex into its components
+ *
+ * Based on "Regex Colorizer" by Steven Levithan (this is a translation from javascript)
+ *
+ * @link https://github.com/slevithan/regex-colorizer
+ * @link https://github.com/symfony/Finder/blob/master/Expression/Regex.php#L64-74
+ * @param string $pattern
+ * @return array
+ */
+ public static function splitRegex($pattern){
+
+ // detection attempt code from the Symfony Finder component
+ $maybeValid = false;
+ if(preg_match('/^(.{3,}?)([imsxuADU]*)$/', $pattern, $m)) {
+ $start = substr($m[1], 0, 1);
+ $end = substr($m[1], -1);
+
+ if(($start === $end && !preg_match('/[*?[:alnum:] \\\\]/', $start)) || ($start === '{' && $end === '}'))
+ $maybeValid = true;
+ }
+
+ if(!$maybeValid)
+ throw new \Exception('Pattern does not appear to be a valid PHP regex');
+
+ $output = array();
+ $capturingGroupCount = 0;
+ $groupStyleDepth = 0;
+ $openGroups = array();
+ $lastIsQuant = false;
+ $lastType = 1; // 1 = none; 2 = alternator
+ $lastStyle = null;
+
+ preg_match_all('/\[\^?]?(?:[^\\\\\]]+|\\\\[\S\s]?)*]?|\\\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9][0-9]*|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|c[A-Za-z]|[\S\s]?)|\((?:\?[:=!]?)?|(?:[?*+]|\{[0-9]+(?:,[0-9]*)?\})\??|[^.?*+^${[()|\\\\]+|./', $pattern, $matches);
+
+ $matches = $matches[0];
+
+ $getTokenCharCode = function($token){
+ if(strlen($token) > 1 && $token[0] === '\\'){
+ $t1 = substr($token, 1);
+
+ if(preg_match('/^c[A-Za-z]$/', $t1))
+ return strpos("ABCDEFGHIJKLMNOPQRSTUVWXYZ", strtoupper($t1[1])) + 1;
+
+ if(preg_match('/^(?:x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4})$/', $t1))
+ return intval(substr($t1, 1), 16);
+
+ if(preg_match('/^(?:[0-3][0-7]{0,2}|[4-7][0-7]?)$/', $t1))
+ return intval($t1, 8);
+
+ $len = strlen($t1);
+
+ if($len === 1 && strpos('cuxDdSsWw', $t1) !== false)
+ return null;
+
+ if($len === 1){
+ switch ($t1) {
+ case 'b': return 8;
+ case 'f': return 12;
+ case 'n': return 10;
+ case 'r': return 13;
+ case 't': return 9;
+ case 'v': return 11;
+ default: return $t1[0];
+ }
+ }
+ }
+
+ return ($token !== '\\') ? $token[0] : null;
+ };
+
+ foreach($matches as $m){
+
+ if($m[0] === '['){
+ $lastCC = null;
+ $cLastRangeable = false;
+ $cLastType = 0; // 0 = none; 1 = range hyphen; 2 = short class
+
+ preg_match('/^(\[\^?)(]?(?:[^\\\\\]]+|\\\\[\S\s]?)*)(]?)$/', $m, $parts);
+
+ array_shift($parts);
+ list($opening, $content, $closing) = $parts;
+
+ if(!$closing)
+ throw new \Exception('Unclosed character class');
+
+ preg_match_all('/[^\\\\-]+|-|\\\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4}|c[A-Za-z]|[\S\s]?)/', $content, $ccTokens);
+ $ccTokens = $ccTokens[0];
+ $ccTokenCount = count($ccTokens);
+ $output[] = array('chr' => $opening);
+
+ foreach($ccTokens as $i => $cm) {
+
+ if($cm[0] === '\\'){
+ if(preg_match('/^\\\\[cux]$/', $cm))
+ throw new \Exception('Incomplete regex token');
+
+ if(preg_match('/^\\\\[dsw]$/i', $cm)) {
+ $output[] = array('chr-meta' => $cm);
+ $cLastRangeable = ($cLastType !== 1);
+ $cLastType = 2;
+
+ }elseif($cm === '\\'){
+ throw new \Exception('Incomplete regex token');
+
+ }else{
+ $output[] = array('chr-meta' => $cm);
+ $cLastRangeable = $cLastType !== 1;
+ $lastCC = $getTokenCharCode($cm);
+ }
+
+ }elseif($cm === '-'){
+ if($cLastRangeable){
+ $nextToken = ($i + 1 < $ccTokenCount) ? $ccTokens[$i + 1] : false;
+
+ if($nextToken){
+ $nextTokenCharCode = $getTokenCharCode($nextToken[0]);
+
+ if((!is_null($nextTokenCharCode) && $lastCC > $nextTokenCharCode) || $cLastType === 2 || preg_match('/^\\\\[dsw]$/i', $nextToken[0]))
+ throw new \Exception('Reversed or invalid range');
+
+ $output[] = array('chr-range' => '-');
+ $cLastRangeable = false;
+ $cLastType = 1;
+
+ }else{
+ $output[] = $closing ? array('chr' => '-') : array('chr-range' => '-');
+ }
+
+ }else{
+ $output[] = array('chr' => '-');
+ $cLastRangeable = ($cLastType !== 1);
+ }
+
+ }else{
+ $output[] = array('chr' => $cm);
+ $cLastRangeable = strlen($cm) > 1 || ($cLastType !== 1);
+ $lastCC = $cm[strlen($cm) - 1];
+ }
+ }
+
+ $output[] = array('chr' => $closing);
+ $lastIsQuant = true;
+
+ }elseif($m[0] === '('){
+ if(strlen($m) === 2)
+ throw new \Exception('Invalid or unsupported group type');
+
+ if(strlen($m) === 1)
+ $capturingGroupCount++;
+
+ $groupStyleDepth = ($groupStyleDepth !== 5) ? $groupStyleDepth + 1 : 1;
+ $openGroups[] = $m; // opening
+ $lastIsQuant = false;
+ $output[] = array("g{$groupStyleDepth}" => $m);
+
+ }elseif($m[0] === ')'){
+ if(!count($openGroups))
+ throw new \Exception('No matching opening parenthesis');
+
+ $output[] = array('g' . $groupStyleDepth => ')');
+ $prevGroup = $openGroups[count($openGroups) - 1];
+ $prevGroup = isset($prevGroup[2]) ? $prevGroup[2] : '';
+ $lastIsQuant = !preg_match('/^[=!]/', $prevGroup);
+ $lastStyle = "g{$groupStyleDepth}";
+ $lastType = 0;
+ $groupStyleDepth = ($groupStyleDepth !== 1) ? $groupStyleDepth - 1 : 5;
+
+ array_pop($openGroups);
+ continue;
+
+ }elseif($m[0] === '\\'){
+ if(isset($m[1]) && preg_match('/^[1-9]/', $m[1])){
+ $nonBackrefDigits = '';
+ $num = substr(+$m, 1);
+
+ while($num > $capturingGroupCount){
+ preg_match('/[0-9]$/', $num, $digits);
+ $nonBackrefDigits = $digits[0] . $nonBackrefDigits;
+ $num = floor($num / 10);
+ }
+
+ if($num > 0){
+ $output[] = array('meta' => "\\{$num}", 'text' => $nonBackrefDigits);
+
+ }else{
+ preg_match('/^\\\\([0-3][0-7]{0,2}|[4-7][0-7]?|[89])([0-9]*)/', $m, $pts);
+ $output[] = array('meta' => '\\' . $pts[1], 'text' => $pts[2]);
+ }
+
+ $lastIsQuant = true;
+
+ }elseif(isset($m[1]) && preg_match('/^[0bBcdDfnrsStuvwWx]/', $m[1])){
+
+ if(preg_match('/^\\\\[cux]$/', $m))
+ throw new \Exception('Incomplete regex token');
+
+ $output[] = array('meta' => $m);
+ $lastIsQuant = (strpos('bB', $m[1]) === false);
+
+ }elseif($m === '\\'){
+ throw new \Exception('Incomplete regex token');
+
+ }else{
+ $output[] = array('text' => $m);
+ $lastIsQuant = true;
+ }
+
+ }elseif(preg_match('/^(?:[?*+]|\{[0-9]+(?:,[0-9]*)?\})\??$/', $m)){
+ if(!$lastIsQuant)
+ throw new \Exception('Quantifiers must be preceded by a token that can be repeated');
+
+ preg_match('/^\{([0-9]+)(?:,([0-9]*))?/', $m, $interval);
+
+ if($interval && (+$interval[1] > 65535 || (isset($interval[2]) && (+$interval[2] > 65535))))
+ throw new \Exception('Interval quantifier cannot use value over 65,535');
+
+ if($interval && isset($interval[2]) && (+$interval[1] > +$interval[2]))
+ throw new \Exception('Interval quantifier range is reversed');
+
+ $output[] = array($lastStyle ? $lastStyle : 'meta' => $m);
+ $lastIsQuant = false;
+
+ }elseif($m === '|'){
+ if($lastType === 1 || ($lastType === 2 && !count($openGroups)))
+ throw new \Exception('Empty alternative effectively truncates the regex here');
+
+ $output[] = count($openGroups) ? array("g{$groupStyleDepth}" => '|') : array('meta' => '|');
+ $lastIsQuant = false;
+ $lastType = 2;
+ $lastStyle = '';
+ continue;
+
+ }elseif($m === '^' || $m === '$'){
+ $output[] = array('meta' => $m);
+ $lastIsQuant = false;
+
+ }elseif($m === '.'){
+ $output[] = array('meta' => '.');
+ $lastIsQuant = true;
+
+ }else{
+ $output[] = array('text' => $m);
+ $lastIsQuant = true;
+ }
+
+ $lastType = 0;
+ $lastStyle = '';
+ }
+
+ if($openGroups)
+ throw new \Exception('Unclosed grouping');
+
+ return $output;
+ }
+
+
+
+ /**
+ * Set or get configuration options
+ *
+ * @param string $key
+ * @param mixed|null $value
+ * @return mixed
+ */
+ public static function config($key, $value = null){
+
+ if(!array_key_exists($key, static::$config))
+ throw new \Exception(sprintf('Unrecognized option: "%s". Valid options are: %s', $key, implode(', ', array_keys(static::$config))));
+
+ if($value === null)
+ return static::$config[$key];
+
+ if(is_array(static::$config[$key]))
+ return static::$config[$key] = (array)$value;
+
+ return static::$config[$key] = $value;
+ }
+
+
+
+ /**
+ * Total CPU time used by the class
+ *
+ * @param int precision
+ * @return double
+ */
+ public static function getTime($precision = 4){
+ return round(static::$time, $precision);
+ }
+
+
+
+ /**
+ * Get relevant backtrace info for last ref call
+ *
+ * @return array|false
+ */
+ public static function getBacktrace(){
+
+ // pull only basic info with php 5.3.6+ to save some memory
+ $trace = defined('DEBUG_BACKTRACE_IGNORE_ARGS') ? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) : debug_backtrace();
+
+ while($callee = array_pop($trace)){
+
+ // extract only the information we neeed
+ $callee = array_intersect_key($callee, array_fill_keys(array('file', 'function', 'line'), false));
+ extract($callee, EXTR_OVERWRITE);
+
+ // skip, if the called function doesn't match the shortcut function name
+ if(!$function || !in_array(mb_strtolower((string)$function), static::$config['shortcutFunc']))
+ continue;
+
+ return compact('file', 'function', 'line');
+ }
+
+ return false;
+ }
+
+
+
+ /**
+ * Determines the input expression(s) passed to the shortcut function
+ *
+ * @param array &$options Optional, options to gather (from operators)
+ * @return array Array of string expressions
+ */
+ public static function getInputExpressions(array &$options = null){
+
+ // used to determine the position of the current call,
+ // if more queries calls were made on the same line
+ static $lineInst = array();
+
+ $trace = static::getBacktrace();
+
+ if(!$trace)
+ return array();
+
+ extract($trace);
+
+ $code = file($file);
+ $code = $code[$line - 1]; // multiline expressions not supported!
+ $instIndx = 0;
+ $tokens = token_get_all(" $token){
+
+ // match token with our shortcut function name
+ if(is_string($token) || ($token[0] !== T_STRING) || (strcasecmp($token[1], $function) !== 0))
+ continue;
+
+ // is this some method that happens to have the same name as the shortcut function?
+ if(isset($tokens[$i - 1]) && is_array($tokens[$i - 1]) && in_array($tokens[$i - 1][0], array(T_DOUBLE_COLON, T_OBJECT_OPERATOR), true))
+ continue;
+
+ // find argument definition start, just after '('
+ if(isset($tokens[$i + 1]) && ($tokens[$i + 1][0] === '(')){
+ $instIndx++;
+
+ if(!isset($lineInst[$line]))
+ $lineInst[$line] = 0;
+
+ if($instIndx <= $lineInst[$line])
+ continue;
+
+ $lineInst[$line]++;
+
+ // gather options
+ if($options !== null){
+ $j = $i - 1;
+ while(isset($tokens[$j]) && is_string($tokens[$j]) && in_array($tokens[$j], array('@', '+', '-', '!', '~')))
+ $options[] = $tokens[$j--];
+ }
+
+ $lvl = $index = $curlies = 0;
+ $expressions = array();
+
+ // get the expressions
+ foreach(array_slice($tokens, $i + 2) as $token){
+
+ if(is_array($token)){
+ if($token[0] !== T_COMMENT)
+ $expressions[$index][] = ($token[0] !== T_WHITESPACE) ? $token[1] : ' ';
+
+ continue;
+ }
+
+ if($token === '{')
+ $curlies++;
+
+ if($token === '}')
+ $curlies--;
+
+ if($token === '(')
+ $lvl++;
+
+ if($token === ')')
+ $lvl--;
+
+ // assume next argument if a comma was encountered,
+ // and we're not insde a curly bracket or inner parentheses
+ if(($curlies < 1) && ($lvl === 0) && ($token === ',')){
+ $index++;
+ continue;
+ }
+
+ // negative parentheses count means we reached the end of argument definitions
+ if($lvl < 0){
+ foreach($expressions as &$expression)
+ $expression = trim(implode('', $expression));
+
+ return $expressions;
+ }
+
+ $expressions[$index][] = $token;
+ }
+
+ break;
+ }
+ }
+
+ return array();
+ }
+
+
+
+ /**
+ * Get all parent classes of a class
+ *
+ * @param Reflector $class Reflection object
+ * @return array Array of ReflectionClass objects (starts with the ancestor, ends with the given class)
+ */
+ protected static function getParentClasses(\Reflector $class){
+
+ $parents = array($class);
+ while(($class = $class->getParentClass()) !== false)
+ $parents[] = $class;
+
+ return array_reverse($parents);
+ }
+
+
+
+ /**
+ * Generate class / function info
+ *
+ * @param Reflector $reflector Class name or reflection object
+ * @param string $single Skip parent classes
+ * @param Reflector|null $context Object context (for methods)
+ * @return string
+ */
+ protected function fromReflector(\Reflector $reflector, $single = '', \Reflector $context = null){
+
+ // @todo: test this
+ $hash = var_export(func_get_args(), true);
+ //$hash = $reflector->getName() . ';' . $single . ';' . ($context ? $context->getName() : '');
+
+ if($this->fmt->didCache($hash)){
+ static::$debug['cacheHits']++;
+ return;
+ }
+
+ $items = array($reflector);
+
+ if(($single === '') && ($reflector instanceof \ReflectionClass))
+ $items = static::getParentClasses($reflector);
+
+ $first = true;
+ foreach($items as $item){
+
+ if(!$first)
+ $this->fmt->sep(' :: ');
+
+ $first = false;
+ $name = ($single !== '') ? $single : $item->getName();
+ $comments = $item->isInternal() ? array() : static::parseComment($item->getDocComment());
+ $meta = array('sub' => array());
+ $bubbles = array();
+
+ if($item->isInternal()){
+ $extension = $item->getExtension();
+ $meta['title'] = ($extension instanceof \ReflectionExtension) ? sprintf('Internal - part of %s (%s)', $extension->getName(), $extension->getVersion()) : 'Internal';
+
+ }else{
+ $comments = static::parseComment($item->getDocComment());
+
+ if($comments)
+ $meta += $comments;
+
+ $meta['sub'][] = array('Defined in', basename($item->getFileName()) . ':' . $item->getStartLine());
+ }
+
+ if(($item instanceof \ReflectionFunction) || ($item instanceof \ReflectionMethod)){
+ if(($context !== null) && ($context->getShortName() !== $item->getDeclaringClass()->getShortName()))
+ $meta['sub'][] = array('Inherited from', $item->getDeclaringClass()->getShortName());
+
+ // @note: PHP 7 seems to crash when calling getPrototype on Closure::__invoke()
+ if(($item instanceof \ReflectionMethod) && !$item->isInternal()){
+ try{
+ $proto = $item->getPrototype();
+ $meta['sub'][] = array('Prototype defined by', $proto->class);
+ }catch(\Exception $e){}
+ }
+
+ $this->fmt->text('name', $name, $meta, $this->linkify($item));
+ continue;
+ }
+
+ // @todo: maybe - list interface methods
+ if(!($item->isInterface() || (static::$env['is54'] && $item->isTrait()))){
+
+ if($item->isAbstract())
+ $bubbles[] = array('A', 'Abstract');
+
+ if(static::$env['is7'] && $item->isAnonymous())
+ $bubbles[] = array('?', 'Anonymous');
+
+ if($item->isFinal())
+ $bubbles[] = array('F', 'Final');
+
+ // php 5.4+ only
+ if(static::$env['is54'] && $item->isCloneable())
+ $bubbles[] = array('C', 'Cloneable');
+
+ if($item->isIterateable())
+ $bubbles[] = array('X', 'Iterateable');
+
+ }
+
+ if($item->isInterface() && $single !== '')
+ $bubbles[] = array('I', 'Interface');
+
+ if($bubbles)
+ $this->fmt->bubbles($bubbles);
+
+ if($item->isInterface() && $single === '')
+ $name .= sprintf(' (%d)', count($item->getMethods()));
+
+ $this->fmt->text('name', $name, $meta, $this->linkify($item));
+ }
+
+ $this->fmt->cacheLock($hash);
+ }
+
+
+
+ /**
+ * Generates an URL that points to the documentation page relevant for the requested context
+ *
+ * For internal functions and classes, the URI will point to the local PHP manual
+ * if installed and configured, otherwise to php.net/manual (the english one)
+ *
+ * @param Reflector $reflector Reflector object (used to determine the URL scheme for internal stuff)
+ * @param string|null $constant Constant name, if this is a request to linkify a constant
+ * @return string|null URL
+ */
+ protected function linkify(\Reflector $reflector, $constant = null){
+
+ static $docRefRoot = null, $docRefExt = null;
+
+ // most people don't have this set
+ if(!$docRefRoot)
+ $docRefRoot = ($docRefRoot = rtrim(ini_get('docref_root'), '/')) ? $docRefRoot : 'http://php.net/manual/en';
+
+ if(!$docRefExt)
+ $docRefExt = ($docRefExt = ini_get('docref_ext')) ? $docRefExt : '.php';
+
+ $phpNetSchemes = array(
+ 'class' => $docRefRoot . '/class.%s' . $docRefExt,
+ 'function' => $docRefRoot . '/function.%s' . $docRefExt,
+ 'method' => $docRefRoot . '/%2$s.%1$s' . $docRefExt,
+ 'property' => $docRefRoot . '/class.%2$s' . $docRefExt . '#%2$s.props.%1$s',
+ 'constant' => $docRefRoot . '/class.%2$s' . $docRefExt . '#%2$s.constants.%1$s',
+ );
+
+ $url = null;
+ $args = array();
+
+ // determine scheme
+ if($constant !== null){
+ $type = 'constant';
+ $args[] = $constant;
+
+ }else{
+ $type = explode('\\', get_class($reflector));
+ $type = strtolower(ltrim(end($type), 'Reflection'));
+
+ if($type === 'object')
+ $type = 'class';
+ }
+
+ // properties don't have the internal flag;
+ // also note that many internal classes use some kind of magic as properties (eg. DateTime);
+ // these will only get linkifed if the declared class is internal one, and not an extension :(
+ $parent = ($type !== 'property') ? $reflector : $reflector->getDeclaringClass();
+
+ // internal function/method/class/property/constant
+ if($parent->isInternal()){
+ $args[] = $reflector->name;
+
+ if(in_array($type, array('method', 'property'), true))
+ $args[] = $reflector->getDeclaringClass()->getName();
+
+ $args = array_map(function($text){
+ return str_replace('_', '-', ltrim(strtolower($text), '\\_'));
+ }, $args);
+
+ // check for some special cases that have no links
+ $valid = (($type === 'method') || (strcasecmp($parent->name, 'stdClass') !== 0))
+ && (($type !== 'method') || (($reflector->name === '__construct') || strpos($reflector->name, '__') !== 0));
+
+ if($valid)
+ $url = vsprintf($phpNetSchemes[$type], $args);
+
+ // custom
+ }else{
+ switch(true){
+
+ // WordPress function;
+ // like pretty much everything else in WordPress, API links are inconsistent as well;
+ // so we're using queryposts.com as doc source for API
+ case ($type === 'function') && class_exists('WP', false) && defined('ABSPATH') && defined('WPINC'):
+ if(strpos($reflector->getFileName(), realpath(ABSPATH . WPINC)) === 0){
+ $url = sprintf('http://queryposts.com/function/%s', urlencode(strtolower($reflector->getName())));
+ break;
+ }
+
+ // @todo: handle more apps
+ }
+
+ }
+
+ return $url;
+ }
+
+
+ public static function getTimeoutPoint(){
+ return static::$timeout;
+ }
+
+
+ public static function getDebugInfo(){
+ return static::$debug;
+ }
+
+
+
+ protected function hasInstanceTimedOut(){
+
+ if(static::$timeout > 0)
+ return true;
+
+ $timeout = static::$config['timeout'];
+
+ if(($timeout > 0) && ((microtime(true) - $this->startTime) > $timeout))
+ return (static::$timeout = (microtime(true) - $this->startTime));
+
+ return false;
+ }
+
+
+
+ /**
+ * Evaluates the given variable
+ *
+ * @param mixed &$subject Variable to query
+ * @param bool $specialStr Should this be interpreted as a special string?
+ * @return mixed Result (both HTML and text modes generate strings)
+ */
+ protected function evaluate(&$subject, $specialStr = false){
+
+ switch($type = gettype($subject)){
+
+ // https://github.com/digitalnature/php-ref/issues/13
+ case 'unknown type':
+ return $this->fmt->text('unknown');
+
+ // null value
+ case 'NULL':
+ return $this->fmt->text('null');
+
+ // integer/double/float
+ case 'integer':
+ case 'double':
+ return $this->fmt->text($type, $subject, $type);
+
+ // boolean
+ case 'boolean':
+ $text = $subject ? 'true' : 'false';
+ return $this->fmt->text($text, $text, $type);
+
+ // arrays
+ case 'array':
+
+ // empty array?
+ if(empty($subject)){
+ $this->fmt->text('array');
+ return $this->fmt->emptyGroup();
+ }
+
+ if(isset($subject[static::MARKER_KEY])){
+ unset($subject[static::MARKER_KEY]);
+ $this->fmt->text('array');
+ $this->fmt->emptyGroup('recursion');
+ return;
+ }
+
+ // first recursion level detection;
+ // this is optional (used to print consistent recursion info)
+ foreach($subject as $key => &$value){
+
+ if(!is_array($value))
+ continue;
+
+ // save current value in a temporary variable
+ $buffer = $value;
+
+ // assign new value
+ $value = ($value !== 1) ? 1 : 2;
+
+ // if they're still equal, then we have a reference
+ if($value === $subject){
+ $value = $buffer;
+ $value[static::MARKER_KEY] = true;
+ $this->evaluate($value);
+ return;
+ }
+
+ // restoring original value
+ $value = $buffer;
+ }
+
+ $this->fmt->text('array');
+ $count = count($subject);
+ if(!$this->fmt->startGroup($count))
+ return;
+
+ $max = max(array_map('static::strLen', array_keys($subject)));
+ $subject[static::MARKER_KEY] = true;
+
+ foreach($subject as $key => &$value){
+
+ // ignore our temporary marker
+ if($key === static::MARKER_KEY)
+ continue;
+
+ if($this->hasInstanceTimedOut())
+ break;
+
+ $keyInfo = gettype($key);
+
+ if($keyInfo === 'string'){
+ $encoding = static::$env['mbStr'] ? mb_detect_encoding($key) : '';
+ $keyLen = static::strLen($key);
+ $keyLenInfo = $encoding && ($encoding !== 'ASCII') ? $keyLen . '; ' . $encoding : $keyLen;
+ $keyInfo = "{$keyInfo}({$keyLenInfo})";
+ }else{
+ $keyLen = strlen($key);
+ }
+
+ $this->fmt->startRow();
+ $this->fmt->text('key', $key, "Key: {$keyInfo}");
+ $this->fmt->colDiv($max - $keyLen);
+ $this->fmt->sep('=>');
+ $this->fmt->colDiv();
+ $this->evaluate($value, $specialStr);
+ $this->fmt->endRow();
+ }
+
+ unset($subject[static::MARKER_KEY]);
+
+ $this->fmt->endGroup();
+ return;
+
+ // resource
+ case 'resource':
+ case 'resource (closed)':
+ $meta = array();
+ $resType = get_resource_type($subject);
+
+ $this->fmt->text('resource', strval($subject));
+
+ if(!static::$config['showResourceInfo'])
+ return $this->fmt->emptyGroup($resType);
+
+ // @see: http://php.net/manual/en/resource.php
+ // need to add more...
+ switch($resType){
+
+ // curl extension resource
+ case 'curl':
+ $meta = curl_getinfo($subject);
+ break;
+
+ case 'FTP Buffer':
+ $meta = array(
+ 'time_out' => ftp_get_option($subject, FTP_TIMEOUT_SEC),
+ 'auto_seek' => ftp_get_option($subject, FTP_AUTOSEEK),
+ );
+
+ break;
+
+ // gd image extension resource
+ case 'gd':
+ $meta = array(
+ 'size' => sprintf('%d x %d', imagesx($subject), imagesy($subject)),
+ 'true_color' => imageistruecolor($subject),
+ );
+
+ break;
+
+ case 'ldap link':
+ $constants = get_defined_constants();
+
+ array_walk($constants, function($value, $key) use(&$constants){
+ if(strpos($key, 'LDAP_OPT_') !== 0)
+ unset($constants[$key]);
+ });
+
+ // this seems to fail on my setup :(
+ unset($constants['LDAP_OPT_NETWORK_TIMEOUT']);
+
+ foreach(array_slice($constants, 3) as $key => $value)
+ if(ldap_get_option($subject, (int)$value, $ret))
+ $meta[strtolower(substr($key, 9))] = $ret;
+
+ break;
+
+ // mysql connection (mysql extension is deprecated from php 5.4/5.5)
+ case 'mysql link':
+ case 'mysql link persistent':
+ $dbs = array();
+ $query = @mysql_list_dbs($subject);
+ while($row = @mysql_fetch_array($query))
+ $dbs[] = $row['Database'];
+
+ $meta = array(
+ 'host' => ltrim(@mysql_get_host_info ($subject), 'MySQL host info: '),
+ 'server_version' => @mysql_get_server_info($subject),
+ 'protocol_version' => @mysql_get_proto_info($subject),
+ 'databases' => $dbs,
+ );
+
+ break;
+
+ // mysql result
+ case 'mysql result':
+ while($row = @mysql_fetch_object($subject)){
+ $meta[] = (array)$row;
+
+ if($this->hasInstanceTimedOut())
+ break;
+ }
+
+ break;
+
+ // stream resource (fopen, fsockopen, popen, opendir etc)
+ case 'stream':
+ $meta = stream_get_meta_data($subject);
+ break;
+
+ }
+
+ if(!$meta)
+ return $this->fmt->emptyGroup($resType);
+
+
+ if(!$this->fmt->startGroup($resType))
+ return;
+
+ $max = max(array_map('static::strLen', array_keys($meta)));
+ foreach($meta as $key => $value){
+ $this->fmt->startRow();
+ $this->fmt->text('resourceProp', ucwords(str_replace('_', ' ', $key)));
+ $this->fmt->colDiv($max - static::strLen($key));
+ $this->fmt->sep(':');
+ $this->fmt->colDiv();
+ $this->evaluate($value);
+ $this->fmt->endRow();
+ }
+ $this->fmt->endGroup();
+ return;
+
+ // string
+ case 'string':
+ $length = static::strLen($subject);
+ $encoding = static::$env['mbStr'] ? mb_detect_encoding($subject) : false;
+ $info = $encoding && ($encoding !== 'ASCII') ? $length . '; ' . $encoding : $length;
+
+ if($specialStr){
+ $this->fmt->sep('"');
+ $this->fmt->text(array('string', 'special'), $subject, "string({$info})");
+ $this->fmt->sep('"');
+ return;
+ }
+
+ $this->fmt->text('string', $subject, "string({$info})");
+
+ // advanced checks only if there are 3 characteres or more
+ if(static::$config['showStringMatches'] && ($length > 2) && (trim($subject) !== '')){
+
+ $isNumeric = is_numeric($subject);
+
+ // very simple check to determine if the string could match a file path
+ // @note: this part of the code is very expensive
+ $isFile = ($length < 2048)
+ && (max(array_map('strlen', explode('/', str_replace('\\', '/', $subject)))) < 128)
+ && !preg_match('/[^\w\.\-\/\\\\:]|\..*\.|\.$|:(?!(?<=^[a-zA-Z]:)[\/\\\\])/', $subject);
+
+ if($isFile){
+ try{
+ $file = new \SplFileInfo($subject);
+ $flags = array();
+ $perms = $file->getPerms();
+
+ if(($perms & 0xC000) === 0xC000) // socket
+ $flags[] = 's';
+ elseif(($perms & 0xA000) === 0xA000) // symlink
+ $flags[] = 'l';
+ elseif(($perms & 0x8000) === 0x8000) // regular
+ $flags[] = '-';
+ elseif(($perms & 0x6000) === 0x6000) // block special
+ $flags[] = 'b';
+ elseif(($perms & 0x4000) === 0x4000) // directory
+ $flags[] = 'd';
+ elseif(($perms & 0x2000) === 0x2000) // character special
+ $flags[] = 'c';
+ elseif(($perms & 0x1000) === 0x1000) // FIFO pipe
+ $flags[] = 'p';
+ else // unknown
+ $flags[] = 'u';
+
+ // owner
+ $flags[] = (($perms & 0x0100) ? 'r' : '-');
+ $flags[] = (($perms & 0x0080) ? 'w' : '-');
+ $flags[] = (($perms & 0x0040) ? (($perms & 0x0800) ? 's' : 'x' ) : (($perms & 0x0800) ? 'S' : '-'));
+
+ // group
+ $flags[] = (($perms & 0x0020) ? 'r' : '-');
+ $flags[] = (($perms & 0x0010) ? 'w' : '-');
+ $flags[] = (($perms & 0x0008) ? (($perms & 0x0400) ? 's' : 'x' ) : (($perms & 0x0400) ? 'S' : '-'));
+
+ // world
+ $flags[] = (($perms & 0x0004) ? 'r' : '-');
+ $flags[] = (($perms & 0x0002) ? 'w' : '-');
+ $flags[] = (($perms & 0x0001) ? (($perms & 0x0200) ? 't' : 'x' ) : (($perms & 0x0200) ? 'T' : '-'));
+
+ $size = is_dir($subject) ? '' : sprintf(' %.2fK', $file->getSize() / 1024);
+
+ $this->fmt->startContain('file', true);
+ $this->fmt->text('file', implode('', $flags) . $size);
+ $this->fmt->endContain();
+
+ }catch(\Exception $e){
+ $isFile = false;
+ }
+ }
+
+ // class/interface/function
+ if(!preg_match('/[^\w+\\\\]/', $subject) && ($length < 96)){
+ $isClass = class_exists($subject, false);
+ if($isClass){
+ $this->fmt->startContain('class', true);
+ $this->fromReflector(new \ReflectionClass($subject));
+ $this->fmt->endContain();
+ }
+
+ if(!$isClass && interface_exists($subject, false)){
+ $this->fmt->startContain('interface', true);
+ $this->fromReflector(new \ReflectionClass($subject));
+ $this->fmt->endContain('interface');
+ }
+
+ if(function_exists($subject)){
+ $this->fmt->startContain('function', true);
+ $this->fromReflector(new \ReflectionFunction($subject));
+ $this->fmt->endContain('function');
+ }
+ }
+
+
+ // skip serialization/json/date checks if the string appears to be numeric,
+ // or if it's shorter than 5 characters
+ if(!$isNumeric && ($length > 4)){
+
+ // url
+ if(static::$config['showUrls'] && static::$env['curlActive'] && filter_var($subject, FILTER_VALIDATE_URL)){
+ $ch = curl_init($subject);
+ curl_setopt($ch, CURLOPT_NOBODY, true);
+ curl_exec($ch);
+ $nfo = curl_getinfo($ch);
+ curl_close($ch);
+
+ if($nfo['http_code']){
+ $this->fmt->startContain('url', true);
+ $contentType = explode(';', $nfo['content_type']);
+ $this->fmt->text('url', sprintf('%s:%d %s %.2fms (%d)', !empty($nfo['primary_ip']) ? $nfo['primary_ip'] : null, !empty($nfo['primary_port']) ? $nfo['primary_port'] : null, $contentType[0], $nfo['total_time'], $nfo['http_code']));
+ $this->fmt->endContain();
+ }
+
+ }
+
+ // date
+ if(($length < 128) && static::$env['supportsDate'] && !preg_match('/[^A-Za-z0-9.:+\s\-\/]/', $subject)){
+ try{
+ $date = new \DateTime($subject);
+ $errors = \DateTime::getLastErrors();
+
+ if(($errors['warning_count'] < 1) && ($errors['error_count'] < 1)){
+ $now = new \Datetime('now');
+ $nowUtc = new \Datetime('now', new \DateTimeZone('UTC'));
+ $diff = $now->diff($date);
+
+ $map = array(
+ 'y' => 'yr',
+ 'm' => 'mo',
+ 'd' => 'da',
+ 'h' => 'hr',
+ 'i' => 'min',
+ 's' => 'sec',
+ );
+
+ $timeAgo = 'now';
+ foreach($map as $k => $label){
+ if($diff->{$k} > 0){
+ $timeAgo = $diff->format("%R%{$k}{$label}");
+ break;
+ }
+ }
+
+ $tz = $date->getTimezone();
+ $offs = round($tz->getOffset($nowUtc) / 3600);
+
+ if($offs > 0)
+ $offs = "+{$offs}";
+
+ $timeAgo .= ((int)$offs !== 0) ? ' ' . sprintf('%s (UTC%s)', $tz->getName(), $offs) : ' UTC';
+ $this->fmt->startContain('date', true);
+ $this->fmt->text('date', $timeAgo);
+ $this->fmt->endContain();
+
+ }
+ }catch(\Exception $e){
+ // not a date
+ }
+
+ }
+
+ // attempt to detect if this is a serialized string
+ static $unserializing = 0;
+ $isSerialized = ($unserializing < 3)
+ && (($subject[$length - 1] === ';') || ($subject[$length - 1] === '}'))
+ && in_array($subject[0], array('s', 'a', 'O'), true)
+ && ((($subject[0] === 's') && ($subject[$length - 2] !== '"')) || preg_match("/^{$subject[0]}:[0-9]+:/s", $subject))
+ && (($unserialized = @unserialize($subject)) !== false);
+
+ if($isSerialized){
+ $unserializing++;
+ $this->fmt->startContain('serialized', true);
+ $this->evaluate($unserialized);
+ $this->fmt->endContain();
+ $unserializing--;
+ }
+
+ // try to find out if it's a json-encoded string;
+ // only do this for json-encoded arrays or objects, because other types have too generic formats
+ static $decodingJson = 0;
+ $isJson = !$isSerialized && ($decodingJson < 3) && in_array($subject[0], array('{', '['), true);
+
+ if($isJson){
+ $decodingJson++;
+ $data = json_decode($subject);
+
+ // ensure created objects live enough for PHP to provide a unique hash
+ if(is_object($data))
+ $this->intObjects->attach($data);
+
+ if($isJson = (json_last_error() === JSON_ERROR_NONE)){
+ $this->fmt->startContain('json', true);
+ $this->evaluate($data);
+ $this->fmt->endContain();
+ }
+
+ $decodingJson--;
+ }
+
+ // attempt to match a regex
+ if(!$isSerialized && !$isJson && $length < 768){
+ try{
+ $components = $this->splitRegex($subject);
+ if($components){
+ $regex = '';
+
+ $this->fmt->startContain('regex', true);
+ foreach($components as $component)
+ $this->fmt->text('regex-' . key($component), reset($component));
+ $this->fmt->endContain();
+ }
+
+ }catch(\Exception $e){
+ // not a regex
+ }
+
+ }
+ }
+ }
+
+ return;
+ }
+
+ // if we reached this point, $subject must be an object
+
+ // track objects to detect recursion
+ static $hashes = array();
+
+ // hash ID of this object
+ $hash = spl_object_hash($subject);
+ $recursion = isset($hashes[$hash]);
+
+ // sometimes incomplete objects may be created from string unserialization,
+ // if the class to which the object belongs wasn't included until the unserialization stage...
+ if($subject instanceof \__PHP_Incomplete_Class){
+ $this->fmt->text('object');
+ $this->fmt->emptyGroup('incomplete');
+ return;
+ }
+
+ // check cache at this point
+ if(!$recursion && $this->fmt->didCache($hash)){
+ static::$debug['cacheHits']++;
+ return;
+ }
+
+ $reflector = new \ReflectionObject($subject);
+ $this->fmt->startContain('class');
+ $this->fromReflector($reflector);
+ $this->fmt->text('object', ' object');
+ $this->fmt->endContain();
+
+ // already been here?
+ if($recursion)
+ return $this->fmt->emptyGroup('recursion');
+
+ $hashes[$hash] = 1;
+
+ $flags = \ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED;
+
+ if(static::$config['showPrivateMembers'])
+ $flags |= \ReflectionProperty::IS_PRIVATE;
+
+ $props = $magicProps = $methods = array();
+
+ if($reflector->hasMethod('__debugInfo')){
+ $magicProps = $subject->__debugInfo();
+ }else{
+ $props = $reflector->getProperties($flags);
+ }
+
+ if(static::$config['showMethods']){
+ $flags = \ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED;
+
+ if(static::$config['showPrivateMembers'])
+ $flags |= \ReflectionMethod::IS_PRIVATE;
+
+ $methods = $reflector->getMethods($flags);
+ }
+
+ $constants = $reflector->getConstants();
+ $interfaces = $reflector->getInterfaces();
+ $traits = static::$env['is54'] ? $reflector->getTraits() : array();
+ $parents = static::getParentClasses($reflector);
+
+ // work-around for https://bugs.php.net/bug.php?id=49154
+ // @see http://stackoverflow.com/questions/15672287/strange-behavior-of-reflectiongetproperties-with-numeric-keys
+ if(!static::$env['is54']){
+ $props = array_values(array_filter($props, function($prop) use($subject){
+ return !$prop->isPublic() || property_exists($subject, $prop->name);
+ }));
+ }
+
+ // no data to display?
+ if(!$props && !$methods && !$constants && !$interfaces && !$traits){
+ unset($hashes[$hash]);
+ return $this->fmt->emptyGroup();
+ }
+
+ if(!$this->fmt->startGroup())
+ return;
+
+ // show contents for iterators
+ if(static::$config['showIteratorContents'] && $reflector->isIterateable()){
+
+ $itContents = iterator_to_array($subject);
+ $this->fmt->sectionTitle(sprintf('Contents (%d)', count($itContents)));
+
+ foreach($itContents as $key => $value){
+ $keyInfo = gettype($key);
+ if($keyInfo === 'string'){
+ $encoding = static::$env['mbStr'] ? mb_detect_encoding($key) : '';
+ $length = $encoding && ($encoding !== 'ASCII') ? static::strLen($key) . '; ' . $encoding : static::strLen($key);
+ $keyInfo = sprintf('%s(%s)', $keyInfo, $length);
+ }
+
+ $this->fmt->startRow();
+ $this->fmt->text(array('key', 'iterator'), $key, sprintf('Iterator key: %s', $keyInfo));
+ $this->fmt->colDiv();
+ $this->fmt->sep('=>');
+ $this->fmt->colDiv();
+ $this->evaluate($value);
+ //$this->evaluate($value instanceof \Traversable ? ((count($value) > 0) ? $value : (string)$value) : $value);
+ $this->fmt->endRow();
+ }
+ }
+
+ // display the interfaces this objects' class implements
+ if($interfaces){
+ $items = array();
+ $this->fmt->sectionTitle('Implements');
+ $this->fmt->startRow();
+ $this->fmt->startContain('interfaces');
+
+ $i = 0;
+ $count = count($interfaces);
+
+ foreach($interfaces as $name => $interface){
+ $this->fromReflector($interface);
+
+ if(++$i < $count)
+ $this->fmt->sep(', ');
+ }
+
+ $this->fmt->endContain();
+ $this->fmt->endRow();
+ }
+
+ // traits this objects' class uses
+ if($traits){
+ $items = array();
+ $this->fmt->sectionTitle('Uses');
+ $this->fmt->startRow();
+ $this->fmt->startContain('traits');
+
+ $i = 0;
+ $count = count($traits);
+
+ foreach($traits as $name => $trait){
+ $this->fromReflector($trait);
+
+ if(++$i < $count)
+ $this->fmt->sep(', ');
+ }
+
+ $this->fmt->endContain();
+ $this->fmt->endRow();
+ }
+
+ // class constants
+ if($constants){
+ $this->fmt->sectionTitle('Constants');
+ $max = max(array_map('static::strLen', array_keys($constants)));
+ foreach($constants as $name => $value){
+ $meta = null;
+ $type = array('const');
+ foreach($parents as $parent){
+ if($parent->hasConstant($name)){
+ if($parent !== $reflector){
+ $type[] = 'inherited';
+ $meta = array('sub' => array(array('Prototype defined by', $parent->name)));
+ }
+ break;
+ }
+ }
+
+ $this->fmt->startRow();
+ $this->fmt->sep('::');
+ $this->fmt->colDiv();
+ $this->fmt->startContain($type);
+ $this->fmt->text('name', $name, $meta, $this->linkify($parent, $name));
+ $this->fmt->endContain();
+ $this->fmt->colDiv($max - static::strLen($name));
+ $this->fmt->sep('=');
+ $this->fmt->colDiv();
+ $this->evaluate($value);
+ $this->fmt->endRow();
+ }
+ }
+
+ // object/class properties
+ if($props){
+ $this->fmt->sectionTitle('Properties');
+
+ $max = 0;
+ foreach($props as $idx => $prop)
+ if(($propNameLen = static::strLen($prop->name)) > $max)
+ $max = $propNameLen;
+
+ foreach($props as $idx => $prop){
+
+ if($this->hasInstanceTimedOut())
+ break;
+
+ $bubbles = array();
+ $sourceClass = $prop->getDeclaringClass();
+ $inherited = $reflector->getShortName() !== $sourceClass->getShortName();
+ $meta = $sourceClass->isInternal() ? null : static::parseComment($prop->getDocComment());
+
+ if($meta){
+ if($inherited)
+ $meta['sub'] = array(array('Declared in', $sourceClass->getShortName()));
+
+ if(isset($meta['tags']['var'][0]))
+ $meta['left'] = $meta['tags']['var'][0][0];
+
+ unset($meta['tags']);
+ }
+
+ if($prop->isProtected() || $prop->isPrivate())
+ $prop->setAccessible(true);
+
+ $value = $prop->getValue($subject);
+
+ $this->fmt->startRow();
+ $this->fmt->sep($prop->isStatic() ? '::' : '->');
+ $this->fmt->colDiv();
+
+ $bubbles = array();
+ if($prop->isProtected())
+ $bubbles[] = array('P', 'Protected');
+
+ if($prop->isPrivate())
+ $bubbles[] = array('!', 'Private');
+
+ $this->fmt->bubbles($bubbles);
+
+ $type = array('prop');
+
+ if($inherited)
+ $type[] = 'inherited';
+
+ if($prop->isPrivate())
+ $type[] = 'private';
+
+ $this->fmt->colDiv(2 - count($bubbles));
+ $this->fmt->startContain($type);
+ $this->fmt->text('name', $prop->name, $meta, $this->linkify($prop));
+ $this->fmt->endContain();
+ $this->fmt->colDiv($max - static::strLen($prop->name));
+ $this->fmt->sep('=');
+ $this->fmt->colDiv();
+ $this->evaluate($value);
+ $this->fmt->endRow();
+ }
+ }
+
+ // __debugInfo()
+ if($magicProps){
+ $this->fmt->sectionTitle('Properties (magic)');
+
+ $max = 0;
+ foreach($magicProps as $name => $value)
+ if(($propNameLen = static::strLen($name)) > $max)
+ $max = $propNameLen;
+
+ foreach($magicProps as $name => $value){
+
+ if($this->hasInstanceTimedOut())
+ break;
+
+ // attempt to pull out doc comment from the "regular" property definition
+ try{
+ $prop = $reflector->getProperty($name);
+ $meta = static::parseComment($prop->getDocComment());
+
+ }catch(\Exception $e){
+ $meta = null;
+ }
+
+ $this->fmt->startRow();
+ $this->fmt->sep('->');
+ $this->fmt->colDiv();
+
+ $type = array('prop');
+
+ $this->fmt->startContain($type);
+ $this->fmt->text('name', $name, $meta);
+ $this->fmt->endContain();
+ $this->fmt->colDiv($max - static::strLen($name));
+ $this->fmt->sep('=');
+ $this->fmt->colDiv();
+ $this->evaluate($value);
+ $this->fmt->endRow();
+ }
+ }
+
+ // class methods
+ if($methods && !$this->hasInstanceTimedOut()){
+
+ $this->fmt->sectionTitle('Methods');
+ foreach($methods as $idx => $method){
+
+ $this->fmt->startRow();
+ $this->fmt->sep($method->isStatic() ? '::' : '->');
+ $this->fmt->colDiv();
+
+ $bubbles = array();
+ if($method->isAbstract())
+ $bubbles[] = array('A', 'Abstract');
+
+ if($method->isFinal())
+ $bubbles[] = array('F', 'Final');
+
+ if($method->isProtected())
+ $bubbles[] = array('P', 'Protected');
+
+ if($method->isPrivate())
+ $bubbles[] = array('!', 'Private');
+
+ $this->fmt->bubbles($bubbles);
+
+ $this->fmt->colDiv(4 - count($bubbles));
+
+ // is this method inherited?
+ $inherited = $reflector->getShortName() !== $method->getDeclaringClass()->getShortName();
+
+ $type = array('method');
+
+ if($inherited)
+ $type[] = 'inherited';
+
+ if($method->isPrivate())
+ $type[] = 'private';
+
+ $this->fmt->startContain($type);
+
+ $name = $method->name;
+ if($method->returnsReference())
+ $name = "&{$name}";
+
+ $this->fromReflector($method, $name, $reflector);
+
+ $paramCom = $method->isInternal() ? array() : static::parseComment($method->getDocComment(), 'tags');
+ $paramCom = empty($paramCom['param']) ? array() : $paramCom['param'];
+ $paramCount = $method->getNumberOfParameters();
+
+ $this->fmt->sep('(');
+
+ // process arguments
+ foreach($method->getParameters() as $idx => $parameter){
+ $meta = null;
+ $paramName = "\${$parameter->name}";
+ $optional = $parameter->isOptional();
+ $variadic = static::$env['is56'] && $parameter->isVariadic();
+
+ if($parameter->isPassedByReference())
+ $paramName = "&{$paramName}";
+
+ if($variadic)
+ $paramName = "...{$paramName}";
+
+ $type = array('param');
+
+ if($optional)
+ $type[] = 'optional';
+
+ $this->fmt->startContain($type);
+
+ // attempt to build meta
+ foreach($paramCom as $tag){
+ list($pcTypes, $pcName, $pcDescription) = $tag;
+ if($pcName !== $paramName)
+ continue;
+
+ $meta = array('title' => $pcDescription);
+
+ if($pcTypes)
+ $meta['left'] = $pcTypes;
+
+ break;
+ }
+
+ try{
+ $paramClass = $parameter->getClass();
+ }catch(\Exception $e){
+ // @see https://bugs.php.net/bug.php?id=32177&edit=1
+ }
+
+ if(!empty($paramClass)){
+ $this->fmt->startContain('hint');
+ $this->fromReflector($paramClass, $paramClass->name);
+ $this->fmt->endContain();
+ $this->fmt->sep(' ');
+
+ }elseif($parameter->isArray()){
+ $this->fmt->text('hint', 'array');
+ $this->fmt->sep(' ');
+
+ }else{
+ $hasType = static::$env['is7'] && $parameter->hasType();
+ if($hasType){
+ $type = $parameter->getType();
+ $this->fmt->text('hint', (string)$type);
+ $this->fmt->sep(' ');
+ }
+ }
+
+ $this->fmt->text('name', $paramName, $meta);
+
+ if($optional){
+ $paramValue = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null;
+ if ($paramValue !== null) {
+ $this->fmt->sep(' = ');
+
+ if(static::$env['is546'] && !$parameter->getDeclaringFunction()->isInternal() && $parameter->isDefaultValueConstant()){
+ $this->fmt->text('constant', $parameter->getDefaultValueConstantName(), 'Constant');
+
+ }else{
+ $this->evaluate($paramValue, true);
+ }
+ }
+ }
+
+ $this->fmt->endContain();
+
+ if($idx < $paramCount - 1)
+ $this->fmt->sep(', ');
+ }
+ $this->fmt->sep(')');
+ $this->fmt->endContain();
+
+ $hasReturnType = static::$env['is7'] && $method->hasReturnType();
+ if($hasReturnType){
+ $type = $method->getReturnType();
+ $this->fmt->startContain('ret');
+ $this->fmt->sep(':');
+ $this->fmt->text('hint', (string)$type);
+ $this->fmt->endContain();
+ }
+
+ $this->fmt->endRow();
+ }
+ }
+
+ unset($hashes[$hash]);
+ $this->fmt->endGroup();
+
+ $this->fmt->cacheLock($hash);
+ }
+
+
+
+ /**
+ * Scans for known classes and functions inside the provided expression,
+ * and linkifies them when possible
+ *
+ * @param string $expression Expression to format
+ * @return string Formatted output
+ */
+ protected function evaluateExp($expression = null){
+
+ if($expression === null)
+ return;
+
+ if(static::strLen($expression) > 120)
+ $expression = substr($expression, 0, 120) . '...';
+
+ $this->fmt->sep('> ');
+
+ if(strpos($expression, '(') === false)
+ return $this->fmt->text('expTxt', $expression);
+
+ $keywords = array_map('trim', explode('(', $expression, 2));
+ $parts = array();
+
+ // try to find out if this is a function
+ try{
+ $reflector = new \ReflectionFunction($keywords[0]);
+ $parts[] = array($keywords[0], $reflector, '');
+
+ }catch(\Exception $e){
+
+ if(stripos($keywords[0], 'new ') === 0){
+ $cn = explode(' ' , $keywords[0], 2);
+
+ // linkify 'new keyword' (as constructor)
+ try{
+ $reflector = new \ReflectionMethod($cn[1], '__construct');
+ $parts[] = array($cn[0], $reflector, '');
+
+ }catch(\Exception $e){
+ $reflector = null;
+ $parts[] = $cn[0];
+ }
+
+ // class name...
+ try{
+ $reflector = new \ReflectionClass($cn[1]);
+ $parts[] = array($cn[1], $reflector, ' ');
+
+ }catch(\Exception $e){
+ $reflector = null;
+ $parts[] = $cn[1];
+ }
+
+ }else{
+
+ // we can only linkify methods called statically
+ if(strpos($keywords[0], '::') === false)
+ return $this->fmt->text('expTxt', $expression);
+
+ $cn = explode('::', $keywords[0], 2);
+
+ // attempt to linkify class name
+ try{
+ $reflector = new \ReflectionClass($cn[0]);
+ $parts[] = array($cn[0], $reflector, '');
+
+ }catch(\Exception $e){
+ $reflector = null;
+ $parts[] = $cn[0];
+ }
+
+ // perhaps it's a static class method; try to linkify method
+ try{
+ $reflector = new \ReflectionMethod($cn[0], $cn[1]);
+ $parts[] = array($cn[1], $reflector, '::');
+
+ }catch(\Exception $e){
+ $reflector = null;
+ $parts[] = $cn[1];
+ }
+ }
+ }
+
+ $parts[] = "({$keywords[1]}";
+
+ foreach($parts as $element){
+ if(!is_array($element)){
+ $this->fmt->text('expTxt', $element);
+ continue;
+ }
+
+ list($text, $reflector, $prefix) = $element;
+
+ if($prefix !== '')
+ $this->fmt->text('expTxt', $prefix);
+
+ $this->fromReflector($reflector, $text);
+ }
+
+ }
+
+
+
+ /**
+ * Calculates real string length
+ *
+ * @param string $string
+ * @return int
+ */
+ protected static function strLen($string){
+ $encoding = function_exists('mb_detect_encoding') ? mb_detect_encoding($string) : false;
+ return $encoding ? mb_strlen($string, $encoding) : strlen($string);
+ }
+
+
+
+ /**
+ * Safe str_pad alternative
+ *
+ * @param string $string
+ * @param int $padLen
+ * @param string $padStr
+ * @param int $padType
+ * @return string
+ */
+ protected static function strPad($input, $padLen, $padStr = ' ', $padType = STR_PAD_RIGHT){
+ $diff = strlen($input) - static::strLen($input);
+ return str_pad($input, $padLen + $diff, $padStr, $padType);
+ }
+
+}
+
+
+
+/**
+ * Formatter abstraction
+ */
+abstract class RFormatter{
+
+ /**
+ * Flush output and send contents to the output device
+ */
+ abstract public function flush();
+
+ /**
+ * Generate a base entity
+ *
+ * @param string|array $type
+ * @param string|null $text
+ * @param string|array|null $meta
+ * @param string|null $uri
+ */
+ abstract public function text($type, $text = null, $meta = null, $uri = null);
+
+ /**
+ * Generate container start token
+ *
+ * @param string|array $type
+ * @param string|bool $label
+ */
+ public function startContain($type, $label = false){}
+
+ /**
+ * Generate container ending token
+ */
+ public function endContain(){}
+
+ /**
+ * Generate empty group token
+ *
+ * @param string $prefix
+ */
+ public function emptyGroup($prefix = ''){}
+
+ /**
+ * Generate group start token
+ *
+ * This method must return boolean TRUE on success, false otherwise (eg. max depth reached).
+ * The evaluator will skip this group on FALSE
+ *
+ * @param string $prefix
+ * @return bool
+ */
+ public function startGroup($prefix = ''){}
+
+ /**
+ * Generate group ending token
+ */
+ public function endGroup(){}
+
+ /**
+ * Generate section title
+ *
+ * @param string $title
+ */
+ public function sectionTitle($title){}
+
+ /**
+ * Generate row start token
+ */
+ public function startRow(){}
+
+ /**
+ * Generate row ending token
+ */
+ public function endRow(){}
+
+ /**
+ * Column divider (cell delimiter)
+ *
+ * @param int $padLen
+ */
+ public function colDiv($padLen = null){}
+
+ /**
+ * Generate modifier tokens
+ *
+ * @param array $items
+ */
+ public function bubbles(array $items){}
+
+ /**
+ * Input expression start
+ */
+ public function startExp(){}
+
+ /**
+ * Input expression end
+ */
+ public function endExp(){}
+
+ /**
+ * Root starting token
+ */
+ public function startRoot(){}
+
+ /**
+ * Root ending token
+ */
+ public function endRoot(){}
+
+ /**
+ * Separator token
+ *
+ * @param string $label
+ */
+ public function sep($label = ' '){}
+
+ /**
+ * Resolve cache request
+ *
+ * If the ID is not present in the cache, then a new cache entry is created
+ * for the given ID, and string offsets are captured until cacheLock is called
+ *
+ * This method must return TRUE if the ID exists in the cache, and append the cached item
+ * to the output, FALSE otherwise.
+ *
+ * @param string $id
+ * @return bool
+ */
+ public function didCache($id){
+ return false;
+ }
+
+ /**
+ * Ends cache capturing for the given ID
+ *
+ * @param string $id
+ */
+ public function cacheLock($id){}
+
+}
+
+
+
+
+/**
+ * Generates the output in HTML5 format
+ *
+ */
+class RHtmlFormatter extends RFormatter{
+
+ protected
+
+ /**
+ * Actual output
+ *
+ * @var string
+ */
+ $out = '',
+
+ /**
+ * Tracks current nesting level
+ *
+ * @var int
+ */
+ $level = 0,
+
+ /**
+ * Stores tooltip content for all entries
+ *
+ * To avoid having duplicate tooltip data in the HTML, we generate them once,
+ * and use references (the Q index) to pull data when required;
+ * this improves performance significantly
+ *
+ * @var array
+ */
+ $tips = array(),
+
+ /**
+ * Used to cache output to speed up processing.
+ *
+ * Contains hashes as keys and string offsets as values.
+ * Cached objects will not be processed again in the same query
+ *
+ * @var array
+ */
+ $cache = array(),
+
+ /**
+ * Map of used HTML tag and attributes
+ *
+ * @var string
+ */
+ $def = array();
+
+
+
+ protected static
+
+ /**
+ * Instance counter
+ *
+ * @var int
+ */
+ $counter = 0,
+
+ /**
+ * Tracks style/jscript inclusion state
+ *
+ * @var bool
+ */
+ $didAssets = false;
+
+
+ public function __construct(){
+
+ if(ref::config('validHtml')){
+
+ $this->def = array(
+ 'base' => 'span',
+ 'tip' => 'div',
+ 'cell' => 'data-cell',
+ 'table' => 'data-table',
+ 'row' => 'data-row',
+ 'group' => 'data-group',
+ 'gLabel' => 'data-gLabel',
+ 'match' => 'data-match',
+ 'tipRef' => 'data-tip',
+ );
+
+
+ }else{
+
+ $this->def = array(
+ 'base' => 'r',
+ 'tip' => 't',
+ 'cell' => 'c',
+ 'table' => 't',
+ 'row' => 'r',
+ 'group' => 'g',
+ 'gLabel' => 'gl',
+ 'match' => 'm',
+ 'tipRef' => 'h',
+ );
+
+ }
+
+ }
+
+
+
+ public function flush(){
+ print $this->out;
+ $this->out = '';
+ $this->cache = array();
+ $this->tips = array();
+ }
+
+
+ public function didCache($id){
+
+ if(!isset($this->cache[$id])){
+ $this->cache[$id] = array();
+ $this->cache[$id][] = strlen($this->out);
+ return false;
+ }
+
+ if(!isset($this->cache[$id][1])){
+ $this->cache[$id][0] = strlen($this->out);
+ return false;
+ }
+
+ $this->out .= substr($this->out, $this->cache[$id][0], $this->cache[$id][1]);
+ return true;
+ }
+
+ public function cacheLock($id){
+ $this->cache[$id][] = strlen($this->out) - $this->cache[$id][0];
+ }
+
+
+ public function sep($label = ' '){
+ $this->out .= $label !== ' ' ? '
' . static::escape($label) . '' : $label;
+ }
+
+ public function text($type, $text = null, $meta = null, $uri = null){
+
+ if(!is_array($type))
+ $type = (array)$type;
+
+ $tip = '';
+ $text = ($text !== null) ? static::escape($text) : static::escape($type[0]);
+
+ if(in_array('special', $type)){
+ $text = strtr($text, array(
+ "\r" => '
\r', // carriage return
+ "\t" => '
\t', // horizontal tab
+ "\n" => '
\n', // linefeed (new line)
+ "\v" => '
\v', // vertical tab
+ "\e" => '
\e', // escape
+ "\f" => '
\f', // form feed
+ "\0" => '
\0',
+ ));
+ }
+
+ // generate tooltip reference (probably the slowest part of the code ;)
+ if($meta !== null){
+ $tipIdx = array_search($meta, $this->tips, true);
+
+ if($tipIdx === false)
+ $tipIdx = array_push($this->tips, $meta) - 1;
+
+ $tip = " {$this->def['tipRef']}=\"{$tipIdx}\"";
+ //$tip = sprintf('%s="%d"', $this->def['tipRef'], $tipIdx);
+ }
+
+ // wrap text in a link?
+ if($uri !== null)
+ $text = '
' . $text . '';
+
+ $typeStr = '';
+ foreach($type as $part)
+ $typeStr .= " data-{$part}";
+
+ $this->out .= "<{$this->def['base']}{$typeStr}{$tip}>{$text}{$this->def['base']}>";
+ //$this->out .= sprintf('<%1$s%2$s %3$s>%4$s%1$s>', $this->def['base'], $typeStr, $tip, $text);
+ }
+
+ public function startContain($type, $label = false){
+
+ if(!is_array($type))
+ $type = (array)$type;
+
+ if($label)
+ $this->out .= '
';
+
+ $typeStr = '';
+ foreach($type as $part)
+ $typeStr .= " data-{$part}";
+
+ $this->out .= "<{$this->def['base']}{$typeStr}>";
+
+ if($label)
+ $this->out .= "<{$this->def['base']} {$this->def['match']}>{$type[0]}{$this->def['base']}>";
+ }
+
+ public function endContain(){
+ $this->out .= "{$this->def['base']}>";
+ }
+
+ public function emptyGroup($prefix = ''){
+
+ if($prefix !== '')
+ $prefix = "<{$this->def['base']} {$this->def['gLabel']}>" . static::escape($prefix) . "{$this->def['base']}>";
+
+ $this->out .= "
({$prefix}
)";
+ }
+
+
+ public function startGroup($prefix = ''){
+
+ $maxDepth = ref::config('maxDepth');
+
+ if(($maxDepth > 0) && (($this->level + 1) > $maxDepth)){
+ $this->emptyGroup('...');
+ return false;
+ }
+
+ $this->level++;
+
+ $expLvl = ref::config('expLvl');
+ $exp = ($expLvl < 0) || (($expLvl > 0) && ($this->level <= $expLvl)) ? ' data-exp' : '';
+
+ if($prefix !== '')
+ $prefix = "<{$this->def['base']} {$this->def['gLabel']}>" . static::escape($prefix) . "{$this->def['base']}>";
+
+ $this->out .= "
({$prefix}<{$this->def['base']} data-toggle{$exp}>{$this->def['base']}><{$this->def['base']} {$this->def['group']}><{$this->def['base']} {$this->def['table']}>";
+
+ return true;
+ }
+
+ public function endGroup(){
+ $this->out .= "{$this->def['base']}>{$this->def['base']}>
)";
+ $this->level--;
+ }
+
+ public function sectionTitle($title){
+ $this->out .= "{$this->def['base']}><{$this->def['base']} data-tHead>{$title}{$this->def['base']}><{$this->def['base']} {$this->def['table']}>";
+ }
+
+ public function startRow(){
+ $this->out .= "<{$this->def['base']} {$this->def['row']}><{$this->def['base']} {$this->def['cell']}>";
+ }
+
+ public function endRow(){
+ $this->out .= "{$this->def['base']}>{$this->def['base']}>";
+ }
+
+ public function colDiv($padLen = null){
+ $this->out .= "{$this->def['base']}><{$this->def['base']} {$this->def['cell']}>";
+ }
+
+ public function bubbles(array $items){
+
+ if(!$items)
+ return;
+
+ $this->out .= "<{$this->def['base']} data-mod>";
+
+ foreach($items as $info)
+ $this->out .= $this->text('mod-' . strtolower($info[1]), $info[0], $info[1]);
+
+ $this->out .= "{$this->def['base']}>";
+ }
+
+ public function startExp(){
+ $this->out .= "<{$this->def['base']} data-input>";
+ }
+
+ public function endExp(){
+ if(ref::config('showBacktrace') && ($trace = ref::getBacktrace())){
+ $docRoot = isset($_SERVER['DOCUMENT_ROOT']) ? $_SERVER['DOCUMENT_ROOT'] : '';
+ $path = strpos($trace['file'], $docRoot) !== 0 ? $trace['file'] : ltrim(str_replace($docRoot, '', $trace['file']), '/');
+ $this->out .= "<{$this->def['base']} data-backtrace>{$path}:{$trace['line']}{$this->def['base']}>";
+ }
+
+ $this->out .= "{$this->def['base']}><{$this->def['base']} data-output>";
+ }
+
+ public function startRoot(){
+ $this->out .= '
' . static::getAssets() . '
';
+ }
+
+ public function endRoot(){
+ $this->out .= "{$this->def['base']}>";
+
+ // process tooltips
+ $tipHtml = '';
+ foreach($this->tips as $idx => $meta){
+
+ $tip = '';
+ if(!is_array($meta))
+ $meta = array('title' => $meta);
+
+ $meta += array(
+ 'title' => '',
+ 'left' => '',
+ 'description' => '',
+ 'tags' => array(),
+ 'sub' => array(),
+ );
+
+ $meta = static::escape($meta);
+ $cols = array();
+
+ if($meta['left'])
+ $cols[] = "<{$this->def['base']} {$this->def['cell']} data-varType>{$meta['left']}{$this->def['base']}>";
+
+ $title = $meta['title'] ? "<{$this->def['base']} data-title>{$meta['title']}{$this->def['base']}>" : '';
+ $desc = $meta['description'] ? "<{$this->def['base']} data-desc>{$meta['description']}{$this->def['base']}>" : '';
+ $tags = '';
+
+ foreach($meta['tags'] as $tag => $values){
+ foreach($values as $value){
+ if($tag === 'param'){
+ $value[0] = "{$value[0]} {$value[1]}";
+ unset($value[1]);
+ }
+
+ $value = is_array($value) ? implode("{$this->def['base']}><{$this->def['base']} {$this->def['cell']}>", $value) : $value;
+ $tags .= "<{$this->def['base']} {$this->def['row']}><{$this->def['base']} {$this->def['cell']}>@{$tag}{$this->def['base']}><{$this->def['base']} {$this->def['cell']}>{$value}{$this->def['base']}>{$this->def['base']}>";
+ }
+ }
+
+ if($tags)
+ $tags = "<{$this->def['base']} {$this->def['table']}>{$tags}{$this->def['base']}>";
+
+ if($title || $desc || $tags)
+ $cols[] = "<{$this->def['base']} {$this->def['cell']}>{$title}{$desc}{$tags}{$this->def['base']}>";
+
+ if($cols)
+ $tip = "<{$this->def['base']} {$this->def['row']}>" . implode('', $cols) . "{$this->def['base']}>";
+
+ $sub = '';
+ foreach($meta['sub'] as $line)
+ $sub .= "<{$this->def['base']} {$this->def['row']}><{$this->def['base']} {$this->def['cell']}>" . implode("{$this->def['base']}><{$this->def['base']} {$this->def['cell']}>", $line) . "{$this->def['base']}>{$this->def['base']}>";
+
+ if($sub)
+ $tip .= "<{$this->def['base']} {$this->def['row']}><{$this->def['base']} {$this->def['cell']} data-sub><{$this->def['base']} {$this->def['table']}>{$sub}{$this->def['base']}>{$this->def['base']}>{$this->def['base']}>";
+
+ if($tip)
+ $this->out .= "<{$this->def['tip']}>{$tip}{$this->def['tip']}>";
+ }
+
+ if(($timeout = ref::getTimeoutPoint()) > 0)
+ $this->out .= sprintf("<{$this->def['base']} data-error>Listing incomplete. Timed-out after %4.2fs{$this->def['base']}>", $timeout);
+
+ $this->out .= '
';
+ }
+
+
+
+ /**
+ * Get styles and javascript (only generated for the 1st call)
+ *
+ * @return string
+ */
+ public static function getAssets(){
+
+ // first call? include styles and javascript
+ if(static::$didAssets)
+ return '';
+
+ ob_start();
+
+ if(ref::config('stylePath') !== false){
+ ?>
+
+
+
+ out;
+ $this->out = '';
+ $this->cache = array();
+ }
+
+ public function sep($label = ' '){
+ $this->out .= $label;
+ }
+
+ public function text($type, $text = null, $meta = null, $uri = null){
+
+ if(!is_array($type))
+ $type = (array)$type;
+
+ if($text === null)
+ $text = $type[0];
+
+ if(in_array('special', $type, true)){
+ $text = strtr($text, array(
+ "\r" => '\r', // carriage return
+ "\t" => '\t', // horizontal tab
+ "\n" => '\n', // linefeed (new line)
+ "\v" => '\v', // vertical tab
+ "\e" => '\e', // escape
+ "\f" => '\f', // form feed
+ "\0" => '\0',
+ ));
+
+ $this->out .= $text;
+ return;
+ }
+
+ $formatMap = array(
+ 'string' => '%3$s "%2$s"',
+ 'integer' => 'int(%2$s)',
+ 'double' => 'double(%2$s)',
+ 'true' => 'bool(%2$s)',
+ 'false' => 'bool(%2$s)',
+ 'key' => '[%2$s]',
+ );
+
+ if(!is_string($meta))
+ $meta = '';
+
+ $this->out .= isset($formatMap[$type[0]]) ? sprintf($formatMap[$type[0]], $type[0], $text, $meta) : $text;
+ }
+
+ public function startContain($type, $label = false){
+
+ if(!is_array($type))
+ $type = (array)$type;
+
+ if($label)
+ $this->out .= "\n" . str_repeat(' ', $this->indent + $this->levelPad[$this->level]) . "┗ {$type[0]} ~ ";
+ }
+
+ public function emptyGroup($prefix = ''){
+ $this->out .= "({$prefix})";
+ }
+
+ public function startGroup($prefix = ''){
+
+ $maxDepth = ref::config('maxDepth');
+
+ if(($maxDepth > 0) && (($this->level + 1) > $maxDepth)){
+ $this->emptyGroup('...');
+ return false;
+ }
+
+ $this->level++;
+ $this->out .= '(';
+
+ $this->indent += $this->levelPad[$this->level - 1];
+ return true;
+ }
+
+ public function endGroup(){
+ $this->out .= "\n" . str_repeat(' ', $this->indent) . ')';
+ $this->indent -= $this->levelPad[$this->level - 1];
+ $this->level--;
+ }
+
+ public function sectionTitle($title){
+ $pad = str_repeat(' ', $this->indent + 2);
+ $this->out .= sprintf("\n\n%s%s\n%s%s", $pad, $title, $pad, str_repeat('-', strlen($title)));
+ }
+
+ public function startRow(){
+ $this->out .= "\n " . str_repeat(' ', $this->indent);
+ $this->lastLineSt = strlen($this->out);
+ }
+
+ public function endRow(){
+ }
+
+ public function colDiv($padLen = null){
+ $padLen = ($padLen !== null) ? $padLen + 1 : 1;
+ $this->out .= str_repeat(' ', $padLen);
+
+ $this->lastIdx = strlen($this->out);
+ $this->levelPad[$this->level] = $this->lastIdx - $this->lastLineSt + 2;
+ }
+
+ public function bubbles(array $items){
+
+ if(!$items){
+ $this->out .= ' ';
+ return;
+ }
+
+ $this->out .= '<';
+
+ foreach($items as $item)
+ $this->out .= $item[0];
+
+ $this->out .= '>';
+ }
+
+ public function endExp(){
+
+ if(ref::config('showBacktrace') && ($trace = ref::getBacktrace()))
+ $this->out .= ' - ' . $trace['file'] . ':' . $trace['line'];
+
+ $this->out .= "\n" . str_repeat('=', strlen($this->out)) . "\n";
+ }
+
+ public function startRoot(){
+ $this->out .= "\n\n";
+
+ }
+
+ public function endRoot(){
+ $this->out .= "\n";
+ if(($timeout = ref::getTimeoutPoint()) > 0)
+ $this->out .= sprintf("\n-- Listing incomplete. Timed-out after %4.2fs -- \n", $timeout);
+ }
+
+}
+
+
+
+/**
+ * Text formatter with color support for CLI -- unfinished
+ *
+ */
+class RCliTextFormatter extends RTextFormatter{
+
+ public function sectionTitle($title){
+ $pad = str_repeat(' ', $this->indent + 2);
+ $this->out .= sprintf("\n\n%s\x1b[4;97m%s\x1b[0m", $pad, $title);
+ }
+
+ public function startExp(){
+ $this->out .= "\x1b[1;44;96m ";
+ }
+
+ public function endExp(){
+ if(ref::config('showBacktrace') && ($trace = ref::getBacktrace()))
+ $this->out .= "\x1b[0m\x1b[44;36m " . $trace['file'] . ':' . $trace['line'];
+
+ $this->out .= " \x1b[0m\n";
+ }
+
+ public function endRoot(){
+ $this->out .= "\n";
+ if(($timeout = ref::getTimeoutPoint()) > 0)
+ $this->out .= sprintf("\n\x1b[3;91m-- Listing incomplete. Timed-out after %4.2fs --\x1b[0m\n", $timeout);
+ }
+
+}
diff --git a/web/rtc.php b/web/rtc.php
index 70313c9..59c3c6b 100644
--- a/web/rtc.php
+++ b/web/rtc.php
@@ -282,7 +282,7 @@ if ($db_file) {
echo "
".human_filesize($row_a['Size'])."
";
}
- if ($row_a['type'] == "dir" && $row_a['items']) {
+ if ($row_a['Type'] == "dir" && $row_a['items']) {
echo "
".$row_a['items']." items
";
}