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}def['base']}>"; + //$this->out .= sprintf('<%1$s%2$s %3$s>%4$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]}def['base']}>"; + } + + public function endContain(){ + $this->out .= "def['base']}>"; + } + + public function emptyGroup($prefix = ''){ + + if($prefix !== '') + $prefix = "<{$this->def['base']} {$this->def['gLabel']}>" . static::escape($prefix) . "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) . "def['base']}>"; + + $this->out .= "({$prefix}<{$this->def['base']} data-toggle{$exp}>def['base']}><{$this->def['base']} {$this->def['group']}><{$this->def['base']} {$this->def['table']}>"; + + return true; + } + + public function endGroup(){ + $this->out .= "def['base']}>def['base']}>)"; + $this->level--; + } + + public function sectionTitle($title){ + $this->out .= "def['base']}><{$this->def['base']} data-tHead>{$title}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 .= "def['base']}>def['base']}>"; + } + + public function colDiv($padLen = null){ + $this->out .= "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 .= "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']}def['base']}>"; + } + + $this->out .= "def['base']}><{$this->def['base']} data-output>"; + } + + public function startRoot(){ + $this->out .= '
' . static::getAssets() . '
'; + } + + public function endRoot(){ + $this->out .= "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']}def['base']}>"; + + $title = $meta['title'] ? "<{$this->def['base']} data-title>{$meta['title']}def['base']}>" : ''; + $desc = $meta['description'] ? "<{$this->def['base']} data-desc>{$meta['description']}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("def['base']}><{$this->def['base']} {$this->def['cell']}>", $value) : $value; + $tags .= "<{$this->def['base']} {$this->def['row']}><{$this->def['base']} {$this->def['cell']}>@{$tag}def['base']}><{$this->def['base']} {$this->def['cell']}>{$value}def['base']}>def['base']}>"; + } + } + + if($tags) + $tags = "<{$this->def['base']} {$this->def['table']}>{$tags}def['base']}>"; + + if($title || $desc || $tags) + $cols[] = "<{$this->def['base']} {$this->def['cell']}>{$title}{$desc}{$tags}def['base']}>"; + + if($cols) + $tip = "<{$this->def['base']} {$this->def['row']}>" . implode('', $cols) . "def['base']}>"; + + $sub = ''; + foreach($meta['sub'] as $line) + $sub .= "<{$this->def['base']} {$this->def['row']}><{$this->def['base']} {$this->def['cell']}>" . implode("def['base']}><{$this->def['base']} {$this->def['cell']}>", $line) . "def['base']}>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}def['base']}>def['base']}>def['base']}>"; + + if($tip) + $this->out .= "<{$this->def['tip']}>{$tip}def['tip']}>"; + } + + if(($timeout = ref::getTimeoutPoint()) > 0) + $this->out .= sprintf("<{$this->def['base']} data-error>Listing incomplete. Timed-out after %4.2fsdef['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
"; }