Exporters From Japan
Wholesale exporters from Japan   Company Established 1983
CARVIEW
Select Language
',trigger:"hover focus",title:"carview.php?tsp=",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",sanitize:!0,sanitizeFn:null,whiteList:Ee},je="show",He="out",Re={HIDE:"hide"+De,HIDDEN:"hidden"+De,SHOW:"show"+De,SHOWN:"shown"+De,INSERTED:"inserted"+De,CLICK:"click"+De,FOCUSIN:"focusin"+De,FOCUSOUT:"focusout"+De,MOUSEENTER:"mouseenter"+De,MOUSELEAVE:"mouseleave"+De},xe="fade",Fe="show",Ue=".tooltip-inner",We=".arrow",qe="hover",Me="focus",Ke="click",Qe="manual",Be=function(){function i(t,e){if("undefined"==typeof u)throw new TypeError("Bootstrap's tooltips require Popper.js (https://popper.js.org/)");this._isEnabled=!0,this._timeout=0,this._hoverState="carview.php?tsp=",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=g(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(g(this.getTipElement()).hasClass(Fe))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),g.removeData(this.element,this.constructor.DATA_KEY),g(this.element).off(this.constructor.EVENT_KEY),g(this.element).closest(".modal").off("hide.bs.modal"),this.tip&&g(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,(this._activeTrigger=null)!==this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===g(this.element).css("display"))throw new Error("Please use show on visible elements");var t=g.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){g(this.element).trigger(t);var n=_.findShadowRoot(this.element),i=g.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!i)return;var o=this.getTipElement(),r=_.getUID(this.constructor.NAME);o.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&g(o).addClass(xe);var s="function"==typeof this.config.placement?this.config.placement.call(this,o,this.element):this.config.placement,a=this._getAttachment(s);this.addAttachmentClass(a);var l=this._getContainer();g(o).data(this.constructor.DATA_KEY,this),g.contains(this.element.ownerDocument.documentElement,this.tip)||g(o).appendTo(l),g(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new u(this.element,o,{placement:a,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:We},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}}),g(o).addClass(Fe),"ontouchstart"in document.documentElement&&g(document.body).children().on("mouseover",null,g.noop);var c=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,g(e.element).trigger(e.constructor.Event.SHOWN),t===He&&e._leave(null,e)};if(g(this.tip).hasClass(xe)){var h=_.getTransitionDurationFromElement(this.tip);g(this.tip).one(_.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},t.hide=function(t){var e=this,n=this.getTipElement(),i=g.Event(this.constructor.Event.HIDE),o=function(){e._hoverState!==je&&n.parentNode&&n.parentNode.removeChild(n),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),g(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(g(this.element).trigger(i),!i.isDefaultPrevented()){if(g(n).removeClass(Fe),"ontouchstart"in document.documentElement&&g(document.body).children().off("mouseover",null,g.noop),this._activeTrigger[Ke]=!1,this._activeTrigger[Me]=!1,this._activeTrigger[qe]=!1,g(this.tip).hasClass(xe)){var r=_.getTransitionDurationFromElement(n);g(n).one(_.TRANSITION_END,o).emulateTransitionEnd(r)}else o();this._hoverState="carview.php?tsp="}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){g(this.getTipElement()).addClass(Ae+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},t.setContent=function(){var t=this.getTipElement();this.setElementContent(g(t.querySelectorAll(Ue)),this.getTitle()),g(t).removeClass(xe+" "+Fe)},t.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=Se(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?g(e).parent().is(t)||t.empty().append(e):t.text(g(e).text())},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},t._getOffset=function(){var e=this,t={};return"function"==typeof this.config.offset?t.fn=function(t){return t.offsets=l({},t.offsets,e.config.offset(t.offsets,e.element)||{}),t}:t.offset=this.config.offset,t},t._getContainer=function(){return!1===this.config.container?document.body:_.isElement(this.config.container)?g(this.config.container):g(document).find(this.config.container)},t._getAttachment=function(t){return Pe[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)g(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==Qe){var e=t===qe?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===qe?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;g(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}}),g(this.element).closest(".modal").on("hide.bs.modal",function(){i.element&&i.hide()}),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:"carview.php?tsp="}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||"carview.php?tsp="),this.element.setAttribute("title","carview.php?tsp="))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Me:qe]=!0),g(e.getTipElement()).hasClass(Fe)||e._hoverState===je?e._hoverState=je:(clearTimeout(e._timeout),e._hoverState=je,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===je&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Me:qe]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=He,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===He&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){var e=g(this.element).data();return Object.keys(e).forEach(function(t){-1!==Oe.indexOf(t)&&delete e[t]}),"number"==typeof(t=l({},this.constructor.Default,e,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),_.typeCheckConfig(be,t,this.constructor.DefaultType),t.sanitize&&(t.template=Se(t.template,t.whiteList,t.sanitizeFn)),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(Ne);null!==e&&e.length&&t.removeClass(e.join("carview.php?tsp="))},t._handlePopperPlacementChange=function(t){var e=t.instance;this.tip=e.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},t._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(g(t).removeClass(xe),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},i._jQueryInterface=function(n){return this.each(function(){var t=g(this).data(Ie),e="object"==typeof n&&n;if((t||!/dispose|hide/.test(n))&&(t||(t=new i(this,e),g(this).data(Ie,t)),"string"==typeof n)){if("undefined"==typeof t[n])throw new TypeError('No method named "'+n+'"');t[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.3.1"}},{key:"Default",get:function(){return Le}},{key:"NAME",get:function(){return be}},{key:"DATA_KEY",get:function(){return Ie}},{key:"Event",get:function(){return Re}},{key:"EVENT_KEY",get:function(){return De}},{key:"DefaultType",get:function(){return ke}}]),i}();g.fn[be]=Be._jQueryInterface,g.fn[be].Constructor=Be,g.fn[be].noConflict=function(){return g.fn[be]=we,Be._jQueryInterface};var Ve="popover",Ye="bs.popover",ze="."+Ye,Xe=g.fn[Ve],$e="bs-popover",Ge=new RegExp("(^|\\s)"+$e+"\\S+","g"),Je=l({},Be.Default,{placement:"right",trigger:"click",content:"carview.php?tsp=",template:''}),Ze=l({},Be.DefaultType,{content:"(string|element|function)"}),tn="fade",en="show",nn=".popover-header",on=".popover-body",rn={HIDE:"hide"+ze,HIDDEN:"hidden"+ze,SHOW:"show"+ze,SHOWN:"shown"+ze,INSERTED:"inserted"+ze,CLICK:"click"+ze,FOCUSIN:"focusin"+ze,FOCUSOUT:"focusout"+ze,MOUSEENTER:"mouseenter"+ze,MOUSELEAVE:"mouseleave"+ze},sn=function(t){var e,n;function i(){return t.apply(this,arguments)||this}n=t,(e=i).prototype=Object.create(n.prototype),(e.prototype.constructor=e).__proto__=n;var o=i.prototype;return o.isWithContent=function(){return this.getTitle()||this._getContent()},o.addAttachmentClass=function(t){g(this.getTipElement()).addClass($e+"-"+t)},o.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},o.setContent=function(){var t=g(this.getTipElement());this.setElementContent(t.find(nn),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(on),e),t.removeClass(tn+" "+en)},o._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},o._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(Ge);null!==e&&0=this._offsets[o]&&("undefined"==typeof this._offsets[o+1]||td||0>e||d>o||e>p||d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement||j){if(k&&d3.event.relatedTarget&&void 0===d3.event.relatedTarget.ownerSVGElement&&(void 0===d3.event.relatedTarget.className||d3.event.relatedTarget.className.match(c.nvPointerEventsClass)))return;return h.elementMouseout({mouseX:d,mouseY:e}),b.renderGuideLine(null),void c.hidden(!0)}c.hidden(!1);var l=g.invert(d);h.elementMousemove({mouseX:d,mouseY:e,pointXValue:l}),"dblclick"===d3.event.type&&h.elementDblclick({mouseX:d,mouseY:e,pointXValue:l}),"click"===d3.event.type&&h.elementClick({mouseX:d,mouseY:e,pointXValue:l})}var n=d3.select(this),o=d||960,p=e||400,q=n.selectAll("g.nv-wrap.nv-interactiveLineLayer").data([l]),r=q.enter().append("g").attr("class"," nv-wrap nv-interactiveLineLayer");r.append("g").attr("class","nv-interactiveGuideLine"),j&&(j.on("touchmove",m).on("mousemove",m,!0).on("mouseout",m,!0).on("dblclick",m).on("click",m),b.guideLine=null,b.renderGuideLine=function(c){i&&(b.guideLine&&b.guideLine.attr("x1")===c||a.dom.write(function(){var b=q.select(".nv-interactiveGuideLine").selectAll("line").data(null!=c?[a.utils.NaNtoZero(c)]:[],String);b.enter().append("line").attr("class","nv-guideline").attr("x1",function(a){return a}).attr("x2",function(a){return a}).attr("y1",p).attr("y2",0),b.exit().remove()}))})})}var c=a.models.tooltip();c.duration(0).hideDelay(0)._isInteractiveLayer(!0).hidden(!1);var d=null,e=null,f={left:0,top:0},g=d3.scale.linear(),h=d3.dispatch("elementMousemove","elementMouseout","elementClick","elementDblclick"),i=!0,j=null,k="ActiveXObject"in window;return b.dispatch=h,b.tooltip=c,b.margin=function(a){return arguments.length?(f.top="undefined"!=typeof a.top?a.top:f.top,f.left="undefined"!=typeof a.left?a.left:f.left,b):f},b.width=function(a){return arguments.length?(d=a,b):d},b.height=function(a){return arguments.length?(e=a,b):e},b.xScale=function(a){return arguments.length?(g=a,b):g},b.showGuideLine=function(a){return arguments.length?(i=a,b):i},b.svgContainer=function(a){return arguments.length?(j=a,b):j},b},a.interactiveBisect=function(a,b,c){"use strict";if(!(a instanceof Array))return null;var d;d="function"!=typeof c?function(a){return a.x}:c;var e=function(a,b){return d(a)-b},f=d3.bisector(e).left,g=d3.max([0,f(a,b)-1]),h=d(a[g]);if("undefined"==typeof h&&(h=g),h===b)return g;var i=d3.min([g+1,a.length-1]),j=d(a[i]);return"undefined"==typeof j&&(j=i),Math.abs(j-b)>=Math.abs(h-b)?g:i},a.nearestValueIndex=function(a,b,c){"use strict";var d=1/0,e=null;return a.forEach(function(a,f){var g=Math.abs(b-a);null!=a&&d>=g&&c>g&&(d=g,e=f)}),e},function(){"use strict";a.models.tooltip=function(){function b(){if(k){var a=d3.select(k);"svg"!==a.node().tagName&&(a=a.select("svg"));var b=a.node()?a.attr("viewBox"):null;if(b){b=b.split(" ");var c=parseInt(a.style("width"),10)/b[2];p.left=p.left*c,p.top=p.top*c}}}function c(){if(!n){var a;a=k?k:document.body,n=d3.select(a).append("div").attr("class","nvtooltip "+(j?j:"xy-tooltip")).attr("id",v),n.style("top",0).style("left",0),n.style("opacity",0),n.selectAll("div, table, td, tr").classed(w,!0),n.classed(w,!0),o=n.node()}}function d(){if(r&&B(e)){b();var f=p.left,g=null!==i?i:p.top;return a.dom.write(function(){c();var b=A(e);b&&(o.innerHTML=b),k&&u?a.dom.read(function(){var a=k.getElementsByTagName("svg")[0],b={left:0,top:0};if(a){var c=a.getBoundingClientRect(),d=k.getBoundingClientRect(),e=c.top;if(0>e){var i=k.getBoundingClientRect();e=Math.abs(e)>i.height?0:e}b.top=Math.abs(e-d.top),b.left=Math.abs(c.left-d.left)}f+=k.offsetLeft+b.left-2*k.scrollLeft,g+=k.offsetTop+b.top-2*k.scrollTop,h&&h>0&&(g=Math.floor(g/h)*h),C([f,g])}):C([f,g])}),d}}var e=null,f="w",g=25,h=0,i=null,j=null,k=null,l=!0,m=400,n=null,o=null,p={left:null,top:null},q={left:0,top:0},r=!0,s=100,t=!0,u=!1,v="nvtooltip-"+Math.floor(1e5*Math.random()),w="nv-pointer-events-none",x=function(a){return a},y=function(a){return a},z=function(a){return a},A=function(a){if(null===a)return"carview.php?tsp=";var b=d3.select(document.createElement("table"));if(t){var c=b.selectAll("thead").data([a]).enter().append("thead");c.append("tr").append("td").attr("colspan",3).append("strong").classed("x-value",!0).html(y(a.value))}var d=b.selectAll("tbody").data([a]).enter().append("tbody"),e=d.selectAll("tr").data(function(a){return a.series}).enter().append("tr").classed("highlight",function(a){return a.highlight});e.append("td").classed("legend-color-guide",!0).append("div").style("background-color",function(a){return a.color}),e.append("td").classed("key",!0).html(function(a,b){return z(a.key,b)}),e.append("td").classed("value",!0).html(function(a,b){return x(a.value,b)}),e.selectAll("td").each(function(a){if(a.highlight){var b=d3.scale.linear().domain([0,1]).range(["#fff",a.color]),c=.6;d3.select(this).style("border-bottom-color",b(c)).style("border-top-color",b(c))}});var f=b.node().outerHTML;return void 0!==a.footer&&(f+=""),f},B=function(a){if(a&&a.series){if(a.series instanceof Array)return!!a.series.length;if(a.series instanceof Object)return a.series=[a.series],!0}return!1},C=function(b){o&&a.dom.read(function(){var c,d,e=parseInt(o.offsetHeight,10),h=parseInt(o.offsetWidth,10),i=a.utils.windowSize().width,j=a.utils.windowSize().height,k=window.pageYOffset,p=window.pageXOffset;j=window.innerWidth>=document.body.scrollWidth?j:j-16,i=window.innerHeight>=document.body.scrollHeight?i:i-16;var r,t,u=function(a){var b=d;do isNaN(a.offsetTop)||(b+=a.offsetTop),a=a.offsetParent;while(a);return b},v=function(a){var b=c;do isNaN(a.offsetLeft)||(b+=a.offsetLeft),a=a.offsetParent;while(a);return b};switch(f){case"e":c=b[0]-h-g,d=b[1]-e/2,r=v(o),t=u(o),p>r&&(c=b[0]+g>p?b[0]+g:p-r+c),k>t&&(d=k-t+d),t+e>k+j&&(d=k+j-t+d-e);break;case"w":c=b[0]+g,d=b[1]-e/2,r=v(o),t=u(o),r+h>i&&(c=b[0]-h-g),k>t&&(d=k+5),t+e>k+j&&(d=k+j-t+d-e);break;case"n":c=b[0]-h/2-5,d=b[1]+g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),t+e>k+j&&(d=k+j-t+d-e);break;case"s":c=b[0]-h/2,d=b[1]-e-g,r=v(o),t=u(o),p>r&&(c=p+5),r+h>i&&(c=c-h/2+5),k>t&&(d=k);break;case"none":c=b[0],d=b[1]-g,r=v(o),t=u(o)}c-=q.left,d-=q.top;var w=o.getBoundingClientRect(),k=window.pageYOffset||document.documentElement.scrollTop,p=window.pageXOffset||document.documentElement.scrollLeft,x="translate("+(w.left+p)+"px, "+(w.top+k)+"px)",y="translate("+c+"px, "+d+"px)",z=d3.interpolateString(x,y),A=n.style("opacity")<.1;l?n.transition().delay(m).duration(0).style("opacity",0):n.interrupt().transition().duration(A?0:s).styleTween("transform",function(){return z},"important").style("-webkit-transform",y).style("opacity",1)})};return d.nvPointerEventsClass=w,d.options=a.utils.optionsFunc.bind(d),d._options=Object.create({},{duration:{get:function(){return s},set:function(a){s=a}},gravity:{get:function(){return f},set:function(a){f=a}},distance:{get:function(){return g},set:function(a){g=a}},snapDistance:{get:function(){return h},set:function(a){h=a}},classes:{get:function(){return j},set:function(a){j=a}},chartContainer:{get:function(){return k},set:function(a){k=a}},fixedTop:{get:function(){return i},set:function(a){i=a}},enabled:{get:function(){return r},set:function(a){r=a}},hideDelay:{get:function(){return m},set:function(a){m=a}},contentGenerator:{get:function(){return A},set:function(a){A=a}},valueFormatter:{get:function(){return x},set:function(a){x=a}},headerFormatter:{get:function(){return y},set:function(a){y=a}},keyFormatter:{get:function(){return z},set:function(a){z=a}},headerEnabled:{get:function(){return t},set:function(a){t=a}},_isInteractiveLayer:{get:function(){return u},set:function(a){u=!!a}},position:{get:function(){return p},set:function(a){p.left=void 0!==a.left?a.left:p.left,p.top=void 0!==a.top?a.top:p.top}},offset:{get:function(){return q},set:function(a){q.left=void 0!==a.left?a.left:q.left,q.top=void 0!==a.top?a.top:q.top}},hidden:{get:function(){return l},set:function(a){l!=a&&(l=!!a,d())}},data:{get:function(){return e},set:function(a){a.point&&(a.value=a.point.x,a.series=a.series||{},a.series.value=a.point.y,a.series.color=a.point.color||a.series.color),e=a}},tooltipElem:{get:function(){return o},set:function(){}},id:{get:function(){return v},set:function(){}}}),a.utils.initOptions(d),d}}(),a.utils.windowSize=function(){var a={width:640,height:480};return window.innerWidth&&window.innerHeight?(a.width=window.innerWidth,a.height=window.innerHeight,a):"CSS1Compat"==document.compatMode&&document.documentElement&&document.documentElement.offsetWidth?(a.width=document.documentElement.offsetWidth,a.height=document.documentElement.offsetHeight,a):document.body&&document.body.offsetWidth?(a.width=document.body.offsetWidth,a.height=document.body.offsetHeight,a):a},a.utils.windowResize=function(b){return window.addEventListener?window.addEventListener("resize",b):a.log("ERROR: Failed to bind to window.resize with: ",b),{callback:b,clear:function(){window.removeEventListener("resize",b)}}},a.utils.getColor=function(b){if(void 0===b)return a.utils.defaultColor();if(Array.isArray(b)){var c=d3.scale.ordinal().range(b);return function(a,b){var d=void 0===b?a:b;return a.color||c(d)}}return b},a.utils.defaultColor=function(){return a.utils.getColor(d3.scale.category20().range())},a.utils.customTheme=function(a,b,c){b=b||function(a){return a.key},c=c||d3.scale.category20().range();var d=c.length;return function(e){var f=b(e);return"function"==typeof a[f]?a[f]():void 0!==a[f]?a[f]:(d||(d=c.length),d-=1,c[d])}},a.utils.pjax=function(b,c){var d=function(d){d3.html(d,function(d){var e=d3.select(c).node();e.parentNode.replaceChild(d3.select(d).select(c).node(),e),a.utils.pjax(b,c)})};d3.selectAll(b).on("click",function(){history.pushState(this.href,this.textContent,this.href),d(this.href),d3.event.preventDefault()}),d3.select(window).on("popstate",function(){d3.event.state&&d(d3.event.state)})},a.utils.calcApproxTextWidth=function(a){if("function"==typeof a.style&&"function"==typeof a.text){var b=parseInt(a.style("font-size").replace("px","carview.php?tsp="),10),c=a.text().length;return c*b*.5}return 0},a.utils.NaNtoZero=function(a){return"number"!=typeof a||isNaN(a)||null===a||1/0===a||a===-1/0?0:a},d3.selection.prototype.watchTransition=function(a){var b=[this].concat([].slice.call(arguments,1));return a.transition.apply(a,b)},a.utils.renderWatch=function(b,c){if(!(this instanceof a.utils.renderWatch))return new a.utils.renderWatch(b,c);var d=void 0!==c?c:250,e=[],f=this;this.models=function(a){return a=[].slice.call(arguments,0),a.forEach(function(a){a.__rendered=!1,function(a){a.dispatch.on("renderEnd",function(){a.__rendered=!0,f.renderEnd("model")})}(a),e.indexOf(a)<0&&e.push(a)}),this},this.reset=function(a){void 0!==a&&(d=a),e=[]},this.transition=function(a,b,c){if(b=arguments.length>1?[].slice.call(arguments,1):[],c=b.length>1?b.pop():void 0!==d?d:250,a.__rendered=!1,e.indexOf(a)<0&&e.push(a),0===c)return a.__rendered=!0,a.delay=function(){return this},a.duration=function(){return this},a;a.__rendered=0===a.length?!0:a.every(function(a){return!a.length})?!0:!1;var g=0;return a.transition().duration(c).each(function(){++g}).each("end",function(){0===--g&&(a.__rendered=!0,f.renderEnd.apply(this,b))})},this.renderEnd=function(){e.every(function(a){return a.__rendered})&&(e.forEach(function(a){a.__rendered=!1}),b.renderEnd.apply(this,arguments))}},a.utils.deepExtend=function(b){var c=arguments.length>1?[].slice.call(arguments,1):[];c.forEach(function(c){for(var d in c){var e=b[d]instanceof Array,f="object"==typeof b[d],g="object"==typeof c[d];f&&!e&&g?a.utils.deepExtend(b[d],c[d]):b[d]=c[d]}})},a.utils.state=function(){if(!(this instanceof a.utils.state))return new a.utils.state;var b={},c=function(){},d=function(){return{}},e=null,f=null;this.dispatch=d3.dispatch("change","set"),this.dispatch.on("set",function(a){c(a,!0)}),this.getter=function(a){return d=a,this},this.setter=function(a,b){return b||(b=function(){}),c=function(c,d){a(c),d&&b()},this},this.init=function(b){e=e||{},a.utils.deepExtend(e,b)};var g=function(){var a=d();if(JSON.stringify(a)===JSON.stringify(b))return!1;for(var c in a)void 0===b[c]&&(b[c]={}),b[c]=a[c],f=!0;return!0};this.update=function(){e&&(c(e,!1),e=null),g.call(this)&&this.dispatch.change(b)}},a.utils.optionsFunc=function(a){return a&&d3.map(a).forEach(function(a,b){"function"==typeof this[a]&&this[a](b)}.bind(this)),this},a.utils.calcTicksX=function(b,c){var d=1,e=0;for(e;ed?f:d}return a.log("Requested number of ticks: ",b),a.log("Calculated max values to be: ",d),b=b>d?b=d-1:b,b=1>b?1:b,b=Math.floor(b),a.log("Calculating tick count as: ",b),b},a.utils.calcTicksY=function(b,c){return a.utils.calcTicksX(b,c)},a.utils.initOption=function(a,b){a._calls&&a._calls[b]?a[b]=a._calls[b]:(a[b]=function(c){return arguments.length?(a._overrides[b]=!0,a._options[b]=c,a):a._options[b]},a["_"+b]=function(c){return arguments.length?(a._overrides[b]||(a._options[b]=c),a):a._options[b]})},a.utils.initOptions=function(b){b._overrides=b._overrides||{};var c=Object.getOwnPropertyNames(b._options||{}),d=Object.getOwnPropertyNames(b._calls||{});c=c.concat(d);for(var e in c)a.utils.initOption(b,c[e])},a.utils.inheritOptionsD3=function(a,b,c){a._d3options=c.concat(a._d3options||[]),c.unshift(b),c.unshift(a),d3.rebind.apply(this,c)},a.utils.arrayUnique=function(a){return a.sort().filter(function(b,c){return!c||b!=a[c-1]})},a.utils.symbolMap=d3.map(),a.utils.symbol=function(){function b(b,e){var f=c.call(this,b,e),g=d.call(this,b,e);return-1!==d3.svg.symbolTypes.indexOf(f)?d3.svg.symbol().type(f).size(g)():a.utils.symbolMap.get(f)(g)}var c,d=64;return b.type=function(a){return arguments.length?(c=d3.functor(a),b):c},b.size=function(a){return arguments.length?(d=d3.functor(a),b):d},b},a.utils.inheritOptions=function(b,c){var d=Object.getOwnPropertyNames(c._options||{}),e=Object.getOwnPropertyNames(c._calls||{}),f=c._inherited||[],g=c._d3options||[],h=d.concat(e).concat(f).concat(g);h.unshift(c),h.unshift(b),d3.rebind.apply(this,h),b._inherited=a.utils.arrayUnique(d.concat(e).concat(f).concat(d).concat(b._inherited||[])),b._d3options=a.utils.arrayUnique(g.concat(b._d3options||[]))},a.utils.initSVG=function(a){a.classed({"nvd3-svg":!0})},a.utils.sanitizeHeight=function(a,b){return a||parseInt(b.style("height"),10)||400},a.utils.sanitizeWidth=function(a,b){return a||parseInt(b.style("width"),10)||960},a.utils.availableHeight=function(b,c,d){return a.utils.sanitizeHeight(b,c)-d.top-d.bottom},a.utils.availableWidth=function(b,c,d){return a.utils.sanitizeWidth(b,c)-d.left-d.right},a.utils.noData=function(b,c){var d=b.options(),e=d.margin(),f=d.noData(),g=null==f?["No Data Available."]:[f],h=a.utils.availableHeight(d.height(),c,e),i=a.utils.availableWidth(d.width(),c,e),j=e.left+i/2,k=e.top+h/2;c.selectAll("g").remove();var l=c.selectAll(".nv-noData").data(g);l.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),l.attr("x",j).attr("y",k).text(function(a){return a})},a.models.axis=function(){"use strict";function b(g){return s.reset(),g.each(function(b){var g=d3.select(this);a.utils.initSVG(g);var p=g.selectAll("g.nv-wrap.nv-axis").data([b]),q=p.enter().append("g").attr("class","nvd3 nv-wrap nv-axis"),t=(q.append("g"),p.select("g"));null!==n?c.ticks(n):("top"==c.orient()||"bottom"==c.orient())&&c.ticks(Math.abs(d.range()[1]-d.range()[0])/100),t.watchTransition(s,"axis").call(c),r=r||c.scale();var u=c.tickFormat();null==u&&(u=r.tickFormat());var v=t.selectAll("text.nv-axislabel").data([h||null]);v.exit().remove();var w,x,y;switch(c.orient()){case"top":v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",0).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b))+",0)"}).select("text").attr("dy","-0.5em").attr("y",-c.tickPadding()).attr("text-anchor","middle").text(function(a){var b=u(a);return("carview.php?tsp="+b).match("NaN")?"carview.php?tsp=":b}),x.watchTransition(s,"min-max top").attr("transform",function(b,c){return"translate("+a.utils.NaNtoZero(d.range()[c])+",0)"}));break;case"bottom":w=o+36;var z=30,A=0,B=t.selectAll("g").select("text"),C="carview.php?tsp=";if(j%360){B.each(function(){var a=this.getBoundingClientRect(),b=a.width;A=a.height,b>z&&(z=b)}),C="rotate("+j+" 0,"+(A/2+c.tickPadding())+")";var D=Math.abs(Math.sin(j*Math.PI/180));w=(D?D*z:z)+30,B.attr("transform",C).style("text-anchor",j%360>0?"start":"end")}v.enter().append("text").attr("class","nv-axislabel"),y=d.range().length<2?0:2===d.range().length?d.range()[1]:d.range()[d.range().length-1]+(d.range()[1]-d.range()[0]),v.attr("text-anchor","middle").attr("y",w).attr("x",y/2),i&&(x=p.selectAll("g.nv-axisMaxMin").data([d.domain()[0],d.domain()[d.domain().length-1]]),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-x",0==b?"nv-axisMin-x":"nv-axisMax-x"].join(" ")}).append("text"),x.exit().remove(),x.attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"}).select("text").attr("dy",".71em").attr("y",c.tickPadding()).attr("transform",C).style("text-anchor",j?j%360>0?"start":"end":"middle").text(function(a){var b=u(a);return("carview.php?tsp="+b).match("NaN")?"carview.php?tsp=":b}),x.watchTransition(s,"min-max bottom").attr("transform",function(b){return"translate("+a.utils.NaNtoZero(d(b)+(m?d.rangeBand()/2:0))+",0)"})),l&&B.attr("transform",function(a,b){return"translate(0,"+(b%2==0?"0":"12")+")"});break;case"right":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"begin").attr("transform",k?"rotate(90)":"carview.php?tsp=").attr("y",k?-Math.max(e.right,f)+12:-10).attr("x",k?d3.max(d.range())/2:c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b){return"translate(0,"+a.utils.NaNtoZero(d(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",c.tickPadding()).style("text-anchor","start").text(function(a){var b=u(a);return("carview.php?tsp="+b).match("NaN")?"carview.php?tsp=":b}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1));break;case"left":v.enter().append("text").attr("class","nv-axislabel"),v.style("text-anchor",k?"middle":"end").attr("transform",k?"rotate(-90)":"carview.php?tsp=").attr("y",k?-Math.max(e.left,f)+25-(o||0):-10).attr("x",k?-d3.max(d.range())/2:-c.tickPadding()),i&&(x=p.selectAll("g.nv-axisMaxMin").data(d.domain()),x.enter().append("g").attr("class",function(a,b){return["nv-axisMaxMin","nv-axisMaxMin-y",0==b?"nv-axisMin-y":"nv-axisMax-y"].join(" ")}).append("text").style("opacity",0),x.exit().remove(),x.attr("transform",function(b){return"translate(0,"+a.utils.NaNtoZero(r(b))+")"}).select("text").attr("dy",".32em").attr("y",0).attr("x",-c.tickPadding()).attr("text-anchor","end").text(function(a){var b=u(a);return("carview.php?tsp="+b).match("NaN")?"carview.php?tsp=":b}),x.watchTransition(s,"min-max right").attr("transform",function(b,c){return"translate(0,"+a.utils.NaNtoZero(d.range()[c])+")"}).select("text").style("opacity",1))}if(v.text(function(a){return a}),!i||"left"!==c.orient()&&"right"!==c.orient()||(t.selectAll("g").each(function(a){d3.select(this).select("text").attr("opacity",1),(d(a)d.range()[0]-10)&&((a>1e-10||-1e-10>a)&&d3.select(this).attr("opacity",0),d3.select(this).select("text").attr("opacity",0))}),d.domain()[0]==d.domain()[1]&&0==d.domain()[0]&&p.selectAll("g.nv-axisMaxMin").style("opacity",function(a,b){return b?0:1})),i&&("top"===c.orient()||"bottom"===c.orient())){var E=[];p.selectAll("g.nv-axisMaxMin").each(function(a,b){try{E.push(b?d(a)-this.getBoundingClientRect().width-4:d(a)+this.getBoundingClientRect().width+4)}catch(c){E.push(b?d(a)-4:d(a)+4)}}),t.selectAll("g").each(function(a){(d(a)E[1])&&(a>1e-10||-1e-10>a?d3.select(this).remove():d3.select(this).select("text").remove())})}t.selectAll(".tick").filter(function(a){return!parseFloat(Math.round(1e5*a)/1e6)&&void 0!==a}).classed("zero",!0),r=d.copy()}),s.renderEnd("axis immediate"),b}var c=d3.svg.axis(),d=d3.scale.linear(),e={top:0,right:0,bottom:0,left:0},f=75,g=60,h=null,i=!0,j=0,k=!0,l=!1,m=!1,n=null,o=0,p=250,q=d3.dispatch("renderEnd");c.scale(d).orient("bottom").tickFormat(function(a){return a});var r,s=a.utils.renderWatch(q,p);return b.axis=c,b.dispatch=q,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{axisLabelDistance:{get:function(){return o},set:function(a){o=a}},staggerLabels:{get:function(){return l},set:function(a){l=a}},rotateLabels:{get:function(){return j},set:function(a){j=a}},rotateYLabel:{get:function(){return k},set:function(a){k=a}},showMaxMin:{get:function(){return i},set:function(a){i=a}},axisLabel:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return g},set:function(a){g=a}},ticks:{get:function(){return n},set:function(a){n=a}},width:{get:function(){return f},set:function(a){f=a}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},duration:{get:function(){return p},set:function(a){p=a,s.reset(p)}},scale:{get:function(){return d},set:function(e){d=e,c.scale(d),m="function"==typeof d.rangeBands,a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"])}}}),a.utils.initOptions(b),a.utils.inheritOptionsD3(b,c,["orient","tickValues","tickSubdivide","tickSize","tickPadding","tickFormat"]),a.utils.inheritOptionsD3(b,d,["domain","range","rangeBand","rangeBands"]),b},a.models.boxPlot=function(){"use strict";function b(l){return v.reset(),l.each(function(b){var l=j-i.left-i.right,p=k-i.top-i.bottom;r=d3.select(this),a.utils.initSVG(r),m.domain(c||b.map(function(a,b){return o(a,b)})).rangeBands(e||[0,l],.1);var w=[];if(!d){var x=d3.min(b.map(function(a){var b=[];return b.push(a.values.Q1),a.values.hasOwnProperty("whisker_low")&&null!==a.values.whisker_low&&b.push(a.values.whisker_low),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.min(b)})),y=d3.max(b.map(function(a){var b=[];return b.push(a.values.Q3),a.values.hasOwnProperty("whisker_high")&&null!==a.values.whisker_high&&b.push(a.values.whisker_high),a.values.hasOwnProperty("outliers")&&null!==a.values.outliers&&(b=b.concat(a.values.outliers)),d3.max(b)}));w=[x,y]}n.domain(d||w),n.range(f||[p,0]),g=g||m,h=h||n.copy().range([n(0),n(0)]);{var z=r.selectAll("g.nv-wrap").data([b]);z.enter().append("g").attr("class","nvd3 nv-wrap")}z.attr("transform","translate("+i.left+","+i.top+")");var A=z.selectAll(".nv-boxplot").data(function(a){return a}),B=A.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);A.attr("class","nv-boxplot").attr("transform",function(a,b){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}).classed("hover",function(a){return a.hover}),A.watchTransition(v,"nv-boxplot: boxplots").style("stroke-opacity",1).style("fill-opacity",.75).delay(function(a,c){return c*t/b.length}).attr("transform",function(a,b){return"translate("+(m(o(a,b))+.05*m.rangeBand())+", 0)"}),A.exit().remove(),B.each(function(a,b){var c=d3.select(this);["low","high"].forEach(function(d){a.values.hasOwnProperty("whisker_"+d)&&null!==a.values["whisker_"+d]&&(c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-whisker nv-boxplot-"+d),c.append("line").style("stroke",a.color?a.color:q(a,b)).attr("class","nv-boxplot-tick nv-boxplot-"+d))})});var C=A.selectAll(".nv-boxplot-outlier").data(function(a){return a.values.hasOwnProperty("outliers")&&null!==a.values.outliers?a.values.outliers:[]});C.enter().append("circle").style("fill",function(a,b,c){return q(a,c)}).style("stroke",function(a,b,c){return q(a,c)}).on("mouseover",function(a,b,c){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:a,color:q(a,c)},e:d3.event})}).on("mouseout",function(a,b,c){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:a,color:q(a,c)},e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})}),C.attr("class","nv-boxplot-outlier"),C.watchTransition(v,"nv-boxplot: nv-boxplot-outlier").attr("cx",.45*m.rangeBand()).attr("cy",function(a){return n(a)}).attr("r","3"),C.exit().remove();var D=function(){return null===u?.9*m.rangeBand():Math.min(75,.9*m.rangeBand())},E=function(){return.45*m.rangeBand()-D()/2},F=function(){return.45*m.rangeBand()+D()/2};["low","high"].forEach(function(a){var b="low"===a?"Q1":"Q3";A.select("line.nv-boxplot-whisker.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",.45*m.rangeBand()).attr("y1",function(b){return n(b.values["whisker_"+a])}).attr("x2",.45*m.rangeBand()).attr("y2",function(a){return n(a.values[b])}),A.select("line.nv-boxplot-tick.nv-boxplot-"+a).watchTransition(v,"nv-boxplot: boxplots").attr("x1",E).attr("y1",function(b){return n(b.values["whisker_"+a])}).attr("x2",F).attr("y2",function(b){return n(b.values["whisker_"+a])})}),["low","high"].forEach(function(a){B.selectAll(".nv-boxplot-"+a).on("mouseover",function(b,c,d){d3.select(this).classed("hover",!0),s.elementMouseover({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mouseout",function(b,c,d){d3.select(this).classed("hover",!1),s.elementMouseout({series:{key:b.values["whisker_"+a],color:q(b,d)},e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})})}),B.append("rect").attr("class","nv-boxplot-box").on("mouseover",function(a,b){d3.select(this).classed("hover",!0),s.elementMouseover({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),s.elementMouseout({key:a.label,value:a.label,series:[{key:"Q3",value:a.values.Q3,color:a.color||q(a,b)},{key:"Q2",value:a.values.Q2,color:a.color||q(a,b)},{key:"Q1",value:a.values.Q1,color:a.color||q(a,b)}],data:a,index:b,e:d3.event})}).on("mousemove",function(){s.elementMousemove({e:d3.event})}),A.select("rect.nv-boxplot-box").watchTransition(v,"nv-boxplot: boxes").attr("y",function(a){return n(a.values.Q3)}).attr("width",D).attr("x",E).attr("height",function(a){return Math.abs(n(a.values.Q3)-n(a.values.Q1))||1}).style("fill",function(a,b){return a.color||q(a,b)}).style("stroke",function(a,b){return a.color||q(a,b)}),B.append("line").attr("class","nv-boxplot-median"),A.select("line.nv-boxplot-median").watchTransition(v,"nv-boxplot: boxplots line").attr("x1",E).attr("y1",function(a){return n(a.values.Q2)}).attr("x2",F).attr("y2",function(a){return n(a.values.Q2)}),g=m.copy(),h=n.copy()}),v.renderEnd("nv-boxplot immediate"),b}var c,d,e,f,g,h,i={top:0,right:0,bottom:0,left:0},j=960,k=500,l=Math.floor(1e4*Math.random()),m=d3.scale.ordinal(),n=d3.scale.linear(),o=function(a){return a.x},p=function(a){return a.y},q=a.utils.defaultColor(),r=null,s=d3.dispatch("elementMouseover","elementMouseout","elementMousemove","renderEnd"),t=250,u=null,v=a.utils.renderWatch(s,t);return b.dispatch=s,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},maxBoxWidth:{get:function(){return u},set:function(a){u=a}},x:{get:function(){return o},set:function(a){o=a}},y:{get:function(){return p},set:function(a){p=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return l},set:function(a){l=a}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}},duration:{get:function(){return t},set:function(a){t=a,v.reset(t)}}}),a.utils.initOptions(b),b},a.models.boxPlotChart=function(){"use strict";function b(k){return t.reset(),t.models(e),l&&t.models(f),m&&t.models(g),k.each(function(k){var p=d3.select(this);a.utils.initSVG(p);var t=(i||parseInt(p.style("width"))||960)-h.left-h.right,u=(j||parseInt(p.style("height"))||400)-h.top-h.bottom;if(b.update=function(){r.beforeUpdate(),p.transition().duration(s).call(b)},b.container=this,!(k&&k.length&&k.filter(function(a){return a.values.hasOwnProperty("Q1")&&a.values.hasOwnProperty("Q2")&&a.values.hasOwnProperty("Q3")}).length)){var v=p.selectAll(".nv-noData").data([q]);return v.enter().append("text").attr("class","nvd3 nv-noData").attr("dy","-.7em").style("text-anchor","middle"),v.attr("x",h.left+t/2).attr("y",h.top+u/2).text(function(a){return a}),b}p.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale().clamp(!0);var w=p.selectAll("g.nv-wrap.nv-boxPlotWithAxes").data([k]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-boxPlotWithAxes").append("g"),y=x.append("defs"),z=w.select("g"); x.append("g").attr("class","nv-x nv-axis"),x.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),x.append("g").attr("class","nv-barsWrap"),z.attr("transform","translate("+h.left+","+h.top+")"),n&&z.select(".nv-y.nv-axis").attr("transform","translate("+t+",0)"),e.width(t).height(u);var A=z.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));if(A.transition().call(e),y.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"),z.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(o?2:1)).attr("height",16).attr("x",-c.rangeBand()/(o?1:2)),l){f.scale(c).ticks(a.utils.calcTicksX(t/100,k)).tickSize(-u,0),z.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),z.select(".nv-x.nv-axis").call(f);var B=z.select(".nv-x.nv-axis").selectAll("g");o&&B.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}m&&(g.scale(d).ticks(Math.floor(u/36)).tickSize(-t,0),z.select(".nv-y.nv-axis").call(g)),z.select(".nv-zeroLine line").attr("x1",0).attr("x2",t).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("nv-boxplot chart immediate"),b}var c,d,e=a.models.boxPlot(),f=a.models.axis(),g=a.models.axis(),h={top:15,right:10,bottom:50,left:60},i=null,j=null,k=a.utils.getColor(),l=!0,m=!0,n=!1,o=!1,p=a.models.tooltip(),q="No Data Available.",r=d3.dispatch("tooltipShow","tooltipHide","beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(n?"right":"left").tickFormat(d3.format(",.1f")),p.duration(0);var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){p.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(a){p.data(a).hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){p.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.boxplot=e,b.xAxis=f,b.yAxis=g,b.tooltip=p,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},staggerLabels:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return l},set:function(a){l=a}},showYAxis:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return tooltips},set:function(a){tooltips=a}},tooltipContent:{get:function(){return p},set:function(a){p=a}},noData:{get:function(){return q},set:function(a){q=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!==a.top?a.top:h.top,h.right=void 0!==a.right?a.right:h.right,h.bottom=void 0!==a.bottom?a.bottom:h.bottom,h.left=void 0!==a.left?a.left:h.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}},rightAlignYAxis:{get:function(){return n},set:function(a){n=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.bullet=function(){"use strict";function b(d){return d.each(function(b,d){var p=m-c.left-c.right,s=n-c.top-c.bottom;o=d3.select(this),a.utils.initSVG(o);{var t=f.call(this,b,d).slice().sort(d3.descending),u=g.call(this,b,d).slice().sort(d3.descending),v=h.call(this,b,d).slice().sort(d3.descending),w=i.call(this,b,d).slice(),x=j.call(this,b,d).slice(),y=k.call(this,b,d).slice(),z=d3.scale.linear().domain(d3.extent(d3.merge([l,t]))).range(e?[p,0]:[0,p]);this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range())}this.__chart__=z;var A=d3.min(t),B=d3.max(t),C=t[1],D=o.selectAll("g.nv-wrap.nv-bullet").data([b]),E=D.enter().append("g").attr("class","nvd3 nv-wrap nv-bullet"),F=E.append("g"),G=D.select("g");F.append("rect").attr("class","nv-range nv-rangeMax"),F.append("rect").attr("class","nv-range nv-rangeAvg"),F.append("rect").attr("class","nv-range nv-rangeMin"),F.append("rect").attr("class","nv-measure"),D.attr("transform","translate("+c.left+","+c.top+")");var H=function(a){return Math.abs(z(a)-z(0))},I=function(a){return z(0>a?a:0)};G.select("rect.nv-rangeMax").attr("height",s).attr("width",H(B>0?B:A)).attr("x",I(B>0?B:A)).datum(B>0?B:A),G.select("rect.nv-rangeAvg").attr("height",s).attr("width",H(C)).attr("x",I(C)).datum(C),G.select("rect.nv-rangeMin").attr("height",s).attr("width",H(B)).attr("x",I(B)).attr("width",H(B>0?A:B)).attr("x",I(B>0?A:B)).datum(B>0?A:B),G.select("rect.nv-measure").style("fill",q).attr("height",s/3).attr("y",s/3).attr("width",0>v?z(0)-z(v[0]):z(v[0])-z(0)).attr("x",I(v)).on("mouseover",function(){r.elementMouseover({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})}).on("mouseout",function(){r.elementMouseout({value:v[0],label:y[0]||"Current",color:d3.select(this).style("fill")})});var J=s/6,K=u.map(function(a,b){return{value:a,label:x[b]}});F.selectAll("path.nv-markerTriangle").data(K).enter().append("path").attr("class","nv-markerTriangle").attr("transform",function(a){return"translate("+z(a.value)+","+s/2+")"}).attr("d","M0,"+J+"L"+J+","+-J+" "+-J+","+-J+"Z").on("mouseover",function(a){r.elementMouseover({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill"),pos:[z(a.value),s/2]})}).on("mousemove",function(a){r.elementMousemove({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a){r.elementMouseout({value:a.value,label:a.label||"Previous",color:d3.select(this).style("fill")})}),D.selectAll(".nv-range").on("mouseover",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseover({value:a,label:c,color:d3.select(this).style("fill")})}).on("mousemove",function(){r.elementMousemove({value:v[0],label:y[0]||"Previous",color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){var c=w[b]||(b?1==b?"Mean":"Minimum":"Maximum");r.elementMouseout({value:a,label:c,color:d3.select(this).style("fill")})})}),b}var c={top:0,right:0,bottom:0,left:0},d="left",e=!1,f=function(a){return a.ranges},g=function(a){return a.markers?a.markers:[0]},h=function(a){return a.measures},i=function(a){return a.rangeLabels?a.rangeLabels:[]},j=function(a){return a.markerLabels?a.markerLabels:[]},k=function(a){return a.measureLabels?a.measureLabels:[]},l=[0],m=380,n=30,o=null,p=null,q=a.utils.getColor(["#1f77b4"]),r=d3.dispatch("elementMouseover","elementMouseout","elementMousemove");return b.dispatch=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return f},set:function(a){f=a}},markers:{get:function(){return g},set:function(a){g=a}},measures:{get:function(){return h},set:function(a){h=a}},forceX:{get:function(){return l},set:function(a){l=a}},width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},tickFormat:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},orient:{get:function(){return d},set:function(a){d=a,e="right"==d||"bottom"==d}},color:{get:function(){return q},set:function(b){q=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.bulletChart=function(){"use strict";function b(d){return d.each(function(e,o){var p=d3.select(this);a.utils.initSVG(p);var q=a.utils.availableWidth(k,p,g),r=l-g.top-g.bottom;if(b.update=function(){b(d)},b.container=this,!e||!h.call(this,e,o))return a.utils.noData(b,p),b;p.selectAll(".nv-noData").remove();var s=h.call(this,e,o).slice().sort(d3.descending),t=i.call(this,e,o).slice().sort(d3.descending),u=j.call(this,e,o).slice().sort(d3.descending),v=p.selectAll("g.nv-wrap.nv-bulletChart").data([e]),w=v.enter().append("g").attr("class","nvd3 nv-wrap nv-bulletChart"),x=w.append("g"),y=v.select("g");x.append("g").attr("class","nv-bulletWrap"),x.append("g").attr("class","nv-titles"),v.attr("transform","translate("+g.left+","+g.top+")");var z=d3.scale.linear().domain([0,Math.max(s[0],t[0],u[0])]).range(f?[q,0]:[0,q]),A=this.__chart__||d3.scale.linear().domain([0,1/0]).range(z.range());this.__chart__=z;var B=x.select(".nv-titles").append("g").attr("text-anchor","end").attr("transform","translate(-6,"+(l-g.top-g.bottom)/2+")");B.append("text").attr("class","nv-title").text(function(a){return a.title}),B.append("text").attr("class","nv-subtitle").attr("dy","1em").text(function(a){return a.subtitle}),c.width(q).height(r);var C=y.select(".nv-bulletWrap");d3.transition(C).call(c);var D=m||z.tickFormat(q/100),E=y.selectAll("g.nv-tick").data(z.ticks(n?n:q/50),function(a){return this.textContent||D(a)}),F=E.enter().append("g").attr("class","nv-tick").attr("transform",function(a){return"translate("+A(a)+",0)"}).style("opacity",1e-6);F.append("line").attr("y1",r).attr("y2",7*r/6),F.append("text").attr("text-anchor","middle").attr("dy","1em").attr("y",7*r/6).text(D);var G=d3.transition(E).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1);G.select("line").attr("y1",r).attr("y2",7*r/6),G.select("text").attr("y",7*r/6),d3.transition(E.exit()).attr("transform",function(a){return"translate("+z(a)+",0)"}).style("opacity",1e-6).remove()}),d3.timer.flush(),b}var c=a.models.bullet(),d=a.models.tooltip(),e="left",f=!1,g={top:5,right:40,bottom:20,left:120},h=function(a){return a.ranges},i=function(a){return a.markers?a.markers:[0]},j=function(a){return a.measures},k=null,l=55,m=null,n=null,o=null,p=d3.dispatch("tooltipShow","tooltipHide");return d.duration(0).headerEnabled(!1),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.label,value:a.value,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.bullet=c,b.dispatch=p,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{ranges:{get:function(){return h},set:function(a){h=a}},markers:{get:function(){return i},set:function(a){i=a}},measures:{get:function(){return j},set:function(a){j=a}},width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},tickFormat:{get:function(){return m},set:function(a){m=a}},ticks:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return o},set:function(a){o=a}},tooltips:{get:function(){return d.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),d.enabled(!!b)}},tooltipContent:{get:function(){return d.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),d.contentGenerator(b)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},orient:{get:function(){return e},set:function(a){e=a,f="right"==e||"bottom"==e}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.candlestickBar=function(){"use strict";function b(x){return x.each(function(b){c=d3.select(this);var x=a.utils.availableWidth(i,c,h),y=a.utils.availableHeight(j,c,h);a.utils.initSVG(c);var A=x/b[0].values.length*.45;l.domain(d||d3.extent(b[0].values.map(n).concat(t))),l.range(v?f||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]:f||[5+A/2,x-A/2-5]),m.domain(e||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(g||[y,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var B=d3.select(this).selectAll("g.nv-wrap.nv-candlestickBar").data([b[0].values]),C=B.enter().append("g").attr("class","nvd3 nv-wrap nv-candlestickBar"),D=C.append("defs"),E=C.append("g"),F=B.select("g");E.append("g").attr("class","nv-ticks"),B.attr("transform","translate("+h.left+","+h.top+")"),c.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:k})}),D.append("clipPath").attr("id","nv-chart-clip-path-"+k).append("rect"),B.select("#nv-chart-clip-path-"+k+" rect").attr("width",x).attr("height",y),F.attr("clip-path",w?"url(#nv-chart-clip-path-"+k+")":"carview.php?tsp=");var G=B.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});G.exit().remove();{var H=G.enter().append("g").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b});H.append("line").attr("class","nv-candlestick-lines").attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),H.append("rect").attr("class","nv-candlestick-rects nv-bars").attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)})}c.selectAll(".nv-candlestick-lines").transition().attr("transform",function(a,b){return"translate("+l(n(a,b))+",0)"}).attr("x1",0).attr("y1",function(a,b){return m(r(a,b))}).attr("x2",0).attr("y2",function(a,b){return m(s(a,b))}),c.selectAll(".nv-candlestick-rects").transition().attr("transform",function(a,b){return"translate("+(l(n(a,b))-A/2)+","+(m(o(a,b))-(p(a,b)>q(a,b)?m(q(a,b))-m(p(a,b)):0))+")"}).attr("x",0).attr("y",0).attr("width",A).attr("height",function(a,b){var c=p(a,b),d=q(a,b);return c>d?m(d)-m(c):m(c)-m(d)})}),b}var c,d,e,f,g,h={top:0,right:0,bottom:0,left:0},i=null,j=null,k=Math.floor(1e4*Math.random()),l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,d){b.clearHighlights(),c.select(".nv-candlestickBar .nv-tick-0-"+a).classed("hover",d)},b.clearHighlights=function(){c.select(".nv-candlestickBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return i},set:function(a){i=a}},height:{get:function(){return j},set:function(a){j=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return k},set:function(a){k=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return h},set:function(a){h.top=void 0!=a.top?a.top:h.top,h.right=void 0!=a.right?a.right:h.right,h.bottom=void 0!=a.bottom?a.bottom:h.bottom,h.left=void 0!=a.left?a.left:h.left}},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.cumulativeLineChart=function(){"use strict";function b(l){return H.reset(),H.models(f),r&&H.models(g),s&&H.models(h),l.each(function(l){function A(){d3.select(b.container).style("cursor","ew-resize")}function E(){G.x=d3.event.x,G.i=Math.round(F.invert(G.x)),K()}function H(){d3.select(b.container).style("cursor","auto"),y.index=G.i,C.stateChange(y)}function K(){bb.data([G]);var a=b.duration();b.duration(0),b.update(),b.duration(a)}var L=d3.select(this);a.utils.initSVG(L),L.classed("nv-chart-"+x,!0);var M=this,N=a.utils.availableWidth(o,L,m),O=a.utils.availableHeight(p,L,m);if(b.update=function(){0===D?L.call(b):L.transition().duration(D).call(b)},b.container=this,y.setter(J(l),b.update).getter(I(l)).update(),y.disabled=l.map(function(a){return!!a.disabled}),!z){var P;z={};for(P in y)z[P]=y[P]instanceof Array?y[P].slice(0):y[P]}var Q=d3.behavior.drag().on("dragstart",A).on("drag",E).on("dragend",H);if(!(l&&l.length&&l.filter(function(a){return a.values.length}).length))return a.utils.noData(b,L),b;if(L.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale(),w)f.yDomain(null);else{var R=l.filter(function(a){return!a.disabled}).map(function(a){var b=d3.extent(a.values,f.y());return b[0]<-.95&&(b[0]=-.95),[(b[0]-b[1])/(1+b[1]),(b[1]-b[0])/(1+b[0])]}),S=[d3.min(R,function(a){return a[0]}),d3.max(R,function(a){return a[1]})];f.yDomain(S)}F.domain([0,l[0].values.length-1]).range([0,N]).clamp(!0);var l=c(G.i,l),T=v?"none":"all",U=L.selectAll("g.nv-wrap.nv-cumulativeLine").data([l]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-cumulativeLine").append("g"),W=U.select("g");if(V.append("g").attr("class","nv-interactive"),V.append("g").attr("class","nv-x nv-axis").style("pointer-events","none"),V.append("g").attr("class","nv-y nv-axis"),V.append("g").attr("class","nv-background"),V.append("g").attr("class","nv-linesWrap").style("pointer-events",T),V.append("g").attr("class","nv-avgLinesWrap").style("pointer-events","none"),V.append("g").attr("class","nv-legendWrap"),V.append("g").attr("class","nv-controlsWrap"),q&&(i.width(N),W.select(".nv-legendWrap").datum(l).call(i),m.top!=i.height()&&(m.top=i.height(),O=a.utils.availableHeight(p,L,m)),W.select(".nv-legendWrap").attr("transform","translate(0,"+-m.top+")")),u){var X=[{key:"Re-scale y-axis",disabled:!w}];j.width(140).color(["#444","#444","#444"]).rightAlign(!1).margin({top:5,right:0,bottom:5,left:20}),W.select(".nv-controlsWrap").datum(X).attr("transform","translate(0,"+-m.top+")").call(j)}U.attr("transform","translate("+m.left+","+m.top+")"),t&&W.select(".nv-y.nv-axis").attr("transform","translate("+N+",0)");var Y=l.filter(function(a){return a.tempDisabled});U.select(".tempDisabled").remove(),Y.length&&U.append("text").attr("class","tempDisabled").attr("x",N/2).attr("y","-.71em").style("text-anchor","end").text(Y.map(function(a){return a.key}).join(", ")+" values cannot be calculated for this time period."),v&&(k.width(N).height(O).margin({left:m.left,top:m.top}).svgContainer(L).xScale(d),U.select(".nv-interactive").call(k)),V.select(".nv-background").append("rect"),W.select(".nv-background rect").attr("width",N).attr("height",O),f.y(function(a){return a.display.y}).width(N).height(O).color(l.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!l[b].disabled&&!l[b].tempDisabled}));var Z=W.select(".nv-linesWrap").datum(l.filter(function(a){return!a.disabled&&!a.tempDisabled}));Z.call(f),l.forEach(function(a,b){a.seriesIndex=b});var $=l.filter(function(a){return!a.disabled&&!!B(a)}),_=W.select(".nv-avgLinesWrap").selectAll("line").data($,function(a){return a.key}),ab=function(a){var b=e(B(a));return 0>b?0:b>O?O:b};_.enter().append("line").style("stroke-width",2).style("stroke-dasharray","10,10").style("stroke",function(a){return f.color()(a,a.seriesIndex)}).attr("x1",0).attr("x2",N).attr("y1",ab).attr("y2",ab),_.style("stroke-opacity",function(a){var b=e(B(a));return 0>b||b>O?0:1}).attr("x1",0).attr("x2",N).attr("y1",ab).attr("y2",ab),_.exit().remove();var bb=Z.selectAll(".nv-indexLine").data([G]);bb.enter().append("rect").attr("class","nv-indexLine").attr("width",3).attr("x",-2).attr("fill","red").attr("fill-opacity",.5).style("pointer-events","all").call(Q),bb.attr("transform",function(a){return"translate("+F(a.i)+",0)"}).attr("height",O),r&&(g.scale(d)._ticks(a.utils.calcTicksX(N/70,l)).tickSize(-O,0),W.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),W.select(".nv-x.nv-axis").call(g)),s&&(h.scale(e)._ticks(a.utils.calcTicksY(O/36,l)).tickSize(-N,0),W.select(".nv-y.nv-axis").call(h)),W.select(".nv-background rect").on("click",function(){G.x=d3.mouse(this)[0],G.i=Math.round(F.invert(G.x)),y.index=G.i,C.stateChange(y),K()}),f.dispatch.on("elementClick",function(a){G.i=a.pointIndex,G.x=F(G.i),y.index=G.i,C.stateChange(y),K()}),j.dispatch.on("legendClick",function(a){a.disabled=!a.disabled,w=!a.disabled,y.rescaleY=w,C.stateChange(y),b.update()}),i.dispatch.on("stateChange",function(a){for(var c in a)y[c]=a[c];C.stateChange(y),b.update()}),k.dispatch.on("elementMousemove",function(c){f.clearHighlights();var d,e,i,j=[];if(l.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g,h){e=a.interactiveBisect(g.values,c.pointXValue,b.x()),f.highlightPoint(h,e,!0);var k=g.values[e];"undefined"!=typeof k&&("undefined"==typeof d&&(d=k),"undefined"==typeof i&&(i=b.xScale()(b.x()(k,e))),j.push({key:g.key,value:b.y()(k,e),color:n(g,g.seriesIndex)}))}),j.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(j.map(function(a){return a.value}),o,q);null!==r&&(j[r].highlight=!0)}var s=g.tickFormat()(b.x()(d,e),e);k.tooltip.position({left:i+m.left,top:c.mouseY+m.top}).chartContainer(M.parentNode).valueFormatter(function(a){return h.tickFormat()(a)}).data({value:s,series:j})(),k.renderGuideLine(i)}),k.dispatch.on("elementMouseout",function(){f.clearHighlights()}),C.on("changeState",function(a){"undefined"!=typeof a.disabled&&(l.forEach(function(b,c){b.disabled=a.disabled[c]}),y.disabled=a.disabled),"undefined"!=typeof a.index&&(G.i=a.index,G.x=F(G.i),y.index=a.index,bb.data([G])),"undefined"!=typeof a.rescaleY&&(w=a.rescaleY),b.update()})}),H.renderEnd("cumulativeLineChart immediate"),b}function c(a,b){return K||(K=f.y()),b.map(function(b){if(!b.values)return b;var c=b.values[a];if(null==c)return b;var d=K(c,a);return-.95>d&&!E?(b.tempDisabled=!0,b):(b.tempDisabled=!1,b.values=b.values.map(function(a,b){return a.display={y:(K(a,b)-d)/(1+d)},a}),b)})}var d,e,f=a.models.line(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.models.legend(),k=a.interactiveGuideline(),l=a.models.tooltip(),m={top:30,right:30,bottom:50,left:60},n=a.utils.defaultColor(),o=null,p=null,q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=!0,x=f.id(),y=a.utils.state(),z=null,A=null,B=function(a){return a.average},C=d3.dispatch("stateChange","changeState","renderEnd"),D=250,E=!1;y.index=0,y.rescaleY=w,g.orient("bottom").tickPadding(7),h.orient(t?"right":"left"),l.valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)}),j.updateState(!1);var F=d3.scale.linear(),G={i:0,x:0},H=a.utils.renderWatch(C,D),I=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),index:G.i,rescaleY:w}}},J=function(a){return function(b){void 0!==b.index&&(G.i=b.index),void 0!==b.rescaleY&&(w=b.rescaleY),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};f.dispatch.on("elementMouseover.tooltip",function(a){var c={x:b.x()(a.point),y:b.y()(a.point),color:a.point.color};a.point=c,l.data(a).position(a.pos).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(){l.hidden(!0)});var K=null;return b.dispatch=C,b.lines=f,b.legend=i,b.controls=j,b.xAxis=g,b.yAxis=h,b.interactiveLayer=k,b.state=y,b.tooltip=l,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return o},set:function(a){o=a}},height:{get:function(){return p},set:function(a){p=a}},rescaleY:{get:function(){return w},set:function(a){w=a}},showControls:{get:function(){return u},set:function(a){u=a}},showLegend:{get:function(){return q},set:function(a){q=a}},average:{get:function(){return B},set:function(a){B=a}},defaultState:{get:function(){return z},set:function(a){z=a}},noData:{get:function(){return A},set:function(a){A=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},noErrorCheck:{get:function(){return E},set:function(a){E=a}},tooltips:{get:function(){return l.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),l.enabled(!!b)}},tooltipContent:{get:function(){return l.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),l.contentGenerator(b)}},margin:{get:function(){return m},set:function(a){m.top=void 0!==a.top?a.top:m.top,m.right=void 0!==a.right?a.right:m.right,m.bottom=void 0!==a.bottom?a.bottom:m.bottom,m.left=void 0!==a.left?a.left:m.left}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),i.color(n)}},useInteractiveGuideline:{get:function(){return v},set:function(a){v=a,a===!0&&(b.interactive(!1),b.useVoronoi(!1))}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,h.orient(a?"right":"left")}},duration:{get:function(){return D},set:function(a){D=a,f.duration(D),g.duration(D),h.duration(D),H.reset(D)}}}),a.utils.inheritOptions(b,f),a.utils.initOptions(b),b},a.models.discreteBar=function(){"use strict";function b(m){return y.reset(),m.each(function(b){var m=k-j.left-j.right,x=l-j.top-j.bottom;c=d3.select(this),a.utils.initSVG(c),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var z=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),y0:a.y0}})});n.domain(d||d3.merge(z).map(function(a){return a.x})).rangeBands(f||[0,m],.1),o.domain(e||d3.extent(d3.merge(z).map(function(a){return a.y}).concat(r))),o.range(t?g||[x-(o.domain()[0]<0?12:0),o.domain()[1]>0?12:0]:g||[x,0]),h=h||n,i=i||o.copy().range([o(0),o(0)]);{var A=c.selectAll("g.nv-wrap.nv-discretebar").data([b]),B=A.enter().append("g").attr("class","nvd3 nv-wrap nv-discretebar"),C=B.append("g");A.select("g")}C.append("g").attr("class","nv-groups"),A.attr("transform","translate("+j.left+","+j.top+")");var D=A.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});D.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),D.exit().watchTransition(y,"discreteBar: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),D.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),D.watchTransition(y,"discreteBar: groups").style("stroke-opacity",1).style("fill-opacity",.75);var E=D.selectAll("g.nv-bar").data(function(a){return a.values});E.exit().remove();var F=E.enter().append("g").attr("transform",function(a,b){return"translate("+(n(p(a,b))+.05*n.rangeBand())+", "+o(0)+")"}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),v.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),v.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){v.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){v.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()});F.append("rect").attr("height",0).attr("width",.9*n.rangeBand()/b.length),t?(F.append("text").attr("text-anchor","middle"),E.select("text").text(function(a,b){return u(q(a,b))}).watchTransition(y,"discreteBar: bars text").attr("x",.9*n.rangeBand()/2).attr("y",function(a,b){return q(a,b)<0?o(q(a,b))-o(0)+12:-4})):E.selectAll("text").remove(),E.attr("class",function(a,b){return q(a,b)<0?"nv-bar negative":"nv-bar positive"}).style("fill",function(a,b){return a.color||s(a,b)}).style("stroke",function(a,b){return a.color||s(a,b)}).select("rect").attr("class",w).watchTransition(y,"discreteBar: bars rect").attr("width",.9*n.rangeBand()/b.length),E.watchTransition(y,"discreteBar: bars").attr("transform",function(a,b){var c=n(p(a,b))+.05*n.rangeBand(),d=q(a,b)<0?o(0):o(0)-o(q(a,b))<1?o(0)-1:o(q(a,b));return"translate("+c+", "+d+")"}).select("rect").attr("height",function(a,b){return Math.max(Math.abs(o(q(a,b))-o(e&&e[0]||0))||1)}),h=n.copy(),i=o.copy()}),y.renderEnd("discreteBar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=d3.scale.ordinal(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=[0],s=a.utils.defaultColor(),t=!1,u=d3.format(",.2f"),v=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),w="discreteBar",x=250,y=a.utils.renderWatch(v,x);return b.dispatch=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},forceY:{get:function(){return r},set:function(a){r=a}},showValues:{get:function(){return t},set:function(a){t=a}},x:{get:function(){return p},set:function(a){p=a}},y:{get:function(){return q},set:function(a){q=a}},xScale:{get:function(){return n},set:function(a){n=a}},yScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},valueFormat:{get:function(){return u},set:function(a){u=a}},id:{get:function(){return m},set:function(a){m=a}},rectClass:{get:function(){return w},set:function(a){w=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b)}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x)}}}),a.utils.initOptions(b),b},a.models.discreteBarChart=function(){"use strict";function b(h){return t.reset(),t.models(e),m&&t.models(f),n&&t.models(g),h.each(function(h){var l=d3.select(this);a.utils.initSVG(l);var q=a.utils.availableWidth(j,l,i),t=a.utils.availableHeight(k,l,i);if(b.update=function(){r.beforeUpdate(),l.transition().duration(s).call(b)},b.container=this,!(h&&h.length&&h.filter(function(a){return a.values.length}).length))return a.utils.noData(b,l),b;l.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale().clamp(!0);var u=l.selectAll("g.nv-wrap.nv-discreteBarWithAxes").data([h]),v=u.enter().append("g").attr("class","nvd3 nv-wrap nv-discreteBarWithAxes").append("g"),w=v.append("defs"),x=u.select("g");v.append("g").attr("class","nv-x nv-axis"),v.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),v.append("g").attr("class","nv-barsWrap"),x.attr("transform","translate("+i.left+","+i.top+")"),o&&x.select(".nv-y.nv-axis").attr("transform","translate("+q+",0)"),e.width(q).height(t);var y=x.select(".nv-barsWrap").datum(h.filter(function(a){return!a.disabled}));if(y.transition().call(e),w.append("clipPath").attr("id","nv-x-label-clip-"+e.id()).append("rect"),x.select("#nv-x-label-clip-"+e.id()+" rect").attr("width",c.rangeBand()*(p?2:1)).attr("height",16).attr("x",-c.rangeBand()/(p?1:2)),m){f.scale(c)._ticks(a.utils.calcTicksX(q/100,h)).tickSize(-t,0),x.select(".nv-x.nv-axis").attr("transform","translate(0,"+(d.range()[0]+(e.showValues()&&d.domain()[0]<0?16:0))+")"),x.select(".nv-x.nv-axis").call(f); var z=x.select(".nv-x.nv-axis").selectAll("g");p&&z.selectAll("text").attr("transform",function(a,b,c){return"translate(0,"+(c%2==0?"5":"17")+")"})}n&&(g.scale(d)._ticks(a.utils.calcTicksY(t/36,h)).tickSize(-q,0),x.select(".nv-y.nv-axis").call(g)),x.select(".nv-zeroLine line").attr("x1",0).attr("x2",q).attr("y1",d(0)).attr("y2",d(0))}),t.renderEnd("discreteBar chart immediate"),b}var c,d,e=a.models.discreteBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.tooltip(),i={top:15,right:10,bottom:50,left:60},j=null,k=null,l=a.utils.getColor(),m=!0,n=!0,o=!1,p=!1,q=null,r=d3.dispatch("beforeUpdate","renderEnd"),s=250;f.orient("bottom").showMaxMin(!1).tickFormat(function(a){return a}),g.orient(o?"right":"left").tickFormat(d3.format(",.1f")),h.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).keyFormatter(function(a,b){return f.tickFormat()(a,b)});var t=a.utils.renderWatch(r,s);return e.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},h.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){h.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){h.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=r,b.discretebar=e,b.xAxis=f,b.yAxis=g,b.tooltip=h,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return j},set:function(a){j=a}},height:{get:function(){return k},set:function(a){k=a}},staggerLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return m},set:function(a){m=a}},showYAxis:{get:function(){return n},set:function(a){n=a}},noData:{get:function(){return q},set:function(a){q=a}},tooltips:{get:function(){return h.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),h.enabled(!!b)}},tooltipContent:{get:function(){return h.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),h.contentGenerator(b)}},margin:{get:function(){return i},set:function(a){i.top=void 0!==a.top?a.top:i.top,i.right=void 0!==a.right?a.right:i.right,i.bottom=void 0!==a.bottom?a.bottom:i.bottom,i.left=void 0!==a.left?a.left:i.left}},duration:{get:function(){return s},set:function(a){s=a,t.reset(s),e.duration(s),f.duration(s),g.duration(s)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),e.color(l)}},rightAlignYAxis:{get:function(){return o},set:function(a){o=a,g.orient(a?"right":"left")}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.distribution=function(){"use strict";function b(k){return m.reset(),k.each(function(b){var k=(e-("x"===g?d.left+d.right:d.top+d.bottom),"x"==g?"y":"x"),l=d3.select(this);a.utils.initSVG(l),c=c||j;var n=l.selectAll("g.nv-distribution").data([b]),o=n.enter().append("g").attr("class","nvd3 nv-distribution"),p=(o.append("g"),n.select("g"));n.attr("transform","translate("+d.left+","+d.top+")");var q=p.selectAll("g.nv-dist").data(function(a){return a},function(a){return a.key});q.enter().append("g"),q.attr("class",function(a,b){return"nv-dist nv-series-"+b}).style("stroke",function(a,b){return i(a,b)});var r=q.selectAll("line.nv-dist"+g).data(function(a){return a.values});r.enter().append("line").attr(g+"1",function(a,b){return c(h(a,b))}).attr(g+"2",function(a,b){return c(h(a,b))}),m.transition(q.exit().selectAll("line.nv-dist"+g),"dist exit").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}).style("stroke-opacity",0).remove(),r.attr("class",function(a,b){return"nv-dist"+g+" nv-dist"+g+"-"+b}).attr(k+"1",0).attr(k+"2",f),m.transition(r,"dist").attr(g+"1",function(a,b){return j(h(a,b))}).attr(g+"2",function(a,b){return j(h(a,b))}),c=j.copy()}),m.renderEnd("distribution immediate"),b}var c,d={top:0,right:0,bottom:0,left:0},e=400,f=8,g="x",h=function(a){return a[g]},i=a.utils.defaultColor(),j=d3.scale.linear(),k=250,l=d3.dispatch("renderEnd"),m=a.utils.renderWatch(l,k);return b.options=a.utils.optionsFunc.bind(b),b.dispatch=l,b.margin=function(a){return arguments.length?(d.top="undefined"!=typeof a.top?a.top:d.top,d.right="undefined"!=typeof a.right?a.right:d.right,d.bottom="undefined"!=typeof a.bottom?a.bottom:d.bottom,d.left="undefined"!=typeof a.left?a.left:d.left,b):d},b.width=function(a){return arguments.length?(e=a,b):e},b.axis=function(a){return arguments.length?(g=a,b):g},b.size=function(a){return arguments.length?(f=a,b):f},b.getData=function(a){return arguments.length?(h=d3.functor(a),b):h},b.scale=function(a){return arguments.length?(j=a,b):j},b.color=function(c){return arguments.length?(i=a.utils.getColor(c),b):i},b.duration=function(a){return arguments.length?(k=a,m.reset(k),b):k},b},a.models.furiousLegend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?g(a,b):"#fff":m?void 0:a.disabled?g(a,b):"#fff"}function r(a,b){return m&&"furious"==o?a.disengaged?"#fff":g(a,b):a.disabled?"#fff":g(a,b)}return p.each(function(b){var p=d-c.left-c.right,s=d3.select(this);a.utils.initSVG(s);var t=s.selectAll("g.nv-legend").data([b]),u=(t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),t.select("g"));t.attr("transform","translate("+c.left+","+c.top+")");var v,w=u.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),x=w.enter().append("g").attr("class","nv-series");if("classic"==o)x.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),v=w.select("circle");else if("furious"==o){x.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),v=w.select("rect"),x.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)");var y=w.select(".nv-check-box");y.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}x.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var z=w.select("text.nv-legend-text");w.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=w.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=w.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),w.classed("nv-disabled",function(a){return a.userDisabled}),w.exit().remove(),z.attr("fill",q).text(f);var A;switch(o){case"furious":A=23;break;case"classic":A=20}if(h){var B=[];w.each(function(){var b,c=d3.select(this).select("text");try{if(b=c.node().getComputedTextLength(),0>=b)throw Error()}catch(d){b=a.utils.calcApproxTextWidth(c)}B.push(b+i)});for(var C=0,D=0,E=[];p>D&&Cp&&C>1;){E=[],C--;for(var F=0;F(E[F%C]||0)&&(E[F%C]=B[F]);D=E.reduce(function(a,b){return a+b})}for(var G=[],H=0,I=0;C>H;H++)G[H]=I,I+=E[H];w.attr("transform",function(a,b){return"translate("+G[b%C]+","+(5+Math.floor(b/C)*A)+")"}),j?u.attr("transform","translate("+(d-c.right-D)+","+c.top+")"):u.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(B.length/C)*A}else{var J,K=5,L=5,M=0;w.attr("transform",function(){var a=d3.select(this).select("text").node().getComputedTextLength()+i;return J=L,dM&&(M=L),"translate("+J+","+K+")"}),u.attr("transform","translate("+(d-c.right-M)+","+c.top+")"),e=c.top+c.bottom+K+15}"furious"==o&&v.attr("width",function(a,b){return z[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),v.style("fill",r).style("stroke",function(a,b){return a.color||g(a,b)})}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=28,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBar=function(){"use strict";function b(x){return x.each(function(b){w.reset(),k=d3.select(this);var x=a.utils.availableWidth(h,k,g),y=a.utils.availableHeight(i,k,g);a.utils.initSVG(k),l.domain(c||d3.extent(b[0].values.map(n).concat(p))),l.range(r?e||[.5*x/b[0].values.length,x*(b[0].values.length-.5)/b[0].values.length]:e||[0,x]),m.domain(d||d3.extent(b[0].values.map(o).concat(q))).range(f||[y,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var z=k.selectAll("g.nv-wrap.nv-historicalBar-"+j).data([b[0].values]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBar-"+j),B=A.append("defs"),C=A.append("g"),D=z.select("g");C.append("g").attr("class","nv-bars"),z.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){u.chartClick({data:a,index:b,pos:d3.event,id:j})}),B.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),z.select("#nv-chart-clip-path-"+j+" rect").attr("width",x).attr("height",y),D.attr("clip-path",s?"url(#nv-chart-clip-path-"+j+")":"carview.php?tsp=");var E=z.select(".nv-bars").selectAll(".nv-bar").data(function(a){return a},function(a,b){return n(a,b)});E.exit().remove(),E.enter().append("rect").attr("x",0).attr("y",function(b,c){return a.utils.NaNtoZero(m(Math.max(0,o(b,c))))}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.abs(m(o(b,c))-m(0)))}).attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).on("mouseover",function(a,b){v&&(d3.select(this).classed("hover",!0),u.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mouseout",function(a,b){v&&(d3.select(this).classed("hover",!1),u.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")}))}).on("mousemove",function(a,b){v&&u.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){v&&(u.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}).on("dblclick",function(a,b){v&&(u.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation())}),E.attr("fill",function(a,b){return t(a,b)}).attr("class",function(a,b,c){return(o(a,b)<0?"nv-bar negative":"nv-bar positive")+" nv-bar-"+c+"-"+b}).watchTransition(w,"bars").attr("transform",function(a,c){return"translate("+(l(n(a,c))-x/b[0].values.length*.45)+",0)"}).attr("width",x/b[0].values.length*.9),E.watchTransition(w,"bars").attr("y",function(b,c){var d=o(b,c)<0?m(0):m(0)-m(o(b,c))<1?m(0)-1:m(o(b,c));return a.utils.NaNtoZero(d)}).attr("height",function(b,c){return a.utils.NaNtoZero(Math.max(Math.abs(m(o(b,c))-m(0)),1))})}),w.renderEnd("historicalBar immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=[],q=[0],r=!1,s=!0,t=a.utils.defaultColor(),u=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),v=!0,w=a.utils.renderWatch(u,0);return b.highlightPoint=function(a,b){k.select(".nv-bars .nv-bar-0-"+a).classed("hover",b)},b.clearHighlights=function(){k.select(".nv-bars .nv-bar.hover").classed("hover",!1)},b.dispatch=u,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},forceX:{get:function(){return p},set:function(a){p=a}},forceY:{get:function(){return q},set:function(a){q=a}},padData:{get:function(){return r},set:function(a){r=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},clipEdge:{get:function(){return s},set:function(a){s=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return v},set:function(a){v=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return t},set:function(b){t=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.historicalBarChart=function(b){"use strict";function c(b){return b.each(function(k){z.reset(),z.models(f),q&&z.models(g),r&&z.models(h);var w=d3.select(this),A=this;a.utils.initSVG(w);var B=a.utils.availableWidth(n,w,l),C=a.utils.availableHeight(o,w,l);if(c.update=function(){w.transition().duration(y).call(c)},c.container=this,u.disabled=k.map(function(a){return!!a.disabled}),!v){var D;v={};for(D in u)v[D]=u[D]instanceof Array?u[D].slice(0):u[D]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(c,w),c;w.selectAll(".nv-noData").remove(),d=f.xScale(),e=f.yScale();var E=w.selectAll("g.nv-wrap.nv-historicalBarChart").data([k]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-historicalBarChart").append("g"),G=E.select("g");F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-barsWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),p&&(i.width(B),G.select(".nv-legendWrap").datum(k).call(i),l.top!=i.height()&&(l.top=i.height(),C=a.utils.availableHeight(o,w,l)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-l.top+")")),E.attr("transform","translate("+l.left+","+l.top+")"),s&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),t&&(j.width(B).height(C).margin({left:l.left,top:l.top}).svgContainer(w).xScale(d),E.select(".nv-interactive").call(j)),f.width(B).height(C).color(k.map(function(a,b){return a.color||m(a,b)}).filter(function(a,b){return!k[b].disabled}));var H=G.select(".nv-barsWrap").datum(k.filter(function(a){return!a.disabled}));H.transition().call(f),q&&(g.scale(d)._ticks(a.utils.calcTicksX(B/100,k)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+e.range()[0]+")"),G.select(".nv-x.nv-axis").transition().call(g)),r&&(h.scale(e)._ticks(a.utils.calcTicksY(C/36,k)).tickSize(-B,0),G.select(".nv-y.nv-axis").transition().call(h)),j.dispatch.on("elementMousemove",function(b){f.clearHighlights();var d,e,i,n=[];k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(g){e=a.interactiveBisect(g.values,b.pointXValue,c.x()),f.highlightPoint(e,!0);var h=g.values[e];void 0!==h&&(void 0===d&&(d=h),void 0===i&&(i=c.xScale()(c.x()(h,e))),n.push({key:g.key,value:c.y()(h,e),color:m(g,g.seriesIndex),data:g.values[e]}))});var o=g.tickFormat()(c.x()(d,e));j.tooltip.position({left:i+l.left,top:b.mouseY+l.top}).chartContainer(A.parentNode).valueFormatter(function(a){return h.tickFormat()(a)}).data({value:o,index:e,series:n})(),j.renderGuideLine(i)}),j.dispatch.on("elementMouseout",function(){x.tooltipHide(),f.clearHighlights()}),i.dispatch.on("legendClick",function(a){a.disabled=!a.disabled,k.filter(function(a){return!a.disabled}).length||k.map(function(a){return a.disabled=!1,E.selectAll(".nv-series").classed("disabled",!1),a}),u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),b.transition().call(c)}),i.dispatch.on("legendDblclick",function(a){k.forEach(function(a){a.disabled=!0}),a.disabled=!1,u.disabled=k.map(function(a){return!!a.disabled}),x.stateChange(u),c.update()}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),c.update()})}),z.renderEnd("historicalBarChart immediate"),c}var d,e,f=b||a.models.historicalBar(),g=a.models.axis(),h=a.models.axis(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:90,bottom:50,left:90},m=a.utils.defaultColor(),n=null,o=null,p=!1,q=!0,r=!0,s=!1,t=!1,u={},v=null,w=null,x=d3.dispatch("tooltipHide","stateChange","changeState","renderEnd"),y=250;g.orient("bottom").tickPadding(7),h.orient(s?"right":"left"),k.duration(0).headerEnabled(!1).valueFormatter(function(a,b){return h.tickFormat()(a,b)}).headerFormatter(function(a,b){return g.tickFormat()(a,b)});var z=a.utils.renderWatch(x,0);return f.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:c.x()(a.data),value:c.y()(a.data),color:a.color},k.data(a).hidden(!1)}),f.dispatch.on("elementMouseout.tooltip",function(){k.hidden(!0)}),f.dispatch.on("elementMousemove.tooltip",function(){k.position({top:d3.event.pageY,left:d3.event.pageX})()}),c.dispatch=x,c.bars=f,c.legend=i,c.xAxis=g,c.yAxis=h,c.interactiveLayer=j,c.tooltip=k,c.options=a.utils.optionsFunc.bind(c),c._options=Object.create({},{width:{get:function(){return n},set:function(a){n=a}},height:{get:function(){return o},set:function(a){o=a}},showLegend:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return q},set:function(a){q=a}},showYAxis:{get:function(){return r},set:function(a){r=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b),i.color(m),f.color(m)}},duration:{get:function(){return y},set:function(a){y=a,z.reset(y),h.duration(y),g.duration(y)}},rightAlignYAxis:{get:function(){return s},set:function(a){s=a,h.orient(a?"right":"left")}},useInteractiveGuideline:{get:function(){return t},set:function(a){t=a,a===!0&&c.interactive(!1)}}}),a.utils.inheritOptions(c,f),a.utils.initOptions(c),c},a.models.ohlcBarChart=function(){var b=a.models.historicalBarChart(a.models.ohlcBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open'+a.value+"
open:"+b.yAxis.tickFormat()(c.open)+"
close:"+b.yAxis.tickFormat()(c.close)+"
high"+b.yAxis.tickFormat()(c.high)+"
low:"+b.yAxis.tickFormat()(c.low)+"
"}),b},a.models.candlestickBarChart=function(){var b=a.models.historicalBarChart(a.models.candlestickBar());return b.useInteractiveGuideline(!0),b.interactiveLayer.tooltip.contentGenerator(function(a){var c=a.series[0].data,d=c.open'+a.value+"
open:"+b.yAxis.tickFormat()(c.open)+"
close:"+b.yAxis.tickFormat()(c.close)+"
high"+b.yAxis.tickFormat()(c.high)+"
low:"+b.yAxis.tickFormat()(c.low)+"
"}),b},a.models.legend=function(){"use strict";function b(p){function q(a,b){return"furious"!=o?"#000":m?a.disengaged?"#000":"#fff":m?void 0:(a.color||(a.color=g(a,b)),a.disabled?a.color:"#fff")}function r(a,b){return m&&"furious"==o&&a.disengaged?"#eee":a.color||g(a,b)}function s(a){return m&&"furious"==o?1:a.disabled?0:1}return p.each(function(b){var g=d-c.left-c.right,p=d3.select(this);a.utils.initSVG(p);var t=p.selectAll("g.nv-legend").data([b]),u=t.enter().append("g").attr("class","nvd3 nv-legend").append("g"),v=t.select("g");t.attr("transform","translate("+c.left+","+c.top+")");var w,x,y=v.selectAll(".nv-series").data(function(a){return"furious"!=o?a:a.filter(function(a){return m?!0:!a.disengaged})}),z=y.enter().append("g").attr("class","nv-series");switch(o){case"furious":x=23;break;case"classic":x=20}if("classic"==o)z.append("circle").style("stroke-width",2).attr("class","nv-legend-symbol").attr("r",5),w=y.select("circle");else if("furious"==o){z.append("rect").style("stroke-width",2).attr("class","nv-legend-symbol").attr("rx",3).attr("ry",3),w=y.select(".nv-legend-symbol"),z.append("g").attr("class","nv-check-box").property("innerHTML",'').attr("transform","translate(-10,-8)scale(0.5)");var A=y.select(".nv-check-box");A.each(function(a,b){d3.select(this).selectAll("path").attr("stroke",q(a,b))})}z.append("text").attr("text-anchor","start").attr("class","nv-legend-text").attr("dy",".32em").attr("dx","8");var B=y.select("text.nv-legend-text");y.on("mouseover",function(a,b){n.legendMouseover(a,b)}).on("mouseout",function(a,b){n.legendMouseout(a,b)}).on("click",function(a,b){n.legendClick(a,b);var c=y.data();if(k){if("classic"==o)l?(c.forEach(function(a){a.disabled=!0}),a.disabled=!1):(a.disabled=!a.disabled,c.every(function(a){return a.disabled})&&c.forEach(function(a){a.disabled=!1}));else if("furious"==o)if(m)a.disengaged=!a.disengaged,a.userDisabled=void 0==a.userDisabled?!!a.disabled:a.userDisabled,a.disabled=a.disengaged||a.userDisabled;else if(!m){a.disabled=!a.disabled,a.userDisabled=a.disabled;var d=c.filter(function(a){return!a.disengaged});d.every(function(a){return a.userDisabled})&&c.forEach(function(a){a.disabled=a.userDisabled=!1})}n.stateChange({disabled:c.map(function(a){return!!a.disabled}),disengaged:c.map(function(a){return!!a.disengaged})})}}).on("dblclick",function(a,b){if(("furious"!=o||!m)&&(n.legendDblclick(a,b),k)){var c=y.data();c.forEach(function(a){a.disabled=!0,"furious"==o&&(a.userDisabled=a.disabled)}),a.disabled=!1,"furious"==o&&(a.userDisabled=a.disabled),n.stateChange({disabled:c.map(function(a){return!!a.disabled})})}}),y.classed("nv-disabled",function(a){return a.userDisabled}),y.exit().remove(),B.attr("fill",q).text(f);var C=0;if(h){var D=[];y.each(function(){var b,c=d3.select(this).select("text");try{if(b=c.node().getComputedTextLength(),0>=b)throw Error()}catch(d){b=a.utils.calcApproxTextWidth(c)}D.push(b+i)});var E=0,F=[];for(C=0;g>C&&Eg&&E>1;){F=[],E--;for(var G=0;G(F[G%E]||0)&&(F[G%E]=D[G]);C=F.reduce(function(a,b){return a+b})}for(var H=[],I=0,J=0;E>I;I++)H[I]=J,J+=F[I];y.attr("transform",function(a,b){return"translate("+H[b%E]+","+(5+Math.floor(b/E)*x)+")"}),j?v.attr("transform","translate("+(d-c.right-C)+","+c.top+")"):v.attr("transform","translate(0,"+c.top+")"),e=c.top+c.bottom+Math.ceil(D.length/E)*x}else{var K,L=5,M=5,N=0;y.attr("transform",function(){var a=d3.select(this).select("text").node().getComputedTextLength()+i;return K=M,dN&&(N=M),K+N>C&&(C=K+N),"translate("+K+","+L+")"}),v.attr("transform","translate("+(d-c.right-N)+","+c.top+")"),e=c.top+c.bottom+L+15}if("furious"==o){w.attr("width",function(a,b){return B[0][b].getComputedTextLength()+27}).attr("height",18).attr("y",-9).attr("x",-15),u.insert("rect",":first-child").attr("class","nv-legend-bg").attr("fill","#eee").attr("opacity",0);var O=v.select(".nv-legend-bg");O.transition().duration(300).attr("x",-x).attr("width",C+x-12).attr("height",e+10).attr("y",-c.top-10).attr("opacity",m?1:0)}w.style("fill",r).style("fill-opacity",s).style("stroke",r)}),b}var c={top:5,right:0,bottom:5,left:0},d=400,e=20,f=function(a){return a.key},g=a.utils.getColor(),h=!0,i=32,j=!0,k=!0,l=!1,m=!1,n=d3.dispatch("legendClick","legendDblclick","legendMouseover","legendMouseout","stateChange"),o="classic";return b.dispatch=n,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},key:{get:function(){return f},set:function(a){f=a}},align:{get:function(){return h},set:function(a){h=a}},rightAlign:{get:function(){return j},set:function(a){j=a}},padding:{get:function(){return i},set:function(a){i=a}},updateState:{get:function(){return k},set:function(a){k=a}},radioButtonMode:{get:function(){return l},set:function(a){l=a}},expanded:{get:function(){return m},set:function(a){m=a}},vers:{get:function(){return o},set:function(a){o=a}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return g},set:function(b){g=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.line=function(){"use strict";function b(r){return v.reset(),v.models(e),r.each(function(b){i=d3.select(this);var r=a.utils.availableWidth(g,i,f),s=a.utils.availableHeight(h,i,f);a.utils.initSVG(i),c=e.xScale(),d=e.yScale(),t=t||c,u=u||d;var w=i.selectAll("g.nv-wrap.nv-line").data([b]),x=w.enter().append("g").attr("class","nvd3 nv-wrap nv-line"),y=x.append("defs"),z=x.append("g"),A=w.select("g");z.append("g").attr("class","nv-groups"),z.append("g").attr("class","nv-scatterWrap"),w.attr("transform","translate("+f.left+","+f.top+")"),e.width(r).height(s);var B=w.select(".nv-scatterWrap");B.call(e),y.append("clipPath").attr("id","nv-edge-clip-"+e.id()).append("rect"),w.select("#nv-edge-clip-"+e.id()+" rect").attr("width",r).attr("height",s>0?s:0),A.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":"carview.php?tsp="),B.attr("clip-path",p?"url(#nv-edge-clip-"+e.id()+")":"carview.php?tsp=");var C=w.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});C.enter().append("g").style("stroke-opacity",1e-6).style("stroke-width",function(a){return a.strokeWidth||j}).style("fill-opacity",1e-6),C.exit().remove(),C.attr("class",function(a,b){return(a.classed||"carview.php?tsp=")+" nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return k(a,b)}).style("stroke",function(a,b){return k(a,b)}),C.watchTransition(v,"line: groups").style("stroke-opacity",1).style("fill-opacity",function(a){return a.fillOpacity||.5});var D=C.selectAll("path.nv-area").data(function(a){return o(a)?[a]:[]});D.enter().append("path").attr("class","nv-area").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y0(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))}).y1(function(){return u(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])}),C.exit().selectAll("path.nv-area").remove(),D.watchTransition(v,"line: areaPaths").attr("d",function(b){return d3.svg.area().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y0(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))}).y1(function(){return d(d.domain()[0]<=0?d.domain()[1]>=0?0:d.domain()[1]:d.domain()[0])}).apply(this,[b.values])});var E=C.selectAll("path.nv-line").data(function(a){return[a.values]});E.enter().append("path").attr("class","nv-line").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,c){return a.utils.NaNtoZero(t(l(b,c)))}).y(function(b,c){return a.utils.NaNtoZero(u(m(b,c)))})),E.watchTransition(v,"line: linePaths").attr("d",d3.svg.line().interpolate(q).defined(n).x(function(b,d){return a.utils.NaNtoZero(c(l(b,d)))}).y(function(b,c){return a.utils.NaNtoZero(d(m(b,c)))})),t=c.copy(),u=d.copy()}),v.renderEnd("line immediate"),b}var c,d,e=a.models.scatter(),f={top:0,right:0,bottom:0,left:0},g=960,h=500,i=null,j=1.5,k=a.utils.defaultColor(),l=function(a){return a.x},m=function(a){return a.y},n=function(a,b){return!isNaN(m(a,b))&&null!==m(a,b)},o=function(a){return a.area},p=!1,q="linear",r=250,s=d3.dispatch("elementClick","elementMouseover","elementMouseout","renderEnd");e.pointSize(16).pointDomain([16,256]);var t,u,v=a.utils.renderWatch(s,r);return b.dispatch=s,b.scatter=e,e.dispatch.on("elementClick",function(){s.elementClick.apply(this,arguments)}),e.dispatch.on("elementMouseover",function(){s.elementMouseover.apply(this,arguments)}),e.dispatch.on("elementMouseout",function(){s.elementMouseout.apply(this,arguments)}),b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},defined:{get:function(){return n},set:function(a){n=a}},interpolate:{get:function(){return q},set:function(a){q=a}},clipEdge:{get:function(){return p},set:function(a){p=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}},duration:{get:function(){return r},set:function(a){r=a,v.reset(r),e.duration(r)}},isArea:{get:function(){return o},set:function(a){o=d3.functor(a)}},x:{get:function(){return l},set:function(a){l=a,e.x(a)}},y:{get:function(){return m},set:function(a){m=a,e.y(a)}},color:{get:function(){return k},set:function(b){k=a.utils.getColor(b),e.color(k)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.lineChart=function(){"use strict";function b(j){return y.reset(),y.models(e),p&&y.models(f),q&&y.models(g),j.each(function(j){var v=d3.select(this),y=this;a.utils.initSVG(v);var B=a.utils.availableWidth(m,v,k),C=a.utils.availableHeight(n,v,k);if(b.update=function(){0===x?v.call(b):v.transition().duration(x).call(b)},b.container=this,t.setter(A(j),b.update).getter(z(j)).update(),t.disabled=j.map(function(a){return!!a.disabled}),!u){var D;u={};for(D in t)u[D]=t[D]instanceof Array?t[D].slice(0):t[D] }if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,v),b;v.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var E=v.selectAll("g.nv-wrap.nv-lineChart").data([j]),F=E.enter().append("g").attr("class","nvd3 nv-wrap nv-lineChart").append("g"),G=E.select("g");F.append("rect").style("opacity",0),F.append("g").attr("class","nv-x nv-axis"),F.append("g").attr("class","nv-y nv-axis"),F.append("g").attr("class","nv-linesWrap"),F.append("g").attr("class","nv-legendWrap"),F.append("g").attr("class","nv-interactive"),G.select("rect").attr("width",B).attr("height",C>0?C:0),o&&(h.width(B),G.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),C=a.utils.availableHeight(n,v,k)),E.select(".nv-legendWrap").attr("transform","translate(0,"+-k.top+")")),E.attr("transform","translate("+k.left+","+k.top+")"),r&&G.select(".nv-y.nv-axis").attr("transform","translate("+B+",0)"),s&&(i.width(B).height(C).margin({left:k.left,top:k.top}).svgContainer(v).xScale(c),E.select(".nv-interactive").call(i)),e.width(B).height(C).color(j.map(function(a,b){return a.color||l(a,b)}).filter(function(a,b){return!j[b].disabled}));var H=G.select(".nv-linesWrap").datum(j.filter(function(a){return!a.disabled}));H.call(e),p&&(f.scale(c)._ticks(a.utils.calcTicksX(B/100,j)).tickSize(-C,0),G.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),G.select(".nv-x.nv-axis").call(f)),q&&(g.scale(d)._ticks(a.utils.calcTicksY(C/36,j)).tickSize(-B,0),G.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)t[c]=a[c];w.stateChange(t),b.update()}),i.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,h,m,n=[];if(j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,g){h=a.interactiveBisect(f.values,c.pointXValue,b.x());var i=f.values[h],j=b.y()(i,h);null!=j&&e.highlightPoint(g,h,!0),void 0!==i&&(void 0===d&&(d=i),void 0===m&&(m=b.xScale()(b.x()(i,h))),n.push({key:f.key,value:j,color:l(f,f.seriesIndex)}))}),n.length>2){var o=b.yScale().invert(c.mouseY),p=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),q=.03*p,r=a.nearestValueIndex(n.map(function(a){return a.value}),o,q);null!==r&&(n[r].highlight=!0)}var s=f.tickFormat()(b.x()(d,h));i.tooltip.position({left:c.mouseX+k.left,top:c.mouseY+k.top}).chartContainer(y.parentNode).valueFormatter(function(a){return null==a?"N/A":g.tickFormat()(a)}).data({value:s,index:h,series:n})(),i.renderGuideLine(m)}),i.dispatch.on("elementClick",function(c){var d,f=[];j.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(e){var g=a.interactiveBisect(e.values,c.pointXValue,b.x()),h=e.values[g];if("undefined"!=typeof h){"undefined"==typeof d&&(d=b.xScale()(b.x()(h,g)));var i=b.yScale()(b.y()(h,g));f.push({point:h,pointIndex:g,pos:[d,i],seriesIndex:e.seriesIndex,series:e})}}),e.dispatch.elementClick(f)}),i.dispatch.on("elementMouseout",function(){e.clearHighlights()}),w.on("changeState",function(a){"undefined"!=typeof a.disabled&&j.length===a.disabled.length&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),t.disabled=a.disabled),b.update()})}),y.renderEnd("lineChart immediate"),b}var c,d,e=a.models.line(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.interactiveGuideline(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=a.utils.defaultColor(),m=null,n=null,o=!0,p=!0,q=!0,r=!1,s=!1,t=a.utils.state(),u=null,v=null,w=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),x=250;f.orient("bottom").tickPadding(7),g.orient(r?"right":"left"),j.valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)});var y=a.utils.renderWatch(w,x),z=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},A=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){j.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),b.dispatch=w,b.lines=e,b.legend=h,b.xAxis=f,b.yAxis=g,b.interactiveLayer=i,b.tooltip=j,b.dispatch=w,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return o},set:function(a){o=a}},showXAxis:{get:function(){return p},set:function(a){p=a}},showYAxis:{get:function(){return q},set:function(a){q=a}},defaultState:{get:function(){return u},set:function(a){u=a}},noData:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return x},set:function(a){x=a,y.reset(x),e.duration(x),f.duration(x),g.duration(x)}},color:{get:function(){return l},set:function(b){l=a.utils.getColor(b),h.color(l),e.color(l)}},rightAlignYAxis:{get:function(){return r},set:function(a){r=a,g.orient(r?"right":"left")}},useInteractiveGuideline:{get:function(){return s},set:function(a){s=a,s&&(e.interactive(!1),e.useVoronoi(!1))}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.linePlusBarChart=function(){"use strict";function b(v){return v.each(function(v){function J(a){var b=+("e"==a),c=b?1:-1,d=X/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function S(){u.empty()||u.extent(I),kb.data([u.empty()?e.domain():I]).each(function(a){var b=e(a[0])-e.range()[0],c=e.range()[1]-e(a[1]);d3.select(this).select(".left").attr("width",0>b?0:b),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>c?0:c)})}function T(){I=u.empty()?null:u.extent(),c=u.empty()?e.domain():u.extent(),K.brush({extent:c,brush:u}),S(),l.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),j.width(V).height(W).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var b=db.select(".nv-focus .nv-barsWrap").datum(Z.length?Z.map(function(a){return{key:a.key,values:a.values.filter(function(a,b){return l.x()(a,b)>=c[0]&&l.x()(a,b)<=c[1]})}}):[{values:[]}]),h=db.select(".nv-focus .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$.map(function(a){return{area:a.area,fillOpacity:a.fillOpacity,key:a.key,values:a.values.filter(function(a,b){return j.x()(a,b)>=c[0]&&j.x()(a,b)<=c[1]})}}));d=Z.length?l.xScale():j.xScale(),n.scale(d)._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-W,0),n.domain([Math.ceil(c[0]),Math.floor(c[1])]),db.select(".nv-x.nv-axis").transition().duration(L).call(n),b.transition().duration(L).call(l),h.transition().duration(L).call(j),db.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),p.scale(f)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(-V,0),q.scale(g)._ticks(a.utils.calcTicksY(W/36,v)).tickSize(Z.length?0:-V,0),db.select(".nv-focus .nv-y1.nv-axis").style("opacity",Z.length?1:0),db.select(".nv-focus .nv-y2.nv-axis").style("opacity",$.length&&!$[0].disabled?1:0).attr("transform","translate("+d.range()[1]+",0)"),db.select(".nv-focus .nv-y1.nv-axis").transition().duration(L).call(p),db.select(".nv-focus .nv-y2.nv-axis").transition().duration(L).call(q)}var U=d3.select(this);a.utils.initSVG(U);var V=a.utils.availableWidth(y,U,w),W=a.utils.availableHeight(z,U,w)-(E?H:0),X=H-x.top-x.bottom;if(b.update=function(){U.transition().duration(L).call(b)},b.container=this,M.setter(R(v),b.update).getter(Q(v)).update(),M.disabled=v.map(function(a){return!!a.disabled}),!N){var Y;N={};for(Y in M)N[Y]=M[Y]instanceof Array?M[Y].slice(0):M[Y]}if(!(v&&v.length&&v.filter(function(a){return a.values.length}).length))return a.utils.noData(b,U),b;U.selectAll(".nv-noData").remove();var Z=v.filter(function(a){return!a.disabled&&a.bar}),$=v.filter(function(a){return!a.bar});d=l.xScale(),e=o.scale(),f=l.yScale(),g=j.yScale(),h=m.yScale(),i=k.yScale();var _=v.filter(function(a){return!a.disabled&&a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})}),ab=v.filter(function(a){return!a.disabled&&!a.bar}).map(function(a){return a.values.map(function(a,b){return{x:A(a,b),y:B(a,b)}})});d.range([0,V]),e.domain(d3.extent(d3.merge(_.concat(ab)),function(a){return a.x})).range([0,V]);var bb=U.selectAll("g.nv-wrap.nv-linePlusBar").data([v]),cb=bb.enter().append("g").attr("class","nvd3 nv-wrap nv-linePlusBar").append("g"),db=bb.select("g");cb.append("g").attr("class","nv-legendWrap");var eb=cb.append("g").attr("class","nv-focus");eb.append("g").attr("class","nv-x nv-axis"),eb.append("g").attr("class","nv-y1 nv-axis"),eb.append("g").attr("class","nv-y2 nv-axis"),eb.append("g").attr("class","nv-barsWrap"),eb.append("g").attr("class","nv-linesWrap");var fb=cb.append("g").attr("class","nv-context");if(fb.append("g").attr("class","nv-x nv-axis"),fb.append("g").attr("class","nv-y1 nv-axis"),fb.append("g").attr("class","nv-y2 nv-axis"),fb.append("g").attr("class","nv-barsWrap"),fb.append("g").attr("class","nv-linesWrap"),fb.append("g").attr("class","nv-brushBackground"),fb.append("g").attr("class","nv-x nv-brush"),D){var gb=t.align()?V/2:V,hb=t.align()?gb:0;t.width(gb),db.select(".nv-legendWrap").datum(v.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(a.bar?O:P),a})).call(t),w.top!=t.height()&&(w.top=t.height(),W=a.utils.availableHeight(z,U,w)-H),db.select(".nv-legendWrap").attr("transform","translate("+hb+","+-w.top+")")}bb.attr("transform","translate("+w.left+","+w.top+")"),db.select(".nv-context").style("display",E?"initial":"none"),m.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&v[b].bar})),k.width(V).height(X).color(v.map(function(a,b){return a.color||C(a,b)}).filter(function(a,b){return!v[b].disabled&&!v[b].bar}));var ib=db.select(".nv-context .nv-barsWrap").datum(Z.length?Z:[{values:[]}]),jb=db.select(".nv-context .nv-linesWrap").datum($[0].disabled?[{values:[]}]:$);db.select(".nv-context").attr("transform","translate(0,"+(W+w.bottom+x.top)+")"),ib.transition().call(m),jb.transition().call(k),G&&(o._ticks(a.utils.calcTicksX(V/100,v)).tickSize(-X,0),db.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+h.range()[0]+")"),db.select(".nv-context .nv-x.nv-axis").transition().call(o)),F&&(r.scale(h)._ticks(X/36).tickSize(-V,0),s.scale(i)._ticks(X/36).tickSize(Z.length?0:-V,0),db.select(".nv-context .nv-y3.nv-axis").style("opacity",Z.length?1:0).attr("transform","translate(0,"+e.range()[0]+")"),db.select(".nv-context .nv-y2.nv-axis").style("opacity",$.length?1:0).attr("transform","translate("+e.range()[1]+",0)"),db.select(".nv-context .nv-y1.nv-axis").transition().call(r),db.select(".nv-context .nv-y2.nv-axis").transition().call(s)),u.x(e).on("brush",T),I&&u.extent(I);var kb=db.select(".nv-brushBackground").selectAll("g").data([I||u.extent()]),lb=kb.enter().append("g");lb.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",X),lb.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",X);var mb=db.select(".nv-x.nv-brush").call(u);mb.selectAll("rect").attr("height",X),mb.selectAll(".resize").append("path").attr("d",J),t.dispatch.on("stateChange",function(a){for(var c in a)M[c]=a[c];K.stateChange(M),b.update()}),K.on("changeState",function(a){"undefined"!=typeof a.disabled&&(v.forEach(function(b,c){b.disabled=a.disabled[c]}),M.disabled=a.disabled),b.update()}),T()}),b}var c,d,e,f,g,h,i,j=a.models.line(),k=a.models.line(),l=a.models.historicalBar(),m=a.models.historicalBar(),n=a.models.axis(),o=a.models.axis(),p=a.models.axis(),q=a.models.axis(),r=a.models.axis(),s=a.models.axis(),t=a.models.legend(),u=d3.svg.brush(),v=a.models.tooltip(),w={top:30,right:30,bottom:30,left:60},x={top:0,right:30,bottom:20,left:60},y=null,z=null,A=function(a){return a.x},B=function(a){return a.y},C=a.utils.defaultColor(),D=!0,E=!0,F=!1,G=!0,H=50,I=null,J=null,K=d3.dispatch("brush","stateChange","changeState"),L=0,M=a.utils.state(),N=null,O=" (left axis)",P=" (right axis)";j.clipEdge(!0),k.interactive(!1),n.orient("bottom").tickPadding(5),p.orient("left"),q.orient("right"),o.orient("bottom").tickPadding(5),r.orient("left"),s.orient("right"),v.headerEnabled(!0).headerFormatter(function(a,b){return n.tickFormat()(a,b)});var Q=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},R=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return j.dispatch.on("elementMouseover.tooltip",function(a){v.duration(100).valueFormatter(function(a,b){return q.tickFormat()(a,b)}).data(a).position(a.pos).hidden(!1)}),j.dispatch.on("elementMouseout.tooltip",function(){v.hidden(!0)}),l.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={value:b.y()(a.data),color:a.color},v.duration(0).valueFormatter(function(a,b){return p.tickFormat()(a,b)}).data(a).hidden(!1)}),l.dispatch.on("elementMouseout.tooltip",function(){v.hidden(!0)}),l.dispatch.on("elementMousemove.tooltip",function(){v.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=K,b.legend=t,b.lines=j,b.lines2=k,b.bars=l,b.bars2=m,b.xAxis=n,b.x2Axis=o,b.y1Axis=p,b.y2Axis=q,b.y3Axis=r,b.y4Axis=s,b.tooltip=v,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return y},set:function(a){y=a}},height:{get:function(){return z},set:function(a){z=a}},showLegend:{get:function(){return D},set:function(a){D=a}},brushExtent:{get:function(){return I},set:function(a){I=a}},noData:{get:function(){return J},set:function(a){J=a}},focusEnable:{get:function(){return E},set:function(a){E=a}},focusHeight:{get:function(){return H},set:function(a){H=a}},focusShowAxisX:{get:function(){return G},set:function(a){G=a}},focusShowAxisY:{get:function(){return F},set:function(a){F=a}},legendLeftAxisHint:{get:function(){return O},set:function(a){O=a}},legendRightAxisHint:{get:function(){return P},set:function(a){P=a}},tooltips:{get:function(){return v.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),v.enabled(!!b)}},tooltipContent:{get:function(){return v.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),v.contentGenerator(b)}},margin:{get:function(){return w},set:function(a){w.top=void 0!==a.top?a.top:w.top,w.right=void 0!==a.right?a.right:w.right,w.bottom=void 0!==a.bottom?a.bottom:w.bottom,w.left=void 0!==a.left?a.left:w.left}},duration:{get:function(){return L},set:function(a){L=a}},color:{get:function(){return C},set:function(b){C=a.utils.getColor(b),t.color(C)}},x:{get:function(){return A},set:function(a){A=a,j.x(a),k.x(a),l.x(a),m.x(a)}},y:{get:function(){return B},set:function(a){B=a,j.y(a),k.y(a),l.y(a),m.y(a)}}}),a.utils.inheritOptions(b,j),a.utils.initOptions(b),b},a.models.lineWithFocusChart=function(){"use strict";function b(o){return o.each(function(o){function z(a){var b=+("e"==a),c=b?1:-1,d=M/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)}function G(){n.empty()||n.extent(y),U.data([n.empty()?e.domain():y]).each(function(a){var b=e(a[0])-c.range()[0],d=K-e(a[1]);d3.select(this).select(".left").attr("width",0>b?0:b),d3.select(this).select(".right").attr("x",e(a[1])).attr("width",0>d?0:d)})}function H(){y=n.empty()?null:n.extent();var a=n.empty()?e.domain():n.extent();if(!(Math.abs(a[0]-a[1])<=1)){A.brush({extent:a,brush:n}),G();var b=Q.select(".nv-focus .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}).map(function(b){return{key:b.key,area:b.area,values:b.values.filter(function(b,c){return g.x()(b,c)>=a[0]&&g.x()(b,c)<=a[1]})}}));b.transition().duration(B).call(g),Q.select(".nv-focus .nv-x.nv-axis").transition().duration(B).call(i),Q.select(".nv-focus .nv-y.nv-axis").transition().duration(B).call(j)}}var I=d3.select(this),J=this;a.utils.initSVG(I);var K=a.utils.availableWidth(t,I,q),L=a.utils.availableHeight(u,I,q)-v,M=v-r.top-r.bottom;if(b.update=function(){I.transition().duration(B).call(b)},b.container=this,C.setter(F(o),b.update).getter(E(o)).update(),C.disabled=o.map(function(a){return!!a.disabled}),!D){var N;D={};for(N in C)D[N]=C[N]instanceof Array?C[N].slice(0):C[N]}if(!(o&&o.length&&o.filter(function(a){return a.values.length}).length))return a.utils.noData(b,I),b;I.selectAll(".nv-noData").remove(),c=g.xScale(),d=g.yScale(),e=h.xScale(),f=h.yScale();var O=I.selectAll("g.nv-wrap.nv-lineWithFocusChart").data([o]),P=O.enter().append("g").attr("class","nvd3 nv-wrap nv-lineWithFocusChart").append("g"),Q=O.select("g");P.append("g").attr("class","nv-legendWrap");var R=P.append("g").attr("class","nv-focus");R.append("g").attr("class","nv-x nv-axis"),R.append("g").attr("class","nv-y nv-axis"),R.append("g").attr("class","nv-linesWrap"),R.append("g").attr("class","nv-interactive");var S=P.append("g").attr("class","nv-context");S.append("g").attr("class","nv-x nv-axis"),S.append("g").attr("class","nv-y nv-axis"),S.append("g").attr("class","nv-linesWrap"),S.append("g").attr("class","nv-brushBackground"),S.append("g").attr("class","nv-x nv-brush"),x&&(m.width(K),Q.select(".nv-legendWrap").datum(o).call(m),q.top!=m.height()&&(q.top=m.height(),L=a.utils.availableHeight(u,I,q)-v),Q.select(".nv-legendWrap").attr("transform","translate(0,"+-q.top+")")),O.attr("transform","translate("+q.left+","+q.top+")"),w&&(p.width(K).height(L).margin({left:q.left,top:q.top}).svgContainer(I).xScale(c),O.select(".nv-interactive").call(p)),g.width(K).height(L).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),h.defined(g.defined()).width(K).height(M).color(o.map(function(a,b){return a.color||s(a,b)}).filter(function(a,b){return!o[b].disabled})),Q.select(".nv-context").attr("transform","translate(0,"+(L+q.bottom+r.top)+")");var T=Q.select(".nv-context .nv-linesWrap").datum(o.filter(function(a){return!a.disabled}));d3.transition(T).call(h),i.scale(c)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-L,0),j.scale(d)._ticks(a.utils.calcTicksY(L/36,o)).tickSize(-K,0),Q.select(".nv-focus .nv-x.nv-axis").attr("transform","translate(0,"+L+")"),n.x(e).on("brush",function(){H()}),y&&n.extent(y);var U=Q.select(".nv-brushBackground").selectAll("g").data([y||n.extent()]),V=U.enter().append("g");V.append("rect").attr("class","left").attr("x",0).attr("y",0).attr("height",M),V.append("rect").attr("class","right").attr("x",0).attr("y",0).attr("height",M);var W=Q.select(".nv-x.nv-brush").call(n);W.selectAll("rect").attr("height",M),W.selectAll(".resize").append("path").attr("d",z),H(),k.scale(e)._ticks(a.utils.calcTicksX(K/100,o)).tickSize(-M,0),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),d3.transition(Q.select(".nv-context .nv-x.nv-axis")).call(k),l.scale(f)._ticks(a.utils.calcTicksY(M/36,o)).tickSize(-K,0),d3.transition(Q.select(".nv-context .nv-y.nv-axis")).call(l),Q.select(".nv-context .nv-x.nv-axis").attr("transform","translate(0,"+f.range()[0]+")"),m.dispatch.on("stateChange",function(a){for(var c in a)C[c]=a[c];A.stateChange(C),b.update()}),p.dispatch.on("elementMousemove",function(c){g.clearHighlights();var d,f,h,k=[];if(o.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(i,j){var l=n.empty()?e.domain():n.extent(),m=i.values.filter(function(a,b){return g.x()(a,b)>=l[0]&&g.x()(a,b)<=l[1]});f=a.interactiveBisect(m,c.pointXValue,g.x());var o=m[f],p=b.y()(o,f);null!=p&&g.highlightPoint(j,f,!0),void 0!==o&&(void 0===d&&(d=o),void 0===h&&(h=b.xScale()(b.x()(o,f))),k.push({key:i.key,value:b.y()(o,f),color:s(i,i.seriesIndex)}))}),k.length>2){var l=b.yScale().invert(c.mouseY),m=Math.abs(b.yScale().domain()[0]-b.yScale().domain()[1]),r=.03*m,t=a.nearestValueIndex(k.map(function(a){return a.value}),l,r);null!==t&&(k[t].highlight=!0)}var u=i.tickFormat()(b.x()(d,f));p.tooltip.position({left:c.mouseX+q.left,top:c.mouseY+q.top}).chartContainer(J.parentNode).valueFormatter(function(a){return null==a?"N/A":j.tickFormat()(a)}).data({value:u,index:f,series:k})(),p.renderGuideLine(h)}),p.dispatch.on("elementMouseout",function(){g.clearHighlights()}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&o.forEach(function(b,c){b.disabled=a.disabled[c]}),b.update()})}),b}var c,d,e,f,g=a.models.line(),h=a.models.line(),i=a.models.axis(),j=a.models.axis(),k=a.models.axis(),l=a.models.axis(),m=a.models.legend(),n=d3.svg.brush(),o=a.models.tooltip(),p=a.interactiveGuideline(),q={top:30,right:30,bottom:30,left:60},r={top:0,right:30,bottom:20,left:60},s=a.utils.defaultColor(),t=null,u=null,v=50,w=!1,x=!0,y=null,z=null,A=d3.dispatch("brush","stateChange","changeState"),B=250,C=a.utils.state(),D=null;g.clipEdge(!0).duration(0),h.interactive(!1),i.orient("bottom").tickPadding(5),j.orient("left"),k.orient("bottom").tickPadding(5),l.orient("left"),o.valueFormatter(function(a,b){return j.tickFormat()(a,b)}).headerFormatter(function(a,b){return i.tickFormat()(a,b)});var E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return g.dispatch.on("elementMouseover.tooltip",function(a){o.data(a).position(a.pos).hidden(!1)}),g.dispatch.on("elementMouseout.tooltip",function(){o.hidden(!0)}),b.dispatch=A,b.legend=m,b.lines=g,b.lines2=h,b.xAxis=i,b.yAxis=j,b.x2Axis=k,b.y2Axis=l,b.interactiveLayer=p,b.tooltip=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return t},set:function(a){t=a}},height:{get:function(){return u},set:function(a){u=a}},focusHeight:{get:function(){return v},set:function(a){v=a}},showLegend:{get:function(){return x},set:function(a){x=a}},brushExtent:{get:function(){return y},set:function(a){y=a}},defaultState:{get:function(){return D},set:function(a){D=a}},noData:{get:function(){return z},set:function(a){z=a}},tooltips:{get:function(){return o.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),o.enabled(!!b)}},tooltipContent:{get:function(){return o.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),o.contentGenerator(b)}},margin:{get:function(){return q},set:function(a){q.top=void 0!==a.top?a.top:q.top,q.right=void 0!==a.right?a.right:q.right,q.bottom=void 0!==a.bottom?a.bottom:q.bottom,q.left=void 0!==a.left?a.left:q.left}},color:{get:function(){return s},set:function(b){s=a.utils.getColor(b),m.color(s)}},interpolate:{get:function(){return g.interpolate()},set:function(a){g.interpolate(a),h.interpolate(a)}},xTickFormat:{get:function(){return i.tickFormat()},set:function(a){i.tickFormat(a),k.tickFormat(a)}},yTickFormat:{get:function(){return j.tickFormat()},set:function(a){j.tickFormat(a),l.tickFormat(a)}},duration:{get:function(){return B},set:function(a){B=a,j.duration(B),l.duration(B),i.duration(B),k.duration(B)}},x:{get:function(){return g.x()},set:function(a){g.x(a),h.x(a)}},y:{get:function(){return g.y()},set:function(a){g.y(a),h.y(a)}},useInteractiveGuideline:{get:function(){return w},set:function(a){w=a,w&&(g.interactive(!1),g.useVoronoi(!1))}}}),a.utils.inheritOptions(b,g),a.utils.initOptions(b),b},a.models.multiBar=function(){"use strict";function b(E){return C.reset(),E.each(function(b){var E=k-j.left-j.right,F=l-j.top-j.bottom;p=d3.select(this),a.utils.initSVG(p);var G=0;if(x&&b.length&&(x=[{values:b[0].values.map(function(a){return{x:a.x,y:0,series:a.series,size:.01}})}]),u){var H=d3.layout.stack().offset(v).values(function(a){return a.values}).y(r)(!b.length&&x?x:b);H.forEach(function(a,c){a.nonStackable?(b[c].nonStackableSeries=G++,H[c]=b[c]):c>0&&H[c-1].nonStackable&&H[c].values.map(function(a,b){a.y0-=H[c-1].values[b].y,a.y1=a.y0+a.y})}),b=H}b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),u&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a,f){if(!b[f].nonStackable){var g=a.values[c];g.size=Math.abs(g.y),g.y<0?(g.y1=e,e-=g.size):(g.y1=g.size+d,d+=g.size)}})});var I=d&&e?[]:b.map(function(a,b){return a.values.map(function(a,c){return{x:q(a,c),y:r(a,c),y0:a.y0,y1:a.y1,idx:b}})});m.domain(d||d3.merge(I).map(function(a){return a.x})).rangeBands(f||[0,E],A),n.domain(e||d3.extent(d3.merge(I).map(function(a){var c=a.y;return u&&!b[a.idx].nonStackable&&(c=a.y>0?a.y1:a.y1+a.y),c}).concat(s))).range(g||[F,0]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]:[-1,1]),n.domain()[0]===n.domain()[1]&&n.domain(n.domain()[0]?[n.domain()[0]+.01*n.domain()[0],n.domain()[1]-.01*n.domain()[1]]:[-1,1]),h=h||m,i=i||n;var J=p.selectAll("g.nv-wrap.nv-multibar").data([b]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-multibar"),L=K.append("defs"),M=K.append("g"),N=J.select("g");M.append("g").attr("class","nv-groups"),J.attr("transform","translate("+j.left+","+j.top+")"),L.append("clipPath").attr("id","nv-edge-clip-"+o).append("rect"),J.select("#nv-edge-clip-"+o+" rect").attr("width",E).attr("height",F),N.attr("clip-path",t?"url(#nv-edge-clip-"+o+")":"carview.php?tsp=");var O=J.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});O.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6);var P=C.transition(O.exit().selectAll("rect.nv-bar"),"multibarExit",Math.min(100,z)).attr("y",function(a){var c=i(0)||0;return u&&b[a.series]&&!b[a.series].nonStackable&&(c=i(a.y0)),c}).attr("height",0).remove();P.delay&&P.delay(function(a,b){var c=b*(z/(D+1))-b;return c}),O.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return w(a,b)}).style("stroke",function(a,b){return w(a,b)}),O.style("stroke-opacity",1).style("fill-opacity",.75);var Q=O.selectAll("rect.nv-bar").data(function(a){return x&&!b.length?x.values:a.values});Q.exit().remove();Q.enter().append("rect").attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("x",function(a,c,d){return u&&!b[d].nonStackable?0:d*m.rangeBand()/b.length}).attr("y",function(a,c,d){return i(u&&!b[d].nonStackable?a.y0:0)||0}).attr("height",0).attr("width",function(a,c,d){return m.rangeBand()/(u&&!b[d].nonStackable?1:b.length)}).attr("transform",function(a,b){return"translate("+m(q(a,b))+",0)"});Q.style("fill",function(a,b,c){return w(a,c,b)}).style("stroke",function(a,b,c){return w(a,c,b)}).on("mouseover",function(a,b){d3.select(this).classed("hover",!0),B.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),B.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){B.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){B.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){B.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),Q.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}).attr("transform",function(a,b){return"translate("+m(q(a,b))+",0)"}),y&&(c||(c=b.map(function(){return!0})),Q.style("fill",function(a,b,d){return d3.rgb(y(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(y(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}));var R=Q.watchTransition(C,"multibar",Math.min(250,z)).delay(function(a,c){return c*z/b[0].values.length});u?R.attr("y",function(a,c,d){var e=0;return e=b[d].nonStackable?r(a,c)<0?n(0):n(0)-n(r(a,c))<-1?n(0)-1:n(r(a,c))||0:n(a.y1)}).attr("height",function(a,c,d){return b[d].nonStackable?Math.max(Math.abs(n(r(a,c))-n(0)),1)||0:Math.max(Math.abs(n(a.y+a.y0)-n(a.y0)),1)}).attr("x",function(a,c,d){var e=0;return b[d].nonStackable&&(e=a.series*m.rangeBand()/b.length,b.length!==G&&(e=b[d].nonStackableSeries*m.rangeBand()/(2*G))),e}).attr("width",function(a,c,d){if(b[d].nonStackable){var e=m.rangeBand()/G;return b.length!==G&&(e=m.rangeBand()/(2*G)),e}return m.rangeBand()}):R.attr("x",function(a){return a.series*m.rangeBand()/b.length}).attr("width",m.rangeBand()/b.length).attr("y",function(a,b){return r(a,b)<0?n(0):n(0)-n(r(a,b))<1?n(0)-1:n(r(a,b))||0}).attr("height",function(a,b){return Math.max(Math.abs(n(r(a,b))-n(0)),1)||0}),h=m.copy(),i=n.copy(),b[0]&&b[0].values&&(D=b[0].values.length)}),C.renderEnd("multibar immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=d3.scale.ordinal(),n=d3.scale.linear(),o=Math.floor(1e4*Math.random()),p=null,q=function(a){return a.x},r=function(a){return a.y},s=[0],t=!0,u=!1,v="zero",w=a.utils.defaultColor(),x=!1,y=null,z=500,A=.1,B=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),C=a.utils.renderWatch(B,z),D=0;return b.dispatch=B,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return s},set:function(a){s=a}},stacked:{get:function(){return u},set:function(a){u=a}},stackOffset:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return t},set:function(a){t=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return o},set:function(a){o=a}},hideable:{get:function(){return x},set:function(a){x=a}},groupSpacing:{get:function(){return A},set:function(a){A=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return z},set:function(a){z=a,C.reset(z)}},color:{get:function(){return w},set:function(b){w=a.utils.getColor(b)}},barColor:{get:function(){return y},set:function(b){y=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarChart=function(){"use strict";function b(j){return D.reset(),D.models(e),r&&D.models(f),s&&D.models(g),j.each(function(j){var z=d3.select(this);a.utils.initSVG(z);var D=a.utils.availableWidth(l,z,k),H=a.utils.availableHeight(m,z,k);if(b.update=function(){0===C?z.call(b):z.transition().duration(C).call(b)},b.container=this,x.setter(G(j),b.update).getter(F(j)).update(),x.disabled=j.map(function(a){return!!a.disabled}),!y){var I;y={};for(I in x)y[I]=x[I]instanceof Array?x[I].slice(0):x[I]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,z),b;z.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale(); var J=z.selectAll("g.nv-wrap.nv-multiBarWithLegend").data([j]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarWithLegend").append("g"),L=J.select("g");if(K.append("g").attr("class","nv-x nv-axis"),K.append("g").attr("class","nv-y nv-axis"),K.append("g").attr("class","nv-barsWrap"),K.append("g").attr("class","nv-legendWrap"),K.append("g").attr("class","nv-controlsWrap"),q&&(h.width(D-B()),L.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),H=a.utils.availableHeight(m,z,k)),L.select(".nv-legendWrap").attr("transform","translate("+B()+","+-k.top+")")),o){var M=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(B()).color(["#444","#444","#444"]),L.select(".nv-controlsWrap").datum(M).attr("transform","translate(0,"+-k.top+")").call(i)}J.attr("transform","translate("+k.left+","+k.top+")"),t&&L.select(".nv-y.nv-axis").attr("transform","translate("+D+",0)"),e.disabled(j.map(function(a){return a.disabled})).width(D).height(H).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var N=L.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(N.call(e),r){f.scale(c)._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-H,0),L.select(".nv-x.nv-axis").attr("transform","translate(0,"+d.range()[0]+")"),L.select(".nv-x.nv-axis").call(f);var O=L.select(".nv-x.nv-axis > g").selectAll("g");if(O.selectAll("line, text").style("opacity",1),v){var P=function(a,b){return"translate("+a+","+b+")"},Q=5,R=17;O.selectAll("text").attr("transform",function(a,b,c){return P(0,c%2==0?Q:R)});var S=d3.selectAll(".nv-x.nv-axis .nv-wrap g g text")[0].length;L.selectAll(".nv-x.nv-axis .nv-axisMaxMin text").attr("transform",function(a,b){return P(0,0===b||S%2!==0?R:Q)})}u&&O.filter(function(a,b){return b%Math.ceil(j[0].values.length/(D/100))!==0}).selectAll("text, line").style("opacity",0),w&&O.selectAll(".tick text").attr("transform","rotate("+w+" 0,0)").style("text-anchor",w>0?"start":"end"),L.select(".nv-x.nv-axis").selectAll("g.nv-axisMaxMin text").style("opacity",1)}s&&(g.scale(d)._ticks(a.utils.calcTicksY(H/36,j)).tickSize(-D,0),L.select(".nv-y.nv-axis").call(g)),h.dispatch.on("stateChange",function(a){for(var c in a)x[c]=a[c];A.stateChange(x),b.update()}),i.dispatch.on("legendClick",function(a){if(a.disabled){switch(M=M.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":case p.grouped:e.stacked(!1);break;case"Stacked":case p.stacked:e.stacked(!0)}x.stacked=e.stacked(),A.stateChange(x),b.update()}}),A.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),x.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),x.stacked=a.stacked,E=a.stacked),b.update()})}),D.renderEnd("multibarchart immediate"),b}var c,d,e=a.models.multiBar(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=!0,v=!1,w=0,x=a.utils.state(),y=null,z=null,A=d3.dispatch("stateChange","changeState","renderEnd"),B=function(){return o?180:0},C=250;x.stacked=!1,e.stacked(!1),f.orient("bottom").tickPadding(7).showMaxMin(!1).tickFormat(function(a){return a}),g.orient(t?"right":"left").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var D=a.utils.renderWatch(A),E=!1,F=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:E}}},G=function(a){return function(b){void 0!==b.stacked&&(E=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=A,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=x,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return y},set:function(a){y=a}},noData:{get:function(){return z},set:function(a){z=a}},reduceXTicks:{get:function(){return u},set:function(a){u=a}},rotateLabels:{get:function(){return w},set:function(a){w=a}},staggerLabels:{get:function(){return v},set:function(a){v=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return C},set:function(a){C=a,e.duration(C),f.duration(C),g.duration(C),D.reset(C)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiBarHorizontal=function(){"use strict";function b(m){return E.reset(),m.each(function(b){var m=k-j.left-j.right,C=l-j.top-j.bottom;n=d3.select(this),a.utils.initSVG(n),w&&(b=d3.layout.stack().offset("zero").values(function(a){return a.values}).y(r)(b)),b.forEach(function(a,b){a.values.forEach(function(c){c.series=b,c.key=a.key})}),w&&b[0].values.map(function(a,c){var d=0,e=0;b.map(function(a){var b=a.values[c];b.size=Math.abs(b.y),b.y<0?(b.y1=e-b.size,e-=b.size):(b.y1=d,d+=b.size)})});var F=d&&e?[]:b.map(function(a){return a.values.map(function(a,b){return{x:q(a,b),y:r(a,b),y0:a.y0,y1:a.y1}})});o.domain(d||d3.merge(F).map(function(a){return a.x})).rangeBands(f||[0,C],A),p.domain(e||d3.extent(d3.merge(F).map(function(a){return w?a.y>0?a.y1+a.y:a.y1:a.y}).concat(t))),p.range(x&&!w?g||[p.domain()[0]<0?z:0,m-(p.domain()[1]>0?z:0)]:g||[0,m]),h=h||o,i=i||d3.scale.linear().domain(p.domain()).range([p(0),p(0)]);{var G=d3.select(this).selectAll("g.nv-wrap.nv-multibarHorizontal").data([b]),H=G.enter().append("g").attr("class","nvd3 nv-wrap nv-multibarHorizontal"),I=(H.append("defs"),H.append("g"));G.select("g")}I.append("g").attr("class","nv-groups"),G.attr("transform","translate("+j.left+","+j.top+")");var J=G.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a,b){return b});J.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),J.exit().watchTransition(E,"multibarhorizontal: exit groups").style("stroke-opacity",1e-6).style("fill-opacity",1e-6).remove(),J.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}).style("fill",function(a,b){return u(a,b)}).style("stroke",function(a,b){return u(a,b)}),J.watchTransition(E,"multibarhorizontal: groups").style("stroke-opacity",1).style("fill-opacity",.75);var K=J.selectAll("g.nv-bar").data(function(a){return a.values});K.exit().remove();var L=K.enter().append("g").attr("transform",function(a,c,d){return"translate("+i(w?a.y0:0)+","+(w?0:d*o.rangeBand()/b.length+o(q(a,c)))+")"});L.append("rect").attr("width",0).attr("height",o.rangeBand()/(w?1:b.length)),K.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),D.elementMouseover({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){d3.select(this).classed("hover",!1),D.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mouseout",function(a,b){D.elementMouseout({data:a,index:b,color:d3.select(this).style("fill")})}).on("mousemove",function(a,b){D.elementMousemove({data:a,index:b,color:d3.select(this).style("fill")})}).on("click",function(a,b){D.elementClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}).on("dblclick",function(a,b){D.elementDblClick({data:a,index:b,color:d3.select(this).style("fill")}),d3.event.stopPropagation()}),s(b[0],0)&&(L.append("polyline"),K.select("polyline").attr("fill","none").attr("points",function(a,c){var d=s(a,c),e=.8*o.rangeBand()/(2*(w?1:b.length));d=d.length?d:[-Math.abs(d),Math.abs(d)],d=d.map(function(a){return p(a)-p(0)});var f=[[d[0],-e],[d[0],e],[d[0],0],[d[1],0],[d[1],-e],[d[1],e]];return f.map(function(a){return a.join(",")}).join(" ")}).attr("transform",function(a,c){var d=o.rangeBand()/(2*(w?1:b.length));return"translate("+(r(a,c)<0?0:p(r(a,c))-p(0))+", "+d+")"})),L.append("text"),x&&!w?(K.select("text").attr("text-anchor",function(a,b){return r(a,b)<0?"end":"start"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){var c=B(r(a,b)),d=s(a,b);return void 0===d?c:d.length?c+"+"+B(Math.abs(d[1]))+"-"+B(Math.abs(d[0])):c+"±"+B(Math.abs(d))}),K.watchTransition(E,"multibarhorizontal: bars").select("text").attr("x",function(a,b){return r(a,b)<0?-4:p(r(a,b))-p(0)+4})):K.selectAll("text").text("carview.php?tsp="),y&&!w?(L.append("text").classed("nv-bar-label",!0),K.select("text.nv-bar-label").attr("text-anchor",function(a,b){return r(a,b)<0?"start":"end"}).attr("y",o.rangeBand()/(2*b.length)).attr("dy",".32em").text(function(a,b){return q(a,b)}),K.watchTransition(E,"multibarhorizontal: bars").select("text.nv-bar-label").attr("x",function(a,b){return r(a,b)<0?p(0)-p(r(a,b))+4:-4})):K.selectAll("text.nv-bar-label").text("carview.php?tsp="),K.attr("class",function(a,b){return r(a,b)<0?"nv-bar negative":"nv-bar positive"}),v&&(c||(c=b.map(function(){return!0})),K.style("fill",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()}).style("stroke",function(a,b,d){return d3.rgb(v(a,b)).darker(c.map(function(a,b){return b}).filter(function(a,b){return!c[b]})[d]).toString()})),w?K.watchTransition(E,"multibarhorizontal: bars").attr("transform",function(a,b){return"translate("+p(a.y1)+","+o(q(a,b))+")"}).select("rect").attr("width",function(a,b){return Math.abs(p(r(a,b)+a.y0)-p(a.y0))}).attr("height",o.rangeBand()):K.watchTransition(E,"multibarhorizontal: bars").attr("transform",function(a,c){return"translate("+p(r(a,c)<0?r(a,c):0)+","+(a.series*o.rangeBand()/b.length+o(q(a,c)))+")"}).select("rect").attr("height",o.rangeBand()/b.length).attr("width",function(a,b){return Math.max(Math.abs(p(r(a,b))-p(0)),1)}),h=o.copy(),i=p.copy()}),E.renderEnd("multibarHorizontal immediate"),b}var c,d,e,f,g,h,i,j={top:0,right:0,bottom:0,left:0},k=960,l=500,m=Math.floor(1e4*Math.random()),n=null,o=d3.scale.ordinal(),p=d3.scale.linear(),q=function(a){return a.x},r=function(a){return a.y},s=function(a){return a.yErr},t=[0],u=a.utils.defaultColor(),v=null,w=!1,x=!1,y=!1,z=60,A=.1,B=d3.format(",.2f"),C=250,D=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),E=a.utils.renderWatch(D,C);return b.dispatch=D,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},x:{get:function(){return q},set:function(a){q=a}},y:{get:function(){return r},set:function(a){r=a}},yErr:{get:function(){return s},set:function(a){s=a}},xScale:{get:function(){return o},set:function(a){o=a}},yScale:{get:function(){return p},set:function(a){p=a}},xDomain:{get:function(){return d},set:function(a){d=a}},yDomain:{get:function(){return e},set:function(a){e=a}},xRange:{get:function(){return f},set:function(a){f=a}},yRange:{get:function(){return g},set:function(a){g=a}},forceY:{get:function(){return t},set:function(a){t=a}},stacked:{get:function(){return w},set:function(a){w=a}},showValues:{get:function(){return x},set:function(a){x=a}},disabled:{get:function(){return c},set:function(a){c=a}},id:{get:function(){return m},set:function(a){m=a}},valueFormat:{get:function(){return B},set:function(a){B=a}},valuePadding:{get:function(){return z},set:function(a){z=a}},groupSpacing:{get:function(){return A},set:function(a){A=a}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},duration:{get:function(){return C},set:function(a){C=a,E.reset(C)}},color:{get:function(){return u},set:function(b){u=a.utils.getColor(b)}},barColor:{get:function(){return v},set:function(b){v=b?a.utils.getColor(b):null}}}),a.utils.initOptions(b),b},a.models.multiBarHorizontalChart=function(){"use strict";function b(j){return C.reset(),C.models(e),r&&C.models(f),s&&C.models(g),j.each(function(j){var w=d3.select(this);a.utils.initSVG(w);var C=a.utils.availableWidth(l,w,k),D=a.utils.availableHeight(m,w,k);if(b.update=function(){w.transition().duration(z).call(b)},b.container=this,t=e.stacked(),u.setter(B(j),b.update).getter(A(j)).update(),u.disabled=j.map(function(a){return!!a.disabled}),!v){var E;v={};for(E in u)v[E]=u[E]instanceof Array?u[E].slice(0):u[E]}if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,w),b;w.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var F=w.selectAll("g.nv-wrap.nv-multiBarHorizontalChart").data([j]),G=F.enter().append("g").attr("class","nvd3 nv-wrap nv-multiBarHorizontalChart").append("g"),H=F.select("g");if(G.append("g").attr("class","nv-x nv-axis"),G.append("g").attr("class","nv-y nv-axis").append("g").attr("class","nv-zeroLine").append("line"),G.append("g").attr("class","nv-barsWrap"),G.append("g").attr("class","nv-legendWrap"),G.append("g").attr("class","nv-controlsWrap"),q&&(h.width(C-y()),H.select(".nv-legendWrap").datum(j).call(h),k.top!=h.height()&&(k.top=h.height(),D=a.utils.availableHeight(m,w,k)),H.select(".nv-legendWrap").attr("transform","translate("+y()+","+-k.top+")")),o){var I=[{key:p.grouped||"Grouped",disabled:e.stacked()},{key:p.stacked||"Stacked",disabled:!e.stacked()}];i.width(y()).color(["#444","#444","#444"]),H.select(".nv-controlsWrap").datum(I).attr("transform","translate(0,"+-k.top+")").call(i)}F.attr("transform","translate("+k.left+","+k.top+")"),e.disabled(j.map(function(a){return a.disabled})).width(C).height(D).color(j.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!j[b].disabled}));var J=H.select(".nv-barsWrap").datum(j.filter(function(a){return!a.disabled}));if(J.transition().call(e),r){f.scale(c)._ticks(a.utils.calcTicksY(D/24,j)).tickSize(-C,0),H.select(".nv-x.nv-axis").call(f);var K=H.select(".nv-x.nv-axis").selectAll("g");K.selectAll("line, text")}s&&(g.scale(d)._ticks(a.utils.calcTicksX(C/100,j)).tickSize(-D,0),H.select(".nv-y.nv-axis").attr("transform","translate(0,"+D+")"),H.select(".nv-y.nv-axis").call(g)),H.select(".nv-zeroLine line").attr("x1",d(0)).attr("x2",d(0)).attr("y1",0).attr("y2",-D),h.dispatch.on("stateChange",function(a){for(var c in a)u[c]=a[c];x.stateChange(u),b.update()}),i.dispatch.on("legendClick",function(a){if(a.disabled){switch(I=I.map(function(a){return a.disabled=!0,a}),a.disabled=!1,a.key){case"Grouped":e.stacked(!1);break;case"Stacked":e.stacked(!0)}u.stacked=e.stacked(),x.stateChange(u),t=e.stacked(),b.update()}}),x.on("changeState",function(a){"undefined"!=typeof a.disabled&&(j.forEach(function(b,c){b.disabled=a.disabled[c]}),u.disabled=a.disabled),"undefined"!=typeof a.stacked&&(e.stacked(a.stacked),u.stacked=a.stacked,t=a.stacked),b.update()})}),C.renderEnd("multibar horizontal chart immediate"),b}var c,d,e=a.models.multiBarHorizontal(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend().height(30),i=a.models.legend().height(30),j=a.models.tooltip(),k={top:30,right:20,bottom:50,left:60},l=null,m=null,n=a.utils.defaultColor(),o=!0,p={},q=!0,r=!0,s=!0,t=!1,u=a.utils.state(),v=null,w=null,x=d3.dispatch("stateChange","changeState","renderEnd"),y=function(){return o?180:0},z=250;u.stacked=!1,e.stacked(t),f.orient("left").tickPadding(5).showMaxMin(!1).tickFormat(function(a){return a}),g.orient("bottom").tickFormat(d3.format(",.1f")),j.duration(0).valueFormatter(function(a,b){return g.tickFormat()(a,b)}).headerFormatter(function(a,b){return f.tickFormat()(a,b)}),i.updateState(!1);var A=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),stacked:t}}},B=function(a){return function(b){void 0!==b.stacked&&(t=b.stacked),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},C=a.utils.renderWatch(x,z);return e.dispatch.on("elementMouseover.tooltip",function(a){a.value=b.x()(a.data),a.series={key:a.data.key,value:b.y()(a.data),color:a.color},j.data(a).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){j.hidden(!0)}),e.dispatch.on("elementMousemove.tooltip",function(){j.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=x,b.multibar=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.state=u,b.tooltip=j,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return l},set:function(a){l=a}},height:{get:function(){return m},set:function(a){m=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showControls:{get:function(){return o},set:function(a){o=a}},controlLabels:{get:function(){return p},set:function(a){p=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return v},set:function(a){v=a}},noData:{get:function(){return w},set:function(a){w=a}},tooltips:{get:function(){return j.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),j.enabled(!!b)}},tooltipContent:{get:function(){return j.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),j.contentGenerator(b)}},margin:{get:function(){return k},set:function(a){k.top=void 0!==a.top?a.top:k.top,k.right=void 0!==a.right?a.right:k.right,k.bottom=void 0!==a.bottom?a.bottom:k.bottom,k.left=void 0!==a.left?a.left:k.left}},duration:{get:function(){return z},set:function(a){z=a,C.reset(z),e.duration(z),f.duration(z),g.duration(z)}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),h.color(n)}},barColor:{get:function(){return e.barColor},set:function(a){e.barColor(a),h.color(function(a,b){return d3.rgb("#ccc").darker(1.5*b).toString()})}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.multiChart=function(){"use strict";function b(j){return j.each(function(j){function k(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.value=a.point.x,a.series={value:a.point.y,color:a.point.color},B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function l(a){var b=2===j[a.seriesIndex].yAxis?z:y;a.point.x=v.x()(a.point),a.point.y=v.y()(a.point),B.duration(100).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).position(a.pos).hidden(!1)}function n(a){var b=2===j[a.data.series].yAxis?z:y;a.value=t.x()(a.data),a.series={value:t.y()(a.data),color:a.color},B.duration(0).valueFormatter(function(a,c){return b.tickFormat()(a,c)}).data(a).hidden(!1)}var C=d3.select(this);a.utils.initSVG(C),b.update=function(){C.transition().call(b)},b.container=this;var D=a.utils.availableWidth(g,C,e),E=a.utils.availableHeight(h,C,e),F=j.filter(function(a){return"line"==a.type&&1==a.yAxis}),G=j.filter(function(a){return"line"==a.type&&2==a.yAxis}),H=j.filter(function(a){return"bar"==a.type&&1==a.yAxis}),I=j.filter(function(a){return"bar"==a.type&&2==a.yAxis}),J=j.filter(function(a){return"area"==a.type&&1==a.yAxis}),K=j.filter(function(a){return"area"==a.type&&2==a.yAxis});if(!(j&&j.length&&j.filter(function(a){return a.values.length}).length))return a.utils.noData(b,C),b;C.selectAll(".nv-noData").remove();var L=j.filter(function(a){return!a.disabled&&1==a.yAxis}).map(function(a){return a.values.map(function(a){return{x:a.x,y:a.y}})}),M=j.filter(function(a){return!a.disabled&&2==a.yAxis}).map(function(a){return a.values.map(function(a){return{x:a.x,y:a.y}})});o.domain(d3.extent(d3.merge(L.concat(M)),function(a){return a.x})).range([0,D]);var N=C.selectAll("g.wrap.multiChart").data([j]),O=N.enter().append("g").attr("class","wrap nvd3 multiChart").append("g");O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y1 nv-axis"),O.append("g").attr("class","nv-y2 nv-axis"),O.append("g").attr("class","lines1Wrap"),O.append("g").attr("class","lines2Wrap"),O.append("g").attr("class","bars1Wrap"),O.append("g").attr("class","bars2Wrap"),O.append("g").attr("class","stack1Wrap"),O.append("g").attr("class","stack2Wrap"),O.append("g").attr("class","legendWrap");var P=N.select("g"),Q=j.map(function(a,b){return j[b].color||f(a,b)});if(i){var R=A.align()?D/2:D,S=A.align()?R:0;A.width(R),A.color(Q),P.select(".legendWrap").datum(j.map(function(a){return a.originalKey=void 0===a.originalKey?a.key:a.originalKey,a.key=a.originalKey+(1==a.yAxis?"carview.php?tsp=":" (right axis)"),a})).call(A),e.top!=A.height()&&(e.top=A.height(),E=a.utils.availableHeight(h,C,e)),P.select(".legendWrap").attr("transform","translate("+S+","+-e.top+")")}r.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"line"==j[b].type})),s.width(D).height(E).interpolate(m).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"line"==j[b].type})),t.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"bar"==j[b].type})),u.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"bar"==j[b].type})),v.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&1==j[b].yAxis&&"area"==j[b].type})),w.width(D).height(E).color(Q.filter(function(a,b){return!j[b].disabled&&2==j[b].yAxis&&"area"==j[b].type})),P.attr("transform","translate("+e.left+","+e.top+")");var T=P.select(".lines1Wrap").datum(F.filter(function(a){return!a.disabled})),U=P.select(".bars1Wrap").datum(H.filter(function(a){return!a.disabled})),V=P.select(".stack1Wrap").datum(J.filter(function(a){return!a.disabled})),W=P.select(".lines2Wrap").datum(G.filter(function(a){return!a.disabled})),X=P.select(".bars2Wrap").datum(I.filter(function(a){return!a.disabled})),Y=P.select(".stack2Wrap").datum(K.filter(function(a){return!a.disabled})),Z=J.length?J.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[],$=K.length?K.map(function(a){return a.values}).reduce(function(a,b){return a.map(function(a,c){return{x:a.x,y:a.y+b[c].y}})}).concat([{x:0,y:0}]):[];p.domain(c||d3.extent(d3.merge(L).concat(Z),function(a){return a.y})).range([0,E]),q.domain(d||d3.extent(d3.merge(M).concat($),function(a){return a.y})).range([0,E]),r.yDomain(p.domain()),t.yDomain(p.domain()),v.yDomain(p.domain()),s.yDomain(q.domain()),u.yDomain(q.domain()),w.yDomain(q.domain()),J.length&&d3.transition(V).call(v),K.length&&d3.transition(Y).call(w),H.length&&d3.transition(U).call(t),I.length&&d3.transition(X).call(u),F.length&&d3.transition(T).call(r),G.length&&d3.transition(W).call(s),x._ticks(a.utils.calcTicksX(D/100,j)).tickSize(-E,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+E+")"),d3.transition(P.select(".nv-x.nv-axis")).call(x),y._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y1.nv-axis")).call(y),z._ticks(a.utils.calcTicksY(E/36,j)).tickSize(-D,0),d3.transition(P.select(".nv-y2.nv-axis")).call(z),P.select(".nv-y1.nv-axis").classed("nv-disabled",L.length?!1:!0).attr("transform","translate("+o.range()[0]+",0)"),P.select(".nv-y2.nv-axis").classed("nv-disabled",M.length?!1:!0).attr("transform","translate("+o.range()[1]+",0)"),A.dispatch.on("stateChange",function(){b.update()}),r.dispatch.on("elementMouseover.tooltip",k),s.dispatch.on("elementMouseover.tooltip",k),r.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),s.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),v.dispatch.on("elementMouseover.tooltip",l),w.dispatch.on("elementMouseover.tooltip",l),v.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),w.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),t.dispatch.on("elementMouseover.tooltip",n),u.dispatch.on("elementMouseover.tooltip",n),t.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),u.dispatch.on("elementMouseout.tooltip",function(){B.hidden(!0)}),t.dispatch.on("elementMousemove.tooltip",function(){B.position({top:d3.event.pageY,left:d3.event.pageX})()}),u.dispatch.on("elementMousemove.tooltip",function(){B.position({top:d3.event.pageY,left:d3.event.pageX})()})}),b}var c,d,e={top:30,right:20,bottom:50,left:60},f=a.utils.defaultColor(),g=null,h=null,i=!0,j=null,k=function(a){return a.x},l=function(a){return a.y},m="monotone",n=!0,o=d3.scale.linear(),p=d3.scale.linear(),q=d3.scale.linear(),r=a.models.line().yScale(p),s=a.models.line().yScale(q),t=a.models.multiBar().stacked(!1).yScale(p),u=a.models.multiBar().stacked(!1).yScale(q),v=a.models.stackedArea().yScale(p),w=a.models.stackedArea().yScale(q),x=a.models.axis().scale(o).orient("bottom").tickPadding(5),y=a.models.axis().scale(p).orient("left"),z=a.models.axis().scale(q).orient("right"),A=a.models.legend().height(30),B=a.models.tooltip(),C=d3.dispatch();return b.dispatch=C,b.lines1=r,b.lines2=s,b.bars1=t,b.bars2=u,b.stack1=v,b.stack2=w,b.xAxis=x,b.yAxis1=y,b.yAxis2=z,b.tooltip=B,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},showLegend:{get:function(){return i},set:function(a){i=a}},yDomain1:{get:function(){return c},set:function(a){c=a}},yDomain2:{get:function(){return d},set:function(a){d=a}},noData:{get:function(){return j},set:function(a){j=a}},interpolate:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return B.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),B.enabled(!!b)}},tooltipContent:{get:function(){return B.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),B.contentGenerator(b)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return f},set:function(b){f=a.utils.getColor(b)}},x:{get:function(){return k},set:function(a){k=a,r.x(a),s.x(a),t.x(a),u.x(a),v.x(a),w.x(a)}},y:{get:function(){return l},set:function(a){l=a,r.y(a),s.y(a),v.y(a),w.y(a),t.y(a),u.y(a)}},useVoronoi:{get:function(){return n},set:function(a){n=a,r.useVoronoi(a),s.useVoronoi(a),v.useVoronoi(a),w.useVoronoi(a)}}}),a.utils.initOptions(b),b},a.models.ohlcBar=function(){"use strict";function b(y){return y.each(function(b){k=d3.select(this);var y=a.utils.availableWidth(h,k,g),A=a.utils.availableHeight(i,k,g);a.utils.initSVG(k);var B=y/b[0].values.length*.9;l.domain(c||d3.extent(b[0].values.map(n).concat(t))),l.range(v?e||[.5*y/b[0].values.length,y*(b[0].values.length-.5)/b[0].values.length]:e||[5+B/2,y-B/2-5]),m.domain(d||[d3.min(b[0].values.map(s).concat(u)),d3.max(b[0].values.map(r).concat(u))]).range(f||[A,0]),l.domain()[0]===l.domain()[1]&&l.domain(l.domain()[0]?[l.domain()[0]-.01*l.domain()[0],l.domain()[1]+.01*l.domain()[1]]:[-1,1]),m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]+.01*m.domain()[0],m.domain()[1]-.01*m.domain()[1]]:[-1,1]);var C=d3.select(this).selectAll("g.nv-wrap.nv-ohlcBar").data([b[0].values]),D=C.enter().append("g").attr("class","nvd3 nv-wrap nv-ohlcBar"),E=D.append("defs"),F=D.append("g"),G=C.select("g");F.append("g").attr("class","nv-ticks"),C.attr("transform","translate("+g.left+","+g.top+")"),k.on("click",function(a,b){z.chartClick({data:a,index:b,pos:d3.event,id:j})}),E.append("clipPath").attr("id","nv-chart-clip-path-"+j).append("rect"),C.select("#nv-chart-clip-path-"+j+" rect").attr("width",y).attr("height",A),G.attr("clip-path",w?"url(#nv-chart-clip-path-"+j+")":"carview.php?tsp=");var H=C.select(".nv-ticks").selectAll(".nv-tick").data(function(a){return a});H.exit().remove(),H.enter().append("path").attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}).attr("d",function(a,b){return"m0,0l0,"+(m(p(a,b))-m(r(a,b)))+"l"+-B/2+",0l"+B/2+",0l0,"+(m(s(a,b))-m(p(a,b)))+"l0,"+(m(q(a,b))-m(s(a,b)))+"l"+B/2+",0l"+-B/2+",0z"}).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("fill",function(){return x[0]}).attr("stroke",function(){return x[0]}).attr("x",0).attr("y",function(a,b){return m(Math.max(0,o(a,b)))}).attr("height",function(a,b){return Math.abs(m(o(a,b))-m(0))}),H.attr("class",function(a,b,c){return(p(a,b)>q(a,b)?"nv-tick negative":"nv-tick positive")+" nv-tick-"+c+"-"+b}),d3.transition(H).attr("transform",function(a,b){return"translate("+l(n(a,b))+","+m(r(a,b))+")"}).attr("d",function(a,c){var d=y/b[0].values.length*.9;return"m0,0l0,"+(m(p(a,c))-m(r(a,c)))+"l"+-d/2+",0l"+d/2+",0l0,"+(m(s(a,c))-m(p(a,c)))+"l0,"+(m(q(a,c))-m(s(a,c)))+"l"+d/2+",0l"+-d/2+",0z"})}),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=Math.floor(1e4*Math.random()),k=null,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=function(a){return a.open},q=function(a){return a.close},r=function(a){return a.high},s=function(a){return a.low},t=[],u=[],v=!1,w=!0,x=a.utils.defaultColor(),y=!1,z=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd","chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove");return b.highlightPoint=function(a,c){b.clearHighlights(),k.select(".nv-ohlcBar .nv-tick-0-"+a).classed("hover",c)},b.clearHighlights=function(){k.select(".nv-ohlcBar .nv-tick.hover").classed("hover",!1)},b.dispatch=z,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},padData:{get:function(){return v},set:function(a){v=a}},clipEdge:{get:function(){return w},set:function(a){w=a}},id:{get:function(){return j},set:function(a){j=a}},interactive:{get:function(){return y},set:function(a){y=a}},x:{get:function(){return n},set:function(a){n=a}},y:{get:function(){return o},set:function(a){o=a}},open:{get:function(){return p()},set:function(a){p=a}},close:{get:function(){return q()},set:function(a){q=a}},high:{get:function(){return r},set:function(a){r=a}},low:{get:function(){return s},set:function(a){s=a}},margin:{get:function(){return g},set:function(a){g.top=void 0!=a.top?a.top:g.top,g.right=void 0!=a.right?a.right:g.right,g.bottom=void 0!=a.bottom?a.bottom:g.bottom,g.left=void 0!=a.left?a.left:g.left }},color:{get:function(){return x},set:function(b){x=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.parallelCoordinates=function(){"use strict";function b(p){return p.each(function(b){function p(a){return F(h.map(function(b){if(isNaN(a[b])||isNaN(parseFloat(a[b]))){var c=g[b].domain(),d=g[b].range(),e=c[0]-(c[1]-c[0])/9;if(J.indexOf(b)<0){var h=d3.scale.linear().domain([e,c[1]]).range([x-12,d[1]]);g[b].brush.y(h),J.push(b)}return[f(b),g[b](e)]}return J.length>0?(D.style("display","inline"),E.style("display","inline")):(D.style("display","none"),E.style("display","none")),[f(b),g[b](a[b])]}))}function q(){var a=h.filter(function(a){return!g[a].brush.empty()}),b=a.map(function(a){return g[a].brush.extent()});k=[],a.forEach(function(a,c){k[c]={dimension:a,extent:b[c]}}),l=[],M.style("display",function(c){var d=a.every(function(a,d){return isNaN(c[a])&&b[d][0]==g[a].brush.y().domain()[0]?!0:b[d][0]<=c[a]&&c[a]<=b[d][1]});return d&&l.push(c),d?null:"none"}),o.brush({filters:k,active:l})}function r(a){m[a]=this.parentNode.__origin__=f(a),L.attr("visibility","hidden")}function s(a){m[a]=Math.min(w,Math.max(0,this.parentNode.__origin__+=d3.event.x)),M.attr("d",p),h.sort(function(a,b){return u(a)-u(b)}),f.domain(h),N.attr("transform",function(a){return"translate("+u(a)+")"})}function t(a){delete this.parentNode.__origin__,delete m[a],d3.select(this.parentNode).attr("transform","translate("+f(a)+")"),M.attr("d",p),L.attr("d",p).attr("visibility",null)}function u(a){var b=m[a];return null==b?f(a):b}var v=d3.select(this),w=a.utils.availableWidth(d,v,c),x=a.utils.availableHeight(e,v,c);a.utils.initSVG(v),l=b,f.rangePoints([0,w],1).domain(h);var y={};h.forEach(function(a){var c=d3.extent(b,function(b){return+b[a]});return y[a]=!1,void 0===c[0]&&(y[a]=!0,c[0]=0,c[1]=0),c[0]===c[1]&&(c[0]=c[0]-1,c[1]=c[1]+1),g[a]=d3.scale.linear().domain(c).range([.9*(x-12),0]),g[a].brush=d3.svg.brush().y(g[a]).on("brush",q),"name"!=a});var z=v.selectAll("g.nv-wrap.nv-parallelCoordinates").data([b]),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-parallelCoordinates"),B=A.append("g"),C=z.select("g");B.append("g").attr("class","nv-parallelCoordinates background"),B.append("g").attr("class","nv-parallelCoordinates foreground"),B.append("g").attr("class","nv-parallelCoordinates missingValuesline"),z.attr("transform","translate("+c.left+","+c.top+")");var D,E,F=d3.svg.line().interpolate("cardinal").tension(n),G=d3.svg.axis().orient("left"),H=d3.behavior.drag().on("dragstart",r).on("drag",s).on("dragend",t),I=f.range()[1]-f.range()[0],J=[],K=[0+I/2,x-12,w-I/2,x-12];D=z.select(".missingValuesline").selectAll("line").data([K]),D.enter().append("line"),D.exit().remove(),D.attr("x1",function(a){return a[0]}).attr("y1",function(a){return a[1]}).attr("x2",function(a){return a[2]}).attr("y2",function(a){return a[3]}),E=z.select(".missingValuesline").selectAll("text").data(["undefined values"]),E.append("text").data(["undefined values"]),E.enter().append("text"),E.exit().remove(),E.attr("y",x).attr("x",w-92-I/2).text(function(a){return a});var L=z.select(".background").selectAll("path").data(b);L.enter().append("path"),L.exit().remove(),L.attr("d",p);var M=z.select(".foreground").selectAll("path").data(b);M.enter().append("path"),M.exit().remove(),M.attr("d",p).attr("stroke",j),M.on("mouseover",function(a,b){d3.select(this).classed("hover",!0),o.elementMouseover({label:a.name,data:a.data,index:b,pos:[d3.mouse(this.parentNode)[0],d3.mouse(this.parentNode)[1]]})}),M.on("mouseout",function(a,b){d3.select(this).classed("hover",!1),o.elementMouseout({label:a.name,data:a.data,index:b})});var N=C.selectAll(".dimension").data(h),O=N.enter().append("g").attr("class","nv-parallelCoordinates dimension");O.append("g").attr("class","nv-parallelCoordinates nv-axis"),O.append("g").attr("class","nv-parallelCoordinates-brush"),O.append("text").attr("class","nv-parallelCoordinates nv-label"),N.attr("transform",function(a){return"translate("+f(a)+",0)"}),N.exit().remove(),N.select(".nv-label").style("cursor","move").attr("dy","-1em").attr("text-anchor","middle").text(String).on("mouseover",function(a){o.elementMouseover({dim:a,pos:[d3.mouse(this.parentNode.parentNode)[0],d3.mouse(this.parentNode.parentNode)[1]]})}).on("mouseout",function(a){o.elementMouseout({dim:a})}).call(H),N.select(".nv-axis").each(function(a,b){d3.select(this).call(G.scale(g[a]).tickFormat(d3.format(i[b])))}),N.select(".nv-parallelCoordinates-brush").each(function(a){d3.select(this).call(g[a].brush)}).selectAll("rect").attr("x",-8).attr("width",16)}),b}var c={top:30,right:0,bottom:10,left:0},d=null,e=null,f=d3.scale.ordinal(),g={},h=[],i=[],j=a.utils.defaultColor(),k=[],l=[],m=[],n=1,o=d3.dispatch("brush","elementMouseover","elementMouseout");return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},dimensionNames:{get:function(){return h},set:function(a){h=a}},dimensionFormats:{get:function(){return i},set:function(a){i=a}},lineTension:{get:function(){return n},set:function(a){n=a}},dimensions:{get:function(){return h},set:function(b){a.deprecated("dimensions","use dimensionNames instead"),h=b}},margin:{get:function(){return c},set:function(a){c.top=void 0!==a.top?a.top:c.top,c.right=void 0!==a.right?a.right:c.right,c.bottom=void 0!==a.bottom?a.bottom:c.bottom,c.left=void 0!==a.left?a.left:c.left}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.pie=function(){"use strict";function b(E){return D.reset(),E.each(function(b){function E(a,b){a.endAngle=isNaN(a.endAngle)?0:a.endAngle,a.startAngle=isNaN(a.startAngle)?0:a.startAngle,p||(a.innerRadius=0);var c=d3.interpolate(this._current,a);return this._current=c(0),function(a){return B[b](c(a))}}var F=d-c.left-c.right,G=e-c.top-c.bottom,H=Math.min(F,G)/2,I=[],J=[];if(i=d3.select(this),0===z.length)for(var K=H-H/5,L=y*H,M=0;Mc)return"carview.php?tsp=";if("function"==typeof n)d=n(a,b,{key:f(a.data),value:g(a.data),percent:k(c)});else switch(n){case"key":d=f(a.data);break;case"value":d=k(g(a.data));break;case"percent":d=d3.format("%")(c)}return d})}}),D.renderEnd("pie immediate"),b}var c={top:0,right:0,bottom:0,left:0},d=500,e=500,f=function(a){return a.x},g=function(a){return a.y},h=Math.floor(1e4*Math.random()),i=null,j=a.utils.defaultColor(),k=d3.format(",.2f"),l=!0,m=!1,n="key",o=.02,p=!1,q=!1,r=!0,s=0,t=!1,u=!1,v=!1,w=!1,x=0,y=.5,z=[],A=d3.dispatch("chartClick","elementClick","elementDblClick","elementMouseover","elementMouseout","elementMousemove","renderEnd"),B=[],C=[],D=a.utils.renderWatch(A);return b.dispatch=A,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{arcsRadius:{get:function(){return z},set:function(a){z=a}},width:{get:function(){return d},set:function(a){d=a}},height:{get:function(){return e},set:function(a){e=a}},showLabels:{get:function(){return l},set:function(a){l=a}},title:{get:function(){return q},set:function(a){q=a}},titleOffset:{get:function(){return s},set:function(a){s=a}},labelThreshold:{get:function(){return o},set:function(a){o=a}},valueFormat:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return f},set:function(a){f=a}},id:{get:function(){return h},set:function(a){h=a}},endAngle:{get:function(){return w},set:function(a){w=a}},startAngle:{get:function(){return u},set:function(a){u=a}},padAngle:{get:function(){return v},set:function(a){v=a}},cornerRadius:{get:function(){return x},set:function(a){x=a}},donutRatio:{get:function(){return y},set:function(a){y=a}},labelsOutside:{get:function(){return m},set:function(a){m=a}},labelSunbeamLayout:{get:function(){return t},set:function(a){t=a}},donut:{get:function(){return p},set:function(a){p=a}},growOnHover:{get:function(){return r},set:function(a){r=a}},pieLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("pieLabelsOutside","use labelsOutside instead")}},donutLabelsOutside:{get:function(){return m},set:function(b){m=b,a.deprecated("donutLabelsOutside","use labelsOutside instead")}},labelFormat:{get:function(){return k},set:function(b){k=b,a.deprecated("labelFormat","use valueFormat instead")}},margin:{get:function(){return c},set:function(a){c.top="undefined"!=typeof a.top?a.top:c.top,c.right="undefined"!=typeof a.right?a.right:c.right,c.bottom="undefined"!=typeof a.bottom?a.bottom:c.bottom,c.left="undefined"!=typeof a.left?a.left:c.left}},y:{get:function(){return g},set:function(a){g=d3.functor(a)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},labelType:{get:function(){return n},set:function(a){n=a||"key"}}}),a.utils.initOptions(b),b},a.models.pieChart=function(){"use strict";function b(e){return q.reset(),q.models(c),e.each(function(e){var k=d3.select(this);a.utils.initSVG(k);var n=a.utils.availableWidth(g,k,f),o=a.utils.availableHeight(h,k,f);if(b.update=function(){k.transition().call(b)},b.container=this,l.setter(s(e),b.update).getter(r(e)).update(),l.disabled=e.map(function(a){return!!a.disabled}),!m){var q;m={};for(q in l)m[q]=l[q]instanceof Array?l[q].slice(0):l[q]}if(!e||!e.length)return a.utils.noData(b,k),b;k.selectAll(".nv-noData").remove();var t=k.selectAll("g.nv-wrap.nv-pieChart").data([e]),u=t.enter().append("g").attr("class","nvd3 nv-wrap nv-pieChart").append("g"),v=t.select("g");if(u.append("g").attr("class","nv-pieWrap"),u.append("g").attr("class","nv-legendWrap"),i)if("top"===j)d.width(n).key(c.x()),t.select(".nv-legendWrap").datum(e).call(d),f.top!=d.height()&&(f.top=d.height(),o=a.utils.availableHeight(h,k,f)),t.select(".nv-legendWrap").attr("transform","translate(0,"+-f.top+")");else if("right"===j){var w=a.models.legend().width();w>n/2&&(w=n/2),d.height(o).key(c.x()),d.width(w),n-=d.width(),t.select(".nv-legendWrap").datum(e).call(d).attr("transform","translate("+n+",0)")}t.attr("transform","translate("+f.left+","+f.top+")"),c.width(n).height(o);var x=v.select(".nv-pieWrap").datum([e]);d3.transition(x).call(c),d.dispatch.on("stateChange",function(a){for(var c in a)l[c]=a[c];p.stateChange(l),b.update()}),p.on("changeState",function(a){"undefined"!=typeof a.disabled&&(e.forEach(function(b,c){b.disabled=a.disabled[c]}),l.disabled=a.disabled),b.update()})}),q.renderEnd("pieChart immediate"),b}var c=a.models.pie(),d=a.models.legend(),e=a.models.tooltip(),f={top:30,right:20,bottom:20,left:20},g=null,h=null,i=!0,j="top",k=a.utils.defaultColor(),l=a.utils.state(),m=null,n=null,o=250,p=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd");e.headerEnabled(!1).duration(0).valueFormatter(function(a,b){return c.valueFormat()(a,b)});var q=a.utils.renderWatch(p),r=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},s=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:b.x()(a.data),value:b.y()(a.data),color:a.color},e.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){e.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){e.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.legend=d,b.dispatch=p,b.pie=c,b.tooltip=e,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return i},set:function(a){i=a}},legendPosition:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return m},set:function(a){m=a}},tooltips:{get:function(){return e.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),e.enabled(!!b)}},tooltipContent:{get:function(){return e.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),e.contentGenerator(b)}},color:{get:function(){return k},set:function(a){k=a,d.color(k),c.color(k)}},duration:{get:function(){return o},set:function(a){o=a,q.reset(o)}},margin:{get:function(){return f},set:function(a){f.top=void 0!==a.top?a.top:f.top,f.right=void 0!==a.right?a.right:f.right,f.bottom=void 0!==a.bottom?a.bottom:f.bottom,f.left=void 0!==a.left?a.left:f.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.scatter=function(){"use strict";function b(N){return P.reset(),N.each(function(b){function N(){if(O=!1,!w)return!1;if(M===!0){var a=d3.merge(b.map(function(a,b){return a.values.map(function(a,c){var d=p(a,c),e=q(a,c);return[m(d)+1e-4*Math.random(),n(e)+1e-4*Math.random(),b,c,a]}).filter(function(a,b){return x(a[4],b)})}));if(0==a.length)return!1;a.length<3&&(a.push([m.range()[0]-20,n.range()[0]-20,null,null]),a.push([m.range()[1]+20,n.range()[1]+20,null,null]),a.push([m.range()[0]-20,n.range()[0]+20,null,null]),a.push([m.range()[1]+20,n.range()[1]-20,null,null]));var c=d3.geom.polygon([[-10,-10],[-10,i+10],[h+10,i+10],[h+10,-10]]),d=d3.geom.voronoi(a).map(function(b,d){return{data:c.clip(b),series:a[d][2],point:a[d][3]}});U.select(".nv-point-paths").selectAll("path").remove();var e=U.select(".nv-point-paths").selectAll("path").data(d),f=e.enter().append("svg:path").attr("d",function(a){return a&&a.data&&0!==a.data.length?"M"+a.data.join(",")+"Z":"M 0 0"}).attr("id",function(a,b){return"nv-path-"+b}).attr("clip-path",function(a,b){return"url(#nv-clip-"+b+")"});C&&f.style("fill",d3.rgb(230,230,230)).style("fill-opacity",.4).style("stroke-opacity",1).style("stroke",d3.rgb(200,200,200)),B&&(U.select(".nv-point-clips").selectAll("clipPath").remove(),U.select(".nv-point-clips").selectAll("clipPath").data(a).enter().append("svg:clipPath").attr("id",function(a,b){return"nv-clip-"+b}).append("svg:circle").attr("cx",function(a){return a[0]}).attr("cy",function(a){return a[1]}).attr("r",D));var k=function(a,c){if(O)return 0;var d=b[a.series];if(void 0!==d){var e=d.values[a.point];e.color=j(d,a.series),e.x=p(e),e.y=q(e);var f=l.node().getBoundingClientRect(),h=window.pageYOffset||document.documentElement.scrollTop,i=window.pageXOffset||document.documentElement.scrollLeft,k={left:m(p(e,a.point))+f.left+i+g.left+10,top:n(q(e,a.point))+f.top+h+g.top+10};c({point:e,series:d,pos:k,seriesIndex:a.series,pointIndex:a.point})}};e.on("click",function(a){k(a,L.elementClick)}).on("dblclick",function(a){k(a,L.elementDblClick)}).on("mouseover",function(a){k(a,L.elementMouseover)}).on("mouseout",function(a){k(a,L.elementMouseout)})}else U.select(".nv-groups").selectAll(".nv-group").selectAll(".nv-point").on("click",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("dblclick",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementDblClick({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c})}).on("mouseover",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseover({point:e,series:d,pos:[m(p(e,c))+g.left,n(q(e,c))+g.top],seriesIndex:a.series,pointIndex:c,color:j(a,c)})}).on("mouseout",function(a,c){if(O||!b[a.series])return 0;var d=b[a.series],e=d.values[c];L.elementMouseout({point:e,series:d,seriesIndex:a.series,pointIndex:c,color:j(a,c)})})}l=d3.select(this);var R=a.utils.availableWidth(h,l,g),S=a.utils.availableHeight(i,l,g);a.utils.initSVG(l),b.forEach(function(a,b){a.values.forEach(function(a){a.series=b})});var T=E&&F&&I?[]:d3.merge(b.map(function(a){return a.values.map(function(a,b){return{x:p(a,b),y:q(a,b),size:r(a,b)}})}));m.domain(E||d3.extent(T.map(function(a){return a.x}).concat(t))),m.range(y&&b[0]?G||[(R*z+R)/(2*b[0].values.length),R-R*(1+z)/(2*b[0].values.length)]:G||[0,R]),n.domain(F||d3.extent(T.map(function(a){return a.y}).concat(u))).range(H||[S,0]),o.domain(I||d3.extent(T.map(function(a){return a.size}).concat(v))).range(J||Q),K=m.domain()[0]===m.domain()[1]||n.domain()[0]===n.domain()[1],m.domain()[0]===m.domain()[1]&&m.domain(m.domain()[0]?[m.domain()[0]-.01*m.domain()[0],m.domain()[1]+.01*m.domain()[1]]:[-1,1]),n.domain()[0]===n.domain()[1]&&n.domain(n.domain()[0]?[n.domain()[0]-.01*n.domain()[0],n.domain()[1]+.01*n.domain()[1]]:[-1,1]),isNaN(m.domain()[0])&&m.domain([-1,1]),isNaN(n.domain()[0])&&n.domain([-1,1]),c=c||m,d=d||n,e=e||o;var U=l.selectAll("g.nv-wrap.nv-scatter").data([b]),V=U.enter().append("g").attr("class","nvd3 nv-wrap nv-scatter nv-chart-"+k),W=V.append("defs"),X=V.append("g"),Y=U.select("g");U.classed("nv-single-point",K),X.append("g").attr("class","nv-groups"),X.append("g").attr("class","nv-point-paths"),V.append("g").attr("class","nv-point-clips"),U.attr("transform","translate("+g.left+","+g.top+")"),W.append("clipPath").attr("id","nv-edge-clip-"+k).append("rect"),U.select("#nv-edge-clip-"+k+" rect").attr("width",R).attr("height",S>0?S:0),Y.attr("clip-path",A?"url(#nv-edge-clip-"+k+")":"carview.php?tsp="),O=!0;var Z=U.select(".nv-groups").selectAll(".nv-group").data(function(a){return a},function(a){return a.key});Z.enter().append("g").style("stroke-opacity",1e-6).style("fill-opacity",1e-6),Z.exit().remove(),Z.attr("class",function(a,b){return"nv-group nv-series-"+b}).classed("hover",function(a){return a.hover}),Z.watchTransition(P,"scatter: groups").style("fill",function(a,b){return j(a,b)}).style("stroke",function(a,b){return j(a,b)}).style("stroke-opacity",1).style("fill-opacity",.5);var $=Z.selectAll("path.nv-point").data(function(a){return a.values.map(function(a,b){return[a,b]}).filter(function(a,b){return x(a[0],b)})});$.enter().append("path").style("fill",function(a){return a.color}).style("stroke",function(a){return a.color}).attr("transform",function(a){return"translate("+c(p(a[0],a[1]))+","+d(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),$.exit().remove(),Z.exit().selectAll("path.nv-point").watchTransition(P,"scatter exit").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).remove(),$.each(function(a){d3.select(this).classed("nv-point",!0).classed("nv-point-"+a[1],!0).classed("nv-noninteractive",!w).classed("hover",!1)}),$.watchTransition(P,"scatter points").attr("transform",function(a){return"translate("+m(p(a[0],a[1]))+","+n(q(a[0],a[1]))+")"}).attr("d",a.utils.symbol().type(function(a){return s(a[0])}).size(function(a){return o(r(a[0],a[1]))})),clearTimeout(f),f=setTimeout(N,300),c=m.copy(),d=n.copy(),e=o.copy()}),P.renderEnd("scatter immediate"),b}var c,d,e,f,g={top:0,right:0,bottom:0,left:0},h=null,i=null,j=a.utils.defaultColor(),k=Math.floor(1e5*Math.random()),l=null,m=d3.scale.linear(),n=d3.scale.linear(),o=d3.scale.linear(),p=function(a){return a.x},q=function(a){return a.y},r=function(a){return a.size||1},s=function(a){return a.shape||"circle"},t=[],u=[],v=[],w=!0,x=function(a){return!a.notActive},y=!1,z=.1,A=!1,B=!0,C=!1,D=function(){return 25},E=null,F=null,G=null,H=null,I=null,J=null,K=!1,L=d3.dispatch("elementClick","elementDblClick","elementMouseover","elementMouseout","renderEnd"),M=!0,N=250,O=!1,P=a.utils.renderWatch(L,N),Q=[16,256];return b.dispatch=L,b.options=a.utils.optionsFunc.bind(b),b._calls=new function(){this.clearHighlights=function(){return a.dom.write(function(){l.selectAll(".nv-point.hover").classed("hover",!1)}),null},this.highlightPoint=function(b,c,d){a.dom.write(function(){l.select(" .nv-series-"+b+" .nv-point-"+c).classed("hover",d)})}},L.on("elementMouseover.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!0)}),L.on("elementMouseout.point",function(a){w&&b._calls.highlightPoint(a.seriesIndex,a.pointIndex,!1)}),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xScale:{get:function(){return m},set:function(a){m=a}},yScale:{get:function(){return n},set:function(a){n=a}},pointScale:{get:function(){return o},set:function(a){o=a}},xDomain:{get:function(){return E},set:function(a){E=a}},yDomain:{get:function(){return F},set:function(a){F=a}},pointDomain:{get:function(){return I},set:function(a){I=a}},xRange:{get:function(){return G},set:function(a){G=a}},yRange:{get:function(){return H},set:function(a){H=a}},pointRange:{get:function(){return J},set:function(a){J=a}},forceX:{get:function(){return t},set:function(a){t=a}},forceY:{get:function(){return u},set:function(a){u=a}},forcePoint:{get:function(){return v},set:function(a){v=a}},interactive:{get:function(){return w},set:function(a){w=a}},pointActive:{get:function(){return x},set:function(a){x=a}},padDataOuter:{get:function(){return z},set:function(a){z=a}},padData:{get:function(){return y},set:function(a){y=a}},clipEdge:{get:function(){return A},set:function(a){A=a}},clipVoronoi:{get:function(){return B},set:function(a){B=a}},clipRadius:{get:function(){return D},set:function(a){D=a}},showVoronoi:{get:function(){return C},set:function(a){C=a}},id:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return p},set:function(a){p=d3.functor(a)}},y:{get:function(){return q},set:function(a){q=d3.functor(a)}},pointSize:{get:function(){return r},set:function(a){r=d3.functor(a)}},pointShape:{get:function(){return s},set:function(a){s=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},duration:{get:function(){return N},set:function(a){N=a,P.reset(N)}},color:{get:function(){return j},set:function(b){j=a.utils.getColor(b)}},useVoronoi:{get:function(){return M},set:function(a){M=a,M===!1&&(B=!1)}}}),a.utils.initOptions(b),b},a.models.scatterChart=function(){"use strict";function b(z){return D.reset(),D.models(c),t&&D.models(d),u&&D.models(e),q&&D.models(g),r&&D.models(h),z.each(function(z){m=d3.select(this),a.utils.initSVG(m);var G=a.utils.availableWidth(k,m,j),H=a.utils.availableHeight(l,m,j);if(b.update=function(){0===A?m.call(b):m.transition().duration(A).call(b)},b.container=this,w.setter(F(z),b.update).getter(E(z)).update(),w.disabled=z.map(function(a){return!!a.disabled}),!x){var I;x={};for(I in w)x[I]=w[I]instanceof Array?w[I].slice(0):w[I]}if(!(z&&z.length&&z.filter(function(a){return a.values.length}).length))return a.utils.noData(b,m),D.renderEnd("scatter immediate"),b;m.selectAll(".nv-noData").remove(),o=c.xScale(),p=c.yScale();var J=m.selectAll("g.nv-wrap.nv-scatterChart").data([z]),K=J.enter().append("g").attr("class","nvd3 nv-wrap nv-scatterChart nv-chart-"+c.id()),L=K.append("g"),M=J.select("g");if(L.append("rect").attr("class","nvd3 nv-background").style("pointer-events","none"),L.append("g").attr("class","nv-x nv-axis"),L.append("g").attr("class","nv-y nv-axis"),L.append("g").attr("class","nv-scatterWrap"),L.append("g").attr("class","nv-regressionLinesWrap"),L.append("g").attr("class","nv-distWrap"),L.append("g").attr("class","nv-legendWrap"),v&&M.select(".nv-y.nv-axis").attr("transform","translate("+G+",0)"),s){var N=G;f.width(N),J.select(".nv-legendWrap").datum(z).call(f),j.top!=f.height()&&(j.top=f.height(),H=a.utils.availableHeight(l,m,j)),J.select(".nv-legendWrap").attr("transform","translate(0,"+-j.top+")")}J.attr("transform","translate("+j.left+","+j.top+")"),c.width(G).height(H).color(z.map(function(a,b){return a.color=a.color||n(a,b),a.color}).filter(function(a,b){return!z[b].disabled})),J.select(".nv-scatterWrap").datum(z.filter(function(a){return!a.disabled})).call(c),J.select(".nv-regressionLinesWrap").attr("clip-path","url(#nv-edge-clip-"+c.id()+")");var O=J.select(".nv-regressionLinesWrap").selectAll(".nv-regLines").data(function(a){return a});O.enter().append("g").attr("class","nv-regLines");var P=O.selectAll(".nv-regLine").data(function(a){return[a]});P.enter().append("line").attr("class","nv-regLine").style("stroke-opacity",0),P.filter(function(a){return a.intercept&&a.slope}).watchTransition(D,"scatterPlusLineChart: regline").attr("x1",o.range()[0]).attr("x2",o.range()[1]).attr("y1",function(a){return p(o.domain()[0]*a.slope+a.intercept)}).attr("y2",function(a){return p(o.domain()[1]*a.slope+a.intercept)}).style("stroke",function(a,b,c){return n(a,c)}).style("stroke-opacity",function(a){return a.disabled||"undefined"==typeof a.slope||"undefined"==typeof a.intercept?0:1}),t&&(d.scale(o)._ticks(a.utils.calcTicksX(G/100,z)).tickSize(-H,0),M.select(".nv-x.nv-axis").attr("transform","translate(0,"+p.range()[0]+")").call(d)),u&&(e.scale(p)._ticks(a.utils.calcTicksY(H/36,z)).tickSize(-G,0),M.select(".nv-y.nv-axis").call(e)),q&&(g.getData(c.x()).scale(o).width(G).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionX"),M.select(".nv-distributionX").attr("transform","translate(0,"+p.range()[0]+")").datum(z.filter(function(a){return!a.disabled})).call(g)),r&&(h.getData(c.y()).scale(p).width(H).color(z.map(function(a,b){return a.color||n(a,b)}).filter(function(a,b){return!z[b].disabled})),L.select(".nv-distWrap").append("g").attr("class","nv-distributionY"),M.select(".nv-distributionY").attr("transform","translate("+(v?G:-h.size())+",0)").datum(z.filter(function(a){return!a.disabled})).call(h)),f.dispatch.on("stateChange",function(a){for(var c in a)w[c]=a[c];y.stateChange(w),b.update()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&(z.forEach(function(b,c){b.disabled=a.disabled[c]}),w.disabled=a.disabled),b.update()}),c.dispatch.on("elementMouseout.tooltip",function(a){i.hidden(!0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",0),m.select(".nv-chart-"+c.id()+" .nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",h.size())}),c.dispatch.on("elementMouseover.tooltip",function(a){m.select(".nv-series-"+a.seriesIndex+" .nv-distx-"+a.pointIndex).attr("y1",a.pos.top-H-j.top),m.select(".nv-series-"+a.seriesIndex+" .nv-disty-"+a.pointIndex).attr("x2",a.pos.left+g.size()-j.left),i.position(a.pos).data(a).hidden(!1)}),B=o.copy(),C=p.copy()}),D.renderEnd("scatter with line immediate"),b}var c=a.models.scatter(),d=a.models.axis(),e=a.models.axis(),f=a.models.legend(),g=a.models.distribution(),h=a.models.distribution(),i=a.models.tooltip(),j={top:30,right:20,bottom:50,left:75},k=null,l=null,m=null,n=a.utils.defaultColor(),o=c.xScale(),p=c.yScale(),q=!1,r=!1,s=!0,t=!0,u=!0,v=!1,w=a.utils.state(),x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=null,A=250;c.xScale(o).yScale(p),d.orient("bottom").tickPadding(10),e.orient(v?"right":"left").tickPadding(10),g.axis("x"),h.axis("y"),i.headerFormatter(function(a,b){return d.tickFormat()(a,b)}).valueFormatter(function(a,b){return e.tickFormat()(a,b)});var B,C,D=a.utils.renderWatch(y,A),E=function(a){return function(){return{active:a.map(function(a){return!a.disabled})}}},F=function(a){return function(b){void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}};return b.dispatch=y,b.scatter=c,b.legend=f,b.xAxis=d,b.yAxis=e,b.distX=g,b.distY=h,b.tooltip=i,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return k},set:function(a){k=a}},height:{get:function(){return l},set:function(a){l=a}},container:{get:function(){return m},set:function(a){m=a}},showDistX:{get:function(){return q},set:function(a){q=a}},showDistY:{get:function(){return r},set:function(a){r=a}},showLegend:{get:function(){return s},set:function(a){s=a}},showXAxis:{get:function(){return t},set:function(a){t=a}},showYAxis:{get:function(){return u},set:function(a){u=a}},defaultState:{get:function(){return x},set:function(a){x=a}},noData:{get:function(){return z},set:function(a){z=a}},duration:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return i.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),i.enabled(!!b) }},tooltipContent:{get:function(){return i.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),i.contentGenerator(b)}},tooltipXContent:{get:function(){return i.contentGenerator()},set:function(){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},tooltipYContent:{get:function(){return i.contentGenerator()},set:function(){a.deprecated("tooltipContent","This option is removed, put values into main tooltip.")}},margin:{get:function(){return j},set:function(a){j.top=void 0!==a.top?a.top:j.top,j.right=void 0!==a.right?a.right:j.right,j.bottom=void 0!==a.bottom?a.bottom:j.bottom,j.left=void 0!==a.left?a.left:j.left}},rightAlignYAxis:{get:function(){return v},set:function(a){v=a,e.orient(a?"right":"left")}},color:{get:function(){return n},set:function(b){n=a.utils.getColor(b),f.color(n),g.color(n),h.color(n)}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.models.sparkline=function(){"use strict";function b(k){return k.each(function(b){var k=h-g.left-g.right,q=i-g.top-g.bottom;j=d3.select(this),a.utils.initSVG(j),l.domain(c||d3.extent(b,n)).range(e||[0,k]),m.domain(d||d3.extent(b,o)).range(f||[q,0]);{var r=j.selectAll("g.nv-wrap.nv-sparkline").data([b]),s=r.enter().append("g").attr("class","nvd3 nv-wrap nv-sparkline");s.append("g"),r.select("g")}r.attr("transform","translate("+g.left+","+g.top+")");var t=r.selectAll("path").data(function(a){return[a]});t.enter().append("path"),t.exit().remove(),t.style("stroke",function(a,b){return a.color||p(a,b)}).attr("d",d3.svg.line().x(function(a,b){return l(n(a,b))}).y(function(a,b){return m(o(a,b))}));var u=r.selectAll("circle.nv-point").data(function(a){function b(b){if(-1!=b){var c=a[b];return c.pointIndex=b,c}return null}var c=a.map(function(a,b){return o(a,b)}),d=b(c.lastIndexOf(m.domain()[1])),e=b(c.indexOf(m.domain()[0])),f=b(c.length-1);return[e,d,f].filter(function(a){return null!=a})});u.enter().append("circle"),u.exit().remove(),u.attr("cx",function(a){return l(n(a,a.pointIndex))}).attr("cy",function(a){return m(o(a,a.pointIndex))}).attr("r",2).attr("class",function(a){return n(a,a.pointIndex)==l.domain()[1]?"nv-point nv-currentValue":o(a,a.pointIndex)==m.domain()[0]?"nv-point nv-minValue":"nv-point nv-maxValue"})}),b}var c,d,e,f,g={top:2,right:0,bottom:2,left:0},h=400,i=32,j=null,k=!0,l=d3.scale.linear(),m=d3.scale.linear(),n=function(a){return a.x},o=function(a){return a.y},p=a.utils.getColor(["#000"]);return b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return h},set:function(a){h=a}},height:{get:function(){return i},set:function(a){i=a}},xDomain:{get:function(){return c},set:function(a){c=a}},yDomain:{get:function(){return d},set:function(a){d=a}},xRange:{get:function(){return e},set:function(a){e=a}},yRange:{get:function(){return f},set:function(a){f=a}},xScale:{get:function(){return l},set:function(a){l=a}},yScale:{get:function(){return m},set:function(a){m=a}},animate:{get:function(){return k},set:function(a){k=a}},x:{get:function(){return n},set:function(a){n=d3.functor(a)}},y:{get:function(){return o},set:function(a){o=d3.functor(a)}},margin:{get:function(){return g},set:function(a){g.top=void 0!==a.top?a.top:g.top,g.right=void 0!==a.right?a.right:g.right,g.bottom=void 0!==a.bottom?a.bottom:g.bottom,g.left=void 0!==a.left?a.left:g.left}},color:{get:function(){return p},set:function(b){p=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sparklinePlus=function(){"use strict";function b(p){return p.each(function(p){function q(){if(!j){var a=z.selectAll(".nv-hoverValue").data(i),b=a.enter().append("g").attr("class","nv-hoverValue").style("stroke-opacity",0).style("fill-opacity",0);a.exit().transition().duration(250).style("stroke-opacity",0).style("fill-opacity",0).remove(),a.attr("transform",function(a){return"translate("+c(e.x()(p[a],a))+",0)"}).transition().duration(250).style("stroke-opacity",1).style("fill-opacity",1),i.length&&(b.append("line").attr("x1",0).attr("y1",-f.top).attr("x2",0).attr("y2",u),b.append("text").attr("class","nv-xValue").attr("x",-6).attr("y",-f.top).attr("text-anchor","end").attr("dy",".9em"),z.select(".nv-hoverValue .nv-xValue").text(k(e.x()(p[i[0]],i[0]))),b.append("text").attr("class","nv-yValue").attr("x",6).attr("y",-f.top).attr("text-anchor","start").attr("dy",".9em"),z.select(".nv-hoverValue .nv-yValue").text(l(e.y()(p[i[0]],i[0]))))}}function r(){function a(a,b){for(var c=Math.abs(e.x()(a[0],0)-b),d=0,f=0;fc;++c){for(b=0,d=0;bb;b++)a[b][c][1]/=d;else for(b=0;e>b;b++)a[b][c][1]=0}for(c=0;f>c;++c)g[c]=0;return g}}),u.renderEnd("stackedArea immediate"),b}var c,d,e={top:0,right:0,bottom:0,left:0},f=960,g=500,h=a.utils.defaultColor(),i=Math.floor(1e5*Math.random()),j=null,k=function(a){return a.x},l=function(a){return a.y},m="stack",n="zero",o="default",p="linear",q=!1,r=a.models.scatter(),s=250,t=d3.dispatch("areaClick","areaMouseover","areaMouseout","renderEnd","elementClick","elementMouseover","elementMouseout");r.pointSize(2.2).pointDomain([2.2,2.2]);var u=a.utils.renderWatch(t,s);return b.dispatch=t,b.scatter=r,r.dispatch.on("elementClick",function(){t.elementClick.apply(this,arguments)}),r.dispatch.on("elementMouseover",function(){t.elementMouseover.apply(this,arguments)}),r.dispatch.on("elementMouseout",function(){t.elementMouseout.apply(this,arguments)}),b.interpolate=function(a){return arguments.length?(p=a,b):p},b.duration=function(a){return arguments.length?(s=a,u.reset(s),r.duration(s),b):s},b.dispatch=t,b.scatter=r,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return f},set:function(a){f=a}},height:{get:function(){return g},set:function(a){g=a}},clipEdge:{get:function(){return q},set:function(a){q=a}},offset:{get:function(){return n},set:function(a){n=a}},order:{get:function(){return o},set:function(a){o=a}},interpolate:{get:function(){return p},set:function(a){p=a}},x:{get:function(){return k},set:function(a){k=d3.functor(a)}},y:{get:function(){return l},set:function(a){l=d3.functor(a)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}},color:{get:function(){return h},set:function(b){h=a.utils.getColor(b)}},style:{get:function(){return m},set:function(a){switch(m=a){case"stack":b.offset("zero"),b.order("default");break;case"stream":b.offset("wiggle"),b.order("inside-out");break;case"stream-center":b.offset("silhouette"),b.order("inside-out");break;case"expand":b.offset("expand"),b.order("default");break;case"stack_percent":b.offset(b.d3_stackedOffset_stackPercent),b.order("default")}}},duration:{get:function(){return s},set:function(a){s=a,u.reset(s),r.duration(s)}}}),a.utils.inheritOptions(b,r),a.utils.initOptions(b),b},a.models.stackedAreaChart=function(){"use strict";function b(k){return F.reset(),F.models(e),r&&F.models(f),s&&F.models(g),k.each(function(k){var x=d3.select(this),F=this;a.utils.initSVG(x);var K=a.utils.availableWidth(m,x,l),L=a.utils.availableHeight(n,x,l);if(b.update=function(){x.transition().duration(C).call(b)},b.container=this,v.setter(I(k),b.update).getter(H(k)).update(),v.disabled=k.map(function(a){return!!a.disabled}),!w){var M;w={};for(M in v)w[M]=v[M]instanceof Array?v[M].slice(0):v[M]}if(!(k&&k.length&&k.filter(function(a){return a.values.length}).length))return a.utils.noData(b,x),b;x.selectAll(".nv-noData").remove(),c=e.xScale(),d=e.yScale();var N=x.selectAll("g.nv-wrap.nv-stackedAreaChart").data([k]),O=N.enter().append("g").attr("class","nvd3 nv-wrap nv-stackedAreaChart").append("g"),P=N.select("g");if(O.append("rect").style("opacity",0),O.append("g").attr("class","nv-x nv-axis"),O.append("g").attr("class","nv-y nv-axis"),O.append("g").attr("class","nv-stackedWrap"),O.append("g").attr("class","nv-legendWrap"),O.append("g").attr("class","nv-controlsWrap"),O.append("g").attr("class","nv-interactive"),P.select("rect").attr("width",K).attr("height",L),q){var Q=p?K-z:K;h.width(Q),P.select(".nv-legendWrap").datum(k).call(h),l.top!=h.height()&&(l.top=h.height(),L=a.utils.availableHeight(n,x,l)),P.select(".nv-legendWrap").attr("transform","translate("+(K-Q)+","+-l.top+")")}if(p){var R=[{key:B.stacked||"Stacked",metaKey:"Stacked",disabled:"stack"!=e.style(),style:"stack"},{key:B.stream||"Stream",metaKey:"Stream",disabled:"stream"!=e.style(),style:"stream"},{key:B.expanded||"Expanded",metaKey:"Expanded",disabled:"expand"!=e.style(),style:"expand"},{key:B.stack_percent||"Stack %",metaKey:"Stack_Percent",disabled:"stack_percent"!=e.style(),style:"stack_percent"}];z=A.length/3*260,R=R.filter(function(a){return-1!==A.indexOf(a.metaKey)}),i.width(z).color(["#444","#444","#444"]),P.select(".nv-controlsWrap").datum(R).call(i),l.top!=Math.max(i.height(),h.height())&&(l.top=Math.max(i.height(),h.height()),L=a.utils.availableHeight(n,x,l)),P.select(".nv-controlsWrap").attr("transform","translate(0,"+-l.top+")")}N.attr("transform","translate("+l.left+","+l.top+")"),t&&P.select(".nv-y.nv-axis").attr("transform","translate("+K+",0)"),u&&(j.width(K).height(L).margin({left:l.left,top:l.top}).svgContainer(x).xScale(c),N.select(".nv-interactive").call(j)),e.width(K).height(L);var S=P.select(".nv-stackedWrap").datum(k);if(S.transition().call(e),r&&(f.scale(c)._ticks(a.utils.calcTicksX(K/100,k)).tickSize(-L,0),P.select(".nv-x.nv-axis").attr("transform","translate(0,"+L+")"),P.select(".nv-x.nv-axis").transition().duration(0).call(f)),s){var T;if(T="wiggle"===e.offset()?0:a.utils.calcTicksY(L/36,k),g.scale(d)._ticks(T).tickSize(-K,0),"expand"===e.style()||"stack_percent"===e.style()){var U=g.tickFormat();D&&U===J||(D=U),g.tickFormat(J)}else D&&(g.tickFormat(D),D=null);P.select(".nv-y.nv-axis").transition().duration(0).call(g)}e.dispatch.on("areaClick.toggle",function(a){k.forEach(1===k.filter(function(a){return!a.disabled}).length?function(a){a.disabled=!1}:function(b,c){b.disabled=c!=a.seriesIndex}),v.disabled=k.map(function(a){return!!a.disabled}),y.stateChange(v),b.update()}),h.dispatch.on("stateChange",function(a){for(var c in a)v[c]=a[c];y.stateChange(v),b.update()}),i.dispatch.on("legendClick",function(a){a.disabled&&(R=R.map(function(a){return a.disabled=!0,a}),a.disabled=!1,e.style(a.style),v.style=e.style(),y.stateChange(v),b.update())}),j.dispatch.on("elementMousemove",function(c){e.clearHighlights();var d,g,h,i=[];if(k.filter(function(a,b){return a.seriesIndex=b,!a.disabled}).forEach(function(f,j){g=a.interactiveBisect(f.values,c.pointXValue,b.x());var k=f.values[g],l=b.y()(k,g);if(null!=l&&e.highlightPoint(j,g,!0),"undefined"!=typeof k){"undefined"==typeof d&&(d=k),"undefined"==typeof h&&(h=b.xScale()(b.x()(k,g)));var m="expand"==e.style()?k.display.y:b.y()(k,g);i.push({key:f.key,value:m,color:o(f,f.seriesIndex),stackedValue:k.display})}}),i.reverse(),i.length>2){var m=b.yScale().invert(c.mouseY),n=null;i.forEach(function(a,b){m=Math.abs(m);var c=Math.abs(a.stackedValue.y0),d=Math.abs(a.stackedValue.y);return m>=c&&d+c>=m?void(n=b):void 0}),null!=n&&(i[n].highlight=!0)}var p=f.tickFormat()(b.x()(d,g)),q=j.tooltip.valueFormatter();"expand"===e.style()||"stack_percent"===e.style()?(E||(E=q),q=d3.format(".1%")):E&&(q=E,E=null),j.tooltip.position({left:h+l.left,top:c.mouseY+l.top}).chartContainer(F.parentNode).valueFormatter(q).data({value:p,series:i})(),j.renderGuideLine(h)}),j.dispatch.on("elementMouseout",function(){e.clearHighlights()}),y.on("changeState",function(a){"undefined"!=typeof a.disabled&&k.length===a.disabled.length&&(k.forEach(function(b,c){b.disabled=a.disabled[c]}),v.disabled=a.disabled),"undefined"!=typeof a.style&&(e.style(a.style),G=a.style),b.update()})}),F.renderEnd("stacked Area chart immediate"),b}var c,d,e=a.models.stackedArea(),f=a.models.axis(),g=a.models.axis(),h=a.models.legend(),i=a.models.legend(),j=a.interactiveGuideline(),k=a.models.tooltip(),l={top:30,right:25,bottom:50,left:60},m=null,n=null,o=a.utils.defaultColor(),p=!0,q=!0,r=!0,s=!0,t=!1,u=!1,v=a.utils.state(),w=null,x=null,y=d3.dispatch("stateChange","changeState","renderEnd"),z=250,A=["Stacked","Stream","Expanded"],B={},C=250;v.style=e.style(),f.orient("bottom").tickPadding(7),g.orient(t?"right":"left"),k.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)}),j.tooltip.headerFormatter(function(a,b){return f.tickFormat()(a,b)}).valueFormatter(function(a,b){return g.tickFormat()(a,b)});var D=null,E=null;i.updateState(!1);var F=a.utils.renderWatch(y),G=e.style(),H=function(a){return function(){return{active:a.map(function(a){return!a.disabled}),style:e.style()}}},I=function(a){return function(b){void 0!==b.style&&(G=b.style),void 0!==b.active&&a.forEach(function(a,c){a.disabled=!b.active[c]})}},J=d3.format("%");return e.dispatch.on("elementMouseover.tooltip",function(a){a.point.x=e.x()(a.point),a.point.y=e.y()(a.point),k.data(a).position(a.pos).hidden(!1)}),e.dispatch.on("elementMouseout.tooltip",function(){k.hidden(!0)}),b.dispatch=y,b.stacked=e,b.legend=h,b.controls=i,b.xAxis=f,b.yAxis=g,b.interactiveLayer=j,b.tooltip=k,b.dispatch=y,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return m},set:function(a){m=a}},height:{get:function(){return n},set:function(a){n=a}},showLegend:{get:function(){return q},set:function(a){q=a}},showXAxis:{get:function(){return r},set:function(a){r=a}},showYAxis:{get:function(){return s},set:function(a){s=a}},defaultState:{get:function(){return w},set:function(a){w=a}},noData:{get:function(){return x},set:function(a){x=a}},showControls:{get:function(){return p},set:function(a){p=a}},controlLabels:{get:function(){return B},set:function(a){B=a}},controlOptions:{get:function(){return A},set:function(a){A=a}},tooltips:{get:function(){return k.enabled()},set:function(b){a.deprecated("tooltips","use chart.tooltip.enabled() instead"),k.enabled(!!b)}},tooltipContent:{get:function(){return k.contentGenerator()},set:function(b){a.deprecated("tooltipContent","use chart.tooltip.contentGenerator() instead"),k.contentGenerator(b)}},margin:{get:function(){return l},set:function(a){l.top=void 0!==a.top?a.top:l.top,l.right=void 0!==a.right?a.right:l.right,l.bottom=void 0!==a.bottom?a.bottom:l.bottom,l.left=void 0!==a.left?a.left:l.left}},duration:{get:function(){return C},set:function(a){C=a,F.reset(C),e.duration(C),f.duration(C),g.duration(C)}},color:{get:function(){return o},set:function(b){o=a.utils.getColor(b),h.color(o),e.color(o)}},rightAlignYAxis:{get:function(){return t},set:function(a){t=a,g.orient(t?"right":"left")}},useInteractiveGuideline:{get:function(){return u},set:function(a){u=!!a,b.interactive(!a),b.useVoronoi(!a),e.scatter.interactive(!a)}}}),a.utils.inheritOptions(b,e),a.utils.initOptions(b),b},a.models.sunburst=function(){"use strict";function b(u){return t.reset(),u.each(function(b){function t(a){a.x0=a.x,a.dx0=a.dx}function u(a){var b=d3.interpolate(p.domain(),[a.x,a.x+a.dx]),c=d3.interpolate(q.domain(),[a.y,1]),d=d3.interpolate(q.range(),[a.y?20:0,y]);return function(a,e){return e?function(){return s(a)}:function(e){return p.domain(b(e)),q.domain(c(e)).range(d(e)),s(a)}}}l=d3.select(this);var v,w=a.utils.availableWidth(g,l,f),x=a.utils.availableHeight(h,l,f),y=Math.min(w,x)/2;a.utils.initSVG(l);var z=l.selectAll(".nv-wrap.nv-sunburst").data(b),A=z.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburst nv-chart-"+k),B=A.selectAll("nv-sunburst");z.attr("transform","translate("+w/2+","+x/2+")"),l.on("click",function(a,b){o.chartClick({data:a,index:b,pos:d3.event,id:k})}),q.range([0,y]),c=c||b,e=b[0],r.value(j[i]||j.count),v=B.data(r.nodes).enter().append("path").attr("d",s).style("fill",function(a){return m((a.children?a:a.parent).name)}).style("stroke","#FFF").on("click",function(a){d!==c&&c!==a&&(d=c),c=a,v.transition().duration(n).attrTween("d",u(a))}).each(t).on("dblclick",function(a){d.parent==a&&v.transition().duration(n).attrTween("d",u(e))}).each(t).on("mouseover",function(a){d3.select(this).classed("hover",!0).style("opacity",.8),o.elementMouseover({data:a,color:d3.select(this).style("fill")})}).on("mouseout",function(a){d3.select(this).classed("hover",!1).style("opacity",1),o.elementMouseout({data:a})}).on("mousemove",function(a){o.elementMousemove({data:a})})}),t.renderEnd("sunburst immediate"),b}var c,d,e,f={top:0,right:0,bottom:0,left:0},g=null,h=null,i="count",j={count:function(){return 1},size:function(a){return a.size}},k=Math.floor(1e4*Math.random()),l=null,m=a.utils.defaultColor(),n=500,o=d3.dispatch("chartClick","elementClick","elementDblClick","elementMousemove","elementMouseover","elementMouseout","renderEnd"),p=d3.scale.linear().range([0,2*Math.PI]),q=d3.scale.sqrt(),r=d3.layout.partition().sort(null).value(function(){return 1}),s=d3.svg.arc().startAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x)))}).endAngle(function(a){return Math.max(0,Math.min(2*Math.PI,p(a.x+a.dx)))}).innerRadius(function(a){return Math.max(0,q(a.y))}).outerRadius(function(a){return Math.max(0,q(a.y+a.dy))}),t=a.utils.renderWatch(o);return b.dispatch=o,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{width:{get:function(){return g},set:function(a){g=a}},height:{get:function(){return h},set:function(a){h=a}},mode:{get:function(){return i},set:function(a){i=a}},id:{get:function(){return k},set:function(a){k=a}},duration:{get:function(){return n},set:function(a){n=a}},margin:{get:function(){return f},set:function(a){f.top=void 0!=a.top?a.top:f.top,f.right=void 0!=a.right?a.right:f.right,f.bottom=void 0!=a.bottom?a.bottom:f.bottom,f.left=void 0!=a.left?a.left:f.left}},color:{get:function(){return m},set:function(b){m=a.utils.getColor(b)}}}),a.utils.initOptions(b),b},a.models.sunburstChart=function(){"use strict";function b(d){return m.reset(),m.models(c),d.each(function(d){var h=d3.select(this);a.utils.initSVG(h);var i=a.utils.availableWidth(f,h,e),j=a.utils.availableHeight(g,h,e);if(b.update=function(){0===k?h.call(b):h.transition().duration(k).call(b)},b.container=this,!d||!d.length)return a.utils.noData(b,h),b;h.selectAll(".nv-noData").remove();var l=h.selectAll("g.nv-wrap.nv-sunburstChart").data(d),m=l.enter().append("g").attr("class","nvd3 nv-wrap nv-sunburstChart").append("g"),n=l.select("g");m.append("g").attr("class","nv-sunburstWrap"),l.attr("transform","translate("+e.left+","+e.top+")"),c.width(i).height(j);var o=n.select(".nv-sunburstWrap").datum(d);d3.transition(o).call(c)}),m.renderEnd("sunburstChart immediate"),b}var c=a.models.sunburst(),d=a.models.tooltip(),e={top:30,right:20,bottom:20,left:20},f=null,g=null,h=a.utils.defaultColor(),i=(Math.round(1e5*Math.random()),null),j=null,k=250,l=d3.dispatch("tooltipShow","tooltipHide","stateChange","changeState","renderEnd"),m=a.utils.renderWatch(l);return d.headerEnabled(!1).duration(0).valueFormatter(function(a){return a}),c.dispatch.on("elementMouseover.tooltip",function(a){a.series={key:a.data.name,value:a.data.size,color:a.color},d.data(a).hidden(!1)}),c.dispatch.on("elementMouseout.tooltip",function(){d.hidden(!0)}),c.dispatch.on("elementMousemove.tooltip",function(){d.position({top:d3.event.pageY,left:d3.event.pageX})()}),b.dispatch=l,b.sunburst=c,b.tooltip=d,b.options=a.utils.optionsFunc.bind(b),b._options=Object.create({},{noData:{get:function(){return j},set:function(a){j=a}},defaultState:{get:function(){return i},set:function(a){i=a}},color:{get:function(){return h},set:function(a){h=a,c.color(h)}},duration:{get:function(){return k},set:function(a){k=a,m.reset(k),c.duration(k)}},margin:{get:function(){return e},set:function(a){e.top=void 0!==a.top?a.top:e.top,e.right=void 0!==a.right?a.right:e.right,e.bottom=void 0!==a.bottom?a.bottom:e.bottom,e.left=void 0!==a.left?a.left:e.left}}}),a.utils.inheritOptions(b,c),a.utils.initOptions(b),b},a.version="1.8.1"}();/* Copyright (C) Federico Zivolo 2019 Distributed under the MIT License (license terms are at https://opensource.org/licenses/MIT). */(function(e,t){'object'==typeof exports&&'undefined'!=typeof module?module.exports=t():'function'==typeof define&&define.amd?define(t):e.Popper=t()})(this,function(){'use strict';function e(e){return e&&'[object Function]'==={}.toString.call(e)}function t(e,t){if(1!==e.nodeType)return[];var o=e.ownerDocument.defaultView,n=o.getComputedStyle(e,null);return t?n[t]:n}function o(e){return'HTML'===e.nodeName?e:e.parentNode||e.host}function n(e){if(!e)return document.body;switch(e.nodeName){case'HTML':case'BODY':return e.ownerDocument.body;case'#document':return e.body;}var i=t(e),r=i.overflow,p=i.overflowX,s=i.overflowY;return /(auto|scroll|overlay)/.test(r+s+p)?e:n(o(e))}function r(e){return 11===e?pe:10===e?se:pe||se}function p(e){if(!e)return document.documentElement;for(var o=r(10)?document.body:null,n=e.offsetParent||null;n===o&&e.nextElementSibling;)n=(e=e.nextElementSibling).offsetParent;var i=n&&n.nodeName;return i&&'BODY'!==i&&'HTML'!==i?-1!==['TH','TD','TABLE'].indexOf(n.nodeName)&&'static'===t(n,'position')?p(n):n:e?e.ownerDocument.documentElement:document.documentElement}function s(e){var t=e.nodeName;return'BODY'!==t&&('HTML'===t||p(e.firstElementChild)===e)}function d(e){return null===e.parentNode?e:d(e.parentNode)}function a(e,t){if(!e||!e.nodeType||!t||!t.nodeType)return document.documentElement;var o=e.compareDocumentPosition(t)&Node.DOCUMENT_POSITION_FOLLOWING,n=o?e:t,i=o?t:e,r=document.createRange();r.setStart(n,0),r.setEnd(i,0);var l=r.commonAncestorContainer;if(e!==l&&t!==l||n.contains(i))return s(l)?l:p(l);var f=d(e);return f.host?a(f.host,t):a(e,d(t).host)}function l(e){var t=1=o.clientWidth&&n>=o.clientHeight}),l=0a[e]&&!t.escapeWithReference&&(n=Q(f[o],a[e]-('right'===e?f.width:f.height))),le({},o,n)}};return l.forEach(function(e){var t=-1===['left','top'].indexOf(e)?'secondary':'primary';f=fe({},f,m[t](e))}),e.offsets.popper=f,e},priority:['left','right','top','bottom'],padding:5,boundariesElement:'scrollParent'},keepTogether:{order:400,enabled:!0,fn:function(e){var t=e.offsets,o=t.popper,n=t.reference,i=e.placement.split('-')[0],r=Z,p=-1!==['top','bottom'].indexOf(i),s=p?'right':'bottom',d=p?'left':'top',a=p?'width':'height';return o[s]r(n[s])&&(e.offsets.popper[d]=r(n[s])),e}},arrow:{order:500,enabled:!0,fn:function(e,o){var n;if(!K(e.instance.modifiers,'arrow','keepTogether'))return e;var i=o.element;if('string'==typeof i){if(i=e.instance.popper.querySelector(i),!i)return e;}else if(!e.instance.popper.contains(i))return console.warn('WARNING: `arrow.element` must be child of its popper element!'),e;var r=e.placement.split('-')[0],p=e.offsets,s=p.popper,d=p.reference,a=-1!==['left','right'].indexOf(r),l=a?'height':'width',f=a?'Top':'Left',m=f.toLowerCase(),h=a?'left':'top',c=a?'bottom':'right',u=S(i)[l];d[c]-us[c]&&(e.offsets.popper[m]+=d[m]+u-s[c]),e.offsets.popper=g(e.offsets.popper);var b=d[m]+d[l]/2-u/2,w=t(e.instance.popper),y=parseFloat(w['margin'+f],10),E=parseFloat(w['border'+f+'Width'],10),v=b-e.offsets.popper[m]-y-E;return v=ee(Q(s[l]-u,v),0),e.arrowElement=i,e.offsets.arrow=(n={},le(n,m,$(v)),le(n,h,''),n),e},element:'[x-arrow]'},flip:{order:600,enabled:!0,fn:function(e,t){if(W(e.instance.modifiers,'inner'))return e;if(e.flipped&&e.placement===e.originalPlacement)return e;var o=v(e.instance.popper,e.instance.reference,t.padding,t.boundariesElement,e.positionFixed),n=e.placement.split('-')[0],i=T(n),r=e.placement.split('-')[1]||'',p=[];switch(t.behavior){case ge.FLIP:p=[n,i];break;case ge.CLOCKWISE:p=G(n);break;case ge.COUNTERCLOCKWISE:p=G(n,!0);break;default:p=t.behavior;}return p.forEach(function(s,d){if(n!==s||p.length===d+1)return e;n=e.placement.split('-')[0],i=T(n);var a=e.offsets.popper,l=e.offsets.reference,f=Z,m='left'===n&&f(a.right)>f(l.left)||'right'===n&&f(a.left)f(l.top)||'bottom'===n&&f(a.top)f(o.right),g=f(a.top)f(o.bottom),b='left'===n&&h||'right'===n&&c||'top'===n&&g||'bottom'===n&&u,w=-1!==['top','bottom'].indexOf(n),y=!!t.flipVariations&&(w&&'start'===r&&h||w&&'end'===r&&c||!w&&'start'===r&&g||!w&&'end'===r&&u),E=!!t.flipVariationsByContent&&(w&&'start'===r&&c||w&&'end'===r&&h||!w&&'start'===r&&u||!w&&'end'===r&&g),v=y||E;(m||b||v)&&(e.flipped=!0,(m||b)&&(n=p[d+1]),v&&(r=z(r)),e.placement=n+(r?'-'+r:''),e.offsets.popper=fe({},e.offsets.popper,C(e.instance.popper,e.offsets.reference,e.placement)),e=P(e.instance.modifiers,e,'flip'))}),e},behavior:'flip',padding:5,boundariesElement:'viewport',flipVariations:!1,flipVariationsByContent:!1},inner:{order:700,enabled:!1,fn:function(e){var t=e.placement,o=t.split('-')[0],n=e.offsets,i=n.popper,r=n.reference,p=-1!==['left','right'].indexOf(o),s=-1===['top','left'].indexOf(o);return i[p?'left':'top']=r[o]-(s?i[p?'width':'height']:0),e.placement=T(t),e.offsets.popper=g(i),e}},hide:{order:800,enabled:!0,fn:function(e){if(!K(e.instance.modifiers,'hide','preventOverflow'))return e;var t=e.offsets.reference,o=D(e.instance.modifiers,function(e){return'preventOverflow'===e.name}).boundaries;if(t.bottomo.right||t.top>o.bottom||t.rightwindow.devicePixelRatio||!me),c='bottom'===o?'top':'bottom',g='right'===n?'left':'right',b=B('transform');if(d='bottom'==c?'HTML'===l.nodeName?-l.clientHeight+h.bottom:-f.height+h.bottom:h.top,s='right'==g?'HTML'===l.nodeName?-l.clientWidth+h.right:-f.width+h.right:h.left,a&&b)m[b]='translate3d('+s+'px, '+d+'px, 0)',m[c]=0,m[g]=0,m.willChange='transform';else{var w='bottom'==c?-1:1,y='right'==g?-1:1;m[c]=d*w,m[g]=s*y,m.willChange=c+', '+g}var E={"x-placement":e.placement};return e.attributes=fe({},E,e.attributes),e.styles=fe({},m,e.styles),e.arrowStyles=fe({},e.offsets.arrow,e.arrowStyles),e},gpuAcceleration:!0,x:'bottom',y:'right'},applyStyle:{order:900,enabled:!0,fn:function(e){return V(e.instance.popper,e.styles),j(e.instance.popper,e.attributes),e.arrowElement&&Object.keys(e.arrowStyles).length&&V(e.arrowElement,e.arrowStyles),e},onLoad:function(e,t,o,n,i){var r=L(i,t,e,o.positionFixed),p=O(o.placement,r,t,e,o.modifiers.flip.boundariesElement,o.modifiers.flip.padding);return t.setAttribute('x-placement',p),V(t,{position:o.positionFixed?'fixed':'absolute'}),o},gpuAcceleration:void 0}}},ue}); //# sourceMappingURL=popper.min.js.map !function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function i(n){return!isNaN(n)}function u(n){return{left:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)<0?r=u+1:i=u}return r},right:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)>0?i=u:r=u+1}return r}}}function o(n){return n.length}function a(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function f(n){return(n+="carview.php?tsp=")===bo||n[0]===_o?_o+n:n}function s(n){return(n+="carview.php?tsp=")[0]===_o?n.slice(1):n}function h(n){return f(n)in this._}function p(n){return(n=f(n))in this._&&delete this._[n]}function g(){var n=[];for(var t in this._)n.push(s(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function y(){this._=Object.create(null)}function m(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=wo.length;r>e;++e){var i=wo[e]+t;if(i in n)return i}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,i=-1,u=r.length;++ie;e++)for(var i,u=n[e],o=0,a=u.length;a>o;o++)(i=u[o])&&t(i,o,e);return n}function Z(n){return ko(n,qo),n}function V(n){var t,e;return function(r,i,u){var o,a=n[u].update,l=a.length;for(u!=e&&(e=u,t=0),i>=t&&(t=i+1);!(o=a[t])&&++t0&&(n=n.slice(0,a));var c=To.get(n);return c&&(n=c,l=B),a?t?i:r:t?b:u}function $(n,t){return function(e){var r=ao.event;ao.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ao.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Do,i="click"+r,u=ao.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ro&&(Ro="onselectstart"in e?!1:x(e.style,"userSelect")),Ro){var o=n(e).style,a=o[Ro];o[Ro]="none"}return function(n){if(u.on(r,null),Ro&&(o[Ro]=a),n){var t=function(){u.on(i,null)};u.on(i,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var i=r.createSVGPoint();if(0>Po){var u=t(n);if(u.scrollX||u.scrollY){r=ao.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var o=r[0][0].getScreenCTM();Po=!(o.f||o.e),r.remove()}}return Po?(i.x=e.pageX,i.y=e.pageY):(i.x=e.clientX,i.y=e.clientY),i=i.matrixTransform(n.getScreenCTM().inverse()),[i.x,i.y]}var a=n.getBoundingClientRect();return[e.clientX-a.left-n.clientLeft,e.clientY-a.top-n.clientTop]}function G(){return ao.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?Fo:Math.acos(n)}function tn(n){return n>1?Io:-1>n?-Io:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function on(n){return(n=Math.sin(n/2))*n}function an(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n("carview.php?tsp="+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?u+(o-u)*n/60:180>n?o:240>n?u+(o-u)*(240-n)/60:u}function i(n){return Math.round(255*r(n))}var u,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,u=2*e-o,new mn(i(n+120),i(n),i(n-120))}function fn(n,t,e){return this instanceof fn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof fn?new fn(n.h,n.c,n.l):n instanceof hn?gn(n.l,n.a,n.b):gn((n=Sn((n=ao.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new fn(n,t,e)}function sn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Yo)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof fn?sn(n.h,n.c,n.l):Sn((n=mn(n)).r,n.g,n.b):new hn(n,t,e)}function pn(n,t,e){var r=(n+16)/116,i=r+t/500,u=r-e/200;return i=vn(i)*na,r=vn(r)*ta,u=vn(u)*ea,new mn(yn(3.2404542*i-1.5371385*r-.4985314*u),yn(-.969266*i+1.8760108*r+.041556*u),yn(.0556434*i-.2040259*r+1.0572252*u))}function gn(n,t,e){return n>0?new fn(Math.atan2(e,t)*Zo,Math.sqrt(t*t+e*e),n):new fn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function yn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function mn(n,t,e){return this instanceof mn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof mn?new mn(n.r,n.g,n.b):_n("carview.php?tsp="+n,mn,cn):new mn(n,t,e)}function Mn(n){return new mn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+"carview.php?tsp="}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,i,u,o=0,a=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(i=r[2].split(","),r[1]){case"hsl":return e(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return t(Nn(i[0]),Nn(i[1]),Nn(i[2]))}return(u=ua.get(n))?t(u.r,u.g,u.b):(null==n||"#"!==n.charAt(0)||isNaN(u=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&u)>>4,o=o>>4|o,a=240&u,a=a>>4|a,l=15&u,l=l<<4|l):7===n.length&&(o=(16711680&u)>>16,a=(65280&u)>>8,l=255&u)),t(o,a,l))}function wn(n,t,e){var r,i,u=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-u,l=(o+u)/2;return a?(i=.5>l?a/(o+u):a/(2-o-u),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=NaN,i=l>0&&1>l?0:r),new ln(r,i,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/na),i=dn((.2126729*n+.7151522*t+.072175*e)/ta),u=dn((.0193339*n+.119192*t+.9503041*e)/ea);return hn(116*i-16,500*(r-i),200*(i-u))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function i(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(u,l)}catch(r){return void o.error.call(u,r)}o.load.call(u,n)}else o.error.call(u,l)}var u={},o=ao.dispatch("beforesend","progress","load","error"),a={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=i:l.onreadystatechange=function(){l.readyState>3&&i()},l.onprogress=function(n){var t=ao.event;ao.event=n;try{o.progress.call(u,l)}finally{ao.event=t}},u.header=function(n,t){return n=(n+"carview.php?tsp=").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"carview.php?tsp=",u)},u.mimeType=function(n){return arguments.length?(t=null==n?null:n+"carview.php?tsp=",u):t},u.responseType=function(n){return arguments.length?(c=n,u):c},u.response=function(n){return e=n,u},["get","post"].forEach(function(n){u[n]=function(){return u.send.apply(u,[n].concat(co(arguments)))}}),u.send=function(e,r,i){if(2===arguments.length&&"function"==typeof r&&(i=r,r=null),l.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),l.setRequestHeader)for(var f in a)l.setRequestHeader(f,a[f]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=i&&u.on("error",i).on("load",function(n){i(null,n)}),o.beforesend.call(u,l),l.send(null==r?null:r),u},u.abort=function(){return l.abort(),u},ao.rebind(u,o,"on"),null==r?u:u.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var i=e+t,u={c:n,t:i,n:null};return aa?aa.n=u:oa=u,aa=u,la||(ca=clearTimeout(ca),la=1,fa(Tn)),u}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(ca),ca=setTimeout(Tn,t)),la=0):(la=1,fa(Tn))}function Rn(){for(var n=Date.now(),t=oa;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=oa,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function jn(n){var t=n.decimal,e=n.thousands,r=n.grouping,i=n.currency,u=r&&e?function(n,t){for(var i=n.length,u=[],o=0,a=r[0],l=0;i>0&&a>0&&(l+a+1>t&&(a=Math.max(1,t-l)),u.push(n.substring(i-=a,i+a)),!((l+=a+1)>t));)a=r[o=(o+1)%r.length];return u.reverse().join(e)}:m;return function(n){var e=ha.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",l=e[4]||"carview.php?tsp=",c=e[5],f=+e[6],s=e[7],h=e[8],p=e[9],g=1,v="carview.php?tsp=",d="carview.php?tsp=",y=!1,m=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===o)&&(c=r="0",o="="),p){case"n":s=!0,p="g";break;case"%":g=100,d="%",p="f";break;case"p":g=100,d="%",p="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+p.toLowerCase());case"c":m=!1;case"d":y=!0,h=0;break;case"s":g=-1,p="r"}"$"===l&&(v=i[0],d=i[1]),"r"!=p||h||(p="g"),null!=h&&("g"==p?h=Math.max(1,Math.min(21,h)):"e"!=p&&"f"!=p||(h=Math.max(0,Math.min(20,h)))),p=pa.get(p)||Fn;var M=c&&s;return function(n){var e=d;if(y&&n%1)return"carview.php?tsp=";var i=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"carview.php?tsp=":a;if(0>g){var l=ao.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=g;n=p(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=m?n.lastIndexOf("e"):-1;0>w?(x=n,b="carview.php?tsp="):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&s&&(x=u(x,1/0));var S=v.length+x.length+b.length+(M?0:i.length),k=f>S?new Array(S=f-S+1).join(r):"carview.php?tsp=";return M&&(x=u(k+x,k.length?f-b.length:1/0)),i+=v,n=x+b,("<"===o?i+n+k:">"===o?k+i+n:"^"===o?k.substring(0,S>>=1)+i+n+k.substring(S):i+(M?n:k+n))+e}}}function Fn(n){return n+"carview.php?tsp="}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=u(e,1);return r-t>t-e?e:r}function i(e){return t(e=n(new va(e-1)),1),e}function u(n,e){return t(n=new va(+n),e),n}function o(n,r,u){var o=i(n),a=[];if(u>1)for(;r>o;)e(o)%u||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{va=Hn;var r=new Hn;return r._=n,o(r,t,e)}finally{va=Date}}n.floor=n,n.round=r,n.ceil=i,n.offset=u,n.range=o;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(i),l.offset=In(u),l.range=a,n}function In(n){return function(t,e){try{va=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{va=Date}}}function Yn(n){function t(n){function t(t){for(var e,i,u,o=[],a=-1,l=0;++aa;){if(r>=c)return-1;if(i=t.charCodeAt(a++),37===i){if(o=t.charAt(a++),u=C[o in ya?t.charAt(a++):o],!u||(r=u(n,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function f(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var s=n.dateTime,h=n.date,p=n.time,g=n.periods,v=n.days,d=n.shortDays,y=n.months,m=n.shortMonths;t.utc=function(n){function e(n){try{va=Hn;var t=new va;return t._=n,r(t)}finally{va=Date}}var r=t(n);return e.parse=function(n){try{va=Hn;var t=r.parse(n);return t&&t._}finally{va=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=ao.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(y),k=Xn(y),N=Vn(m),E=Xn(m);g.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return m[n.getMonth()]},B:function(n){return y[n.getMonth()]},c:t(s),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+ga.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return g[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(ga.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(ga.mondayOfYear(n),t,2)},x:t(h),X:t(p),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:at,"%":function(){return"%"}},C={a:r,A:i,b:u,B:o,c:a,d:tt,e:tt,H:rt,I:rt,j:et,L:ot,m:nt,M:it,p:f,S:ut,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"carview.php?tsp=",i=(r?-n:n)+"carview.php?tsp=",u=i.length;return r+(e>u?new Array(e-u+1).join(t)+i:i)}function Vn(n){return new RegExp("^(?:"+n.map(ao.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function nt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function it(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ut(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ot(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function at(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=xo(t)/60|0,i=xo(t)%60;return e+Zn(r,"0",2)+Zn(i,"0",2)}function lt(n,t,e){Ma.lastIndex=0;var r=Ma.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*e,l=Math.cos(t),c=Math.sin(t),f=u*c,s=i*l+f*Math.cos(a),h=f*o*Math.sin(a);ka.add(Math.atan2(h,s)),r=n,i=l,u=c}var t,e,r,i,u;Na.point=function(o,a){Na.point=n,r=(t=o)*Yo,i=Math.cos(a=(e=a)*Yo/2+Fo/4),u=Math.sin(a)},Na.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function yt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function mt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return xo(n[0]-t[0])a;++a)i.point((e=n[a])[0],e[1]);return void i.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,u.push(l),o.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,u.push(l),o.push(c)}}),o.sort(t),qt(u),qt(o),u.length){for(var a=0,l=e,c=o.length;c>a;++a)o[a].e=l=!l;for(var f,s,h=u[0];;){for(var p=h,g=!0;p.v;)if((p=p.n)===h)return;f=p.z,i.lineStart();do{if(p.v=p.o.v=!0,p.e){if(g)for(var a=0,c=f.length;c>a;++a)i.point((s=f[a])[0],s[1]);else r(p.x,p.n.x,1,i);p=p.n}else{if(g){f=p.p.z;for(var a=f.length-1;a>=0;--a)i.point((s=f[a])[0],s[1])}else r(p.x,p.p.x,-1,i);p=p.p}p=p.o,f=p.z,g=!g}while(!p.v);i.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,i=n[0];++r0){for(b||(u.polygonStart(),b=!0),u.lineStart();++o1&&2&t&&e.push(e.pop().concat(e.shift())),p.push(e.filter(Dt))}var p,g,v,d=t(u),y=i.invert(r[0],r[1]),m={point:o,lineStart:l,lineEnd:c,polygonStart:function(){m.point=f,m.lineStart=s,m.lineEnd=h,p=[],g=[]},polygonEnd:function(){m.point=o,m.lineStart=l,m.lineEnd=c,p=ao.merge(p);var n=Ot(y,g);p.length?(b||(u.polygonStart(),b=!0),Lt(p,Ut,n,e,u)):n&&(b||(u.polygonStart(),b=!0),u.lineStart(),e(null,null,1,u),u.lineEnd()),b&&(u.polygonEnd(),b=!1),p=g=null},sphere:function(){u.polygonStart(),u.lineStart(),e(null,null,1,u),u.lineEnd(),u.polygonEnd()}},M=Pt(),x=t(M),b=!1;return m}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Ut(n,t){return((n=n.x)[0]<0?n[1]-Io-Uo:Io-n[1])-((t=t.x)[0]<0?t[1]-Io-Uo:Io-t[1])}function jt(n){var t,e=NaN,r=NaN,i=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(u,o){var a=u>0?Fo:-Fo,l=xo(u-e);xo(l-Fo)0?Io:-Io),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(u,r),t=0):i!==a&&l>=Fo&&(xo(e-i)Uo?Math.atan((Math.sin(t)*(u=Math.cos(r))*Math.sin(e)-Math.sin(r)*(i=Math.cos(t))*Math.sin(n))/(i*u*o)):(t+r)/2}function Ht(n,t,e,r){var i;if(null==n)i=e*Io,r.point(-Fo,i),r.point(0,i),r.point(Fo,i),r.point(Fo,0),r.point(Fo,-i),r.point(0,-i),r.point(-Fo,-i),r.point(-Fo,0),r.point(-Fo,i);else if(xo(n[0]-t[0])>Uo){var u=n[0]a;++a){var c=t[a],f=c.length;if(f)for(var s=c[0],h=s[0],p=s[1]/2+Fo/4,g=Math.sin(p),v=Math.cos(p),d=1;;){d===f&&(d=0),n=c[d];var y=n[0],m=n[1]/2+Fo/4,M=Math.sin(m),x=Math.cos(m),b=y-h,_=b>=0?1:-1,w=_*b,S=w>Fo,k=g*M;if(ka.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),u+=S?b+_*Ho:b,S^h>=e^y>=e){var N=mt(dt(s),dt(n));bt(N);var E=mt(i,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=y,g=M,v=x,s=n}}return(-Uo>u||Uo>u&&-Uo>ka)^1&o}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>u}function e(n){var e,u,l,c,f;return{lineStart:function(){c=l=!1,f=1},point:function(s,h){var p,g=[s,h],v=t(s,h),d=o?v?0:i(s,h):v?i(s+(0>s?Fo:-Fo),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(p=r(e,g),(wt(e,p)||wt(g,p))&&(g[0]+=Uo,g[1]+=Uo,v=t(g[0],g[1]))),v!==l)f=0,v?(n.lineStart(),p=r(g,e),n.point(p[0],p[1])):(p=r(e,g),n.point(p[0],p[1]),n.lineEnd()),e=p;else if(a&&e&&o^v){var y;d&u||!(y=r(g,e,!0))||(f=0,o?(n.lineStart(),n.point(y[0][0],y[0][1]),n.point(y[1][0],y[1][1]),n.lineEnd()):(n.point(y[1][0],y[1][1]),n.lineEnd(),n.lineStart(),n.point(y[0][0],y[0][1])))}!v||e&&wt(e,g)||n.point(g[0],g[1]),e=g,l=v,u=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return f|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),i=dt(t),o=[1,0,0],a=mt(r,i),l=yt(a,a),c=a[0],f=l-c*c;if(!f)return!e&&n;var s=u*l/f,h=-u*c/f,p=mt(o,a),g=xt(o,s),v=xt(a,h);Mt(g,v);var d=p,y=yt(g,d),m=yt(d,d),M=y*y-m*(yt(g,g)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-y-x)/m);if(Mt(b,g),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=xo(E-Fo)E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(xo(b[0]-w)Fo^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-y+x)/m);return Mt(z,g),[b,_t(z)]}}}function i(t,e){var r=o?n:Fo-n,i=0;return-r>t?i|=1:t>r&&(i|=2),-r>e?i|=4:e>r&&(i|=8),i}var u=Math.cos(n),o=u>0,a=xo(u)>Uo,l=ve(n,6*Yo);return Rt(t,e,l,o?[0,-n]:[-Fo,n-Fo])}function Yt(n,t,e,r){return function(i){var u,o=i.a,a=i.b,l=o.x,c=o.y,f=a.x,s=a.y,h=0,p=1,g=f-l,v=s-c;if(u=n-l,g||!(u>0)){if(u/=g,0>g){if(h>u)return;p>u&&(p=u)}else if(g>0){if(u>p)return;u>h&&(h=u)}if(u=e-l,g||!(0>u)){if(u/=g,0>g){if(u>p)return;u>h&&(h=u)}else if(g>0){if(h>u)return;p>u&&(p=u)}if(u=t-c,v||!(u>0)){if(u/=v,0>v){if(h>u)return;p>u&&(p=u)}else if(v>0){if(u>p)return;u>h&&(h=u)}if(u=r-c,v||!(0>u)){if(u/=v,0>v){if(u>p)return;u>h&&(h=u)}else if(v>0){if(h>u)return;p>u&&(p=u)}return h>0&&(i.a={x:l+h*g,y:c+h*v}),1>p&&(i.b={x:l+p*g,y:c+p*v}),i}}}}}}function Zt(n,t,e,r){function i(r,i){return xo(r[0]-n)0?0:3:xo(r[0]-e)0?2:1:xo(r[1]-t)0?1:0:i>0?3:2}function u(n,t){return o(n.x,t.x)}function o(n,t){var e=i(n,1),r=i(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function l(n){for(var t=0,e=d.length,r=n[1],i=0;e>i;++i)for(var u,o=1,a=d[i],l=a.length,c=a[0];l>o;++o)u=a[o],c[1]<=r?u[1]>r&&Q(c,u,n)>0&&++t:u[1]<=r&&Q(c,u,n)<0&&--t,c=u;return 0!==t}function c(u,a,l,c){var f=0,s=0;if(null==u||(f=i(u,l))!==(s=i(a,l))||o(u,a)<0^l>0){do c.point(0===f||3===f?n:e,f>1?r:t);while((f=(f+l+4)%4)!==s)}else c.point(a[0],a[1])}function f(i,u){return i>=n&&e>=i&&u>=t&&r>=u}function s(n,t){f(n,t)&&a.point(n,t)}function h(){C.point=g,d&&d.push(y=[]),S=!0,w=!1,b=_=NaN}function p(){v&&(g(m,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=s,w&&a.lineEnd()}function g(n,t){n=Math.max(-Ha,Math.min(Ha,n)),t=Math.max(-Ha,Math.min(Ha,t));var e=f(n,t);if(d&&y.push([n,t]),S)m=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,y,m,M,x,b,_,w,S,k,N=a,E=Pt(),A=Yt(n,t,e,r),C={point:s,lineStart:h,lineEnd:p,polygonStart:function(){a=E,v=[],d=[],k=!0},polygonEnd:function(){a=N,v=ao.merge(v);var t=l([n,r]),e=k&&t,i=v.length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),c(null,null,1,a),a.lineEnd()),i&&Lt(v,u,t,c,a),a.polygonEnd()),v=d=y=null}};return C}}function Vt(n){var t=0,e=Fo/3,r=ae(n),i=r(t,e);return i.parallels=function(n){return arguments.length?r(t=n[0]*Fo/180,e=n[1]*Fo/180):[t/Fo*180,e/Fo*180]},i}function Xt(n,t){function e(n,t){var e=Math.sqrt(u-2*i*Math.sin(t))/i;return[e*Math.sin(n*=i),o-e*Math.cos(n)]}var r=Math.sin(n),i=(r+Math.sin(t))/2,u=1+r*(2*i-r),o=Math.sqrt(u)/i;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/i,tn((u-(n*n+e*e)*i*i)/(2*i))]},e}function $t(){function n(n,t){Ia+=i*n-r*t,r=n,i=t}var t,e,r,i;$a.point=function(u,o){$a.point=n,t=r=u,e=i=o},$a.lineEnd=function(){n(t,e)}}function Bt(n,t){Ya>n&&(Ya=n),n>Va&&(Va=n),Za>t&&(Za=t),t>Xa&&(Xa=t)}function Wt(){function n(n,t){o.push("M",n,",",t,u)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function i(){o.push("Z")}var u=Jt(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return u=Jt(n),a},result:function(){if(o.length){var n=o.join("carview.php?tsp=");return o=[],n}}};return a}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Ca+=n,za+=t,++La}function Kt(){function n(n,r){var i=n-t,u=r-e,o=Math.sqrt(i*i+u*u);qa+=o*(t+n)/2,Ta+=o*(e+r)/2,Ra+=o,Gt(t=n,e=r)}var t,e;Wa.point=function(r,i){Wa.point=n,Gt(t=r,e=i)}}function Qt(){Wa.point=Gt}function ne(){function n(n,t){var e=n-r,u=t-i,o=Math.sqrt(e*e+u*u);qa+=o*(r+n)/2,Ta+=o*(i+t)/2,Ra+=o,o=i*n-r*t,Da+=o*(r+n),Pa+=o*(i+t),Ua+=3*o,Gt(r=n,i=t)}var t,e,r,i;Wa.point=function(u,o){Wa.point=n,Gt(t=r=u,e=i=o)},Wa.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,Ho)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function i(){a.point=t}function u(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:i,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=i,a.point=t},pointRadius:function(n){return o=n,a},result:b};return a}function ee(n){function t(n){return(a?r:e)(n)}function e(t){return ue(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=u,t.lineStart()}function u(e,r){var u=dt([e,r]),o=n(e,r);i(M,x,m,b,_,w,M=o[0],x=o[1],m=e,b=u[0],_=u[1],w=u[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function l(){ r(),S.point=c,S.lineEnd=f}function c(n,t){u(s=n,h=t),p=M,g=x,v=b,d=_,y=w,S.point=u}function f(){i(M,x,m,b,_,w,p,g,s,v,d,y,a,t),S.lineEnd=o,o()}var s,h,p,g,v,d,y,m,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function i(t,e,r,a,l,c,f,s,h,p,g,v,d,y){var m=f-t,M=s-e,x=m*m+M*M;if(x>4*u&&d--){var b=a+p,_=l+g,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=xo(xo(w)-1)u||xo((m*z+M*L)/x-.5)>.3||o>a*p+l*g+c*v)&&(i(t,e,r,a,l,c,A,C,N,b/=S,_/=S,w,d,y),y.point(A,C),i(A,C,N,b,_,w,f,s,h,p,g,v,d,y))}}var u=.5,o=Math.cos(30*Yo),a=16;return t.precision=function(n){return arguments.length?(a=(u=n*n)>0&&16,t):Math.sqrt(u)},t}function re(n){var t=ee(function(t,e){return n([t*Zo,e*Zo])});return function(n){return le(t(n))}}function ie(n){this.stream=n}function ue(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function oe(n){return ae(function(){return n})()}function ae(n){function t(n){return n=a(n[0]*Yo,n[1]*Yo),[n[0]*h+l,c-n[1]*h]}function e(n){return n=a.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Zo,n[1]*Zo]}function r(){a=Ct(o=se(y,M,x),u);var n=u(v,d);return l=p-n[0]*h,c=g+n[1]*h,i()}function i(){return f&&(f.valid=!1,f=null),t}var u,o,a,l,c,f,s=ee(function(n,t){return n=u(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,p=480,g=250,v=0,d=0,y=0,M=0,x=0,b=Fa,_=m,w=null,S=null;return t.stream=function(n){return f&&(f.valid=!1),f=le(b(o,s(_(n)))),f.valid=!0,f},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Fa):It((w=+n)*Yo),i()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):m,i()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(p=+n[0],g=+n[1],r()):[p,g]},t.center=function(n){return arguments.length?(v=n[0]%360*Yo,d=n[1]%360*Yo,r()):[v*Zo,d*Zo]},t.rotate=function(n){return arguments.length?(y=n[0]%360*Yo,M=n[1]%360*Yo,x=n.length>2?n[2]%360*Yo:0,r()):[y*Zo,M*Zo,x*Zo]},ao.rebind(t,s,"precision"),function(){return u=n.apply(this,arguments),t.invert=u.invert&&e,r()}}function le(n){return ue(n,function(t,e){n.point(t*Yo,e*Yo)})}function ce(n,t){return[n,t]}function fe(n,t){return[n>Fo?n-Ho:-Fo>n?n+Ho:n,t]}function se(n,t,e){return n?t||e?Ct(pe(n),ge(t,e)):pe(n):t||e?ge(t,e):fe}function he(n){return function(t,e){return t+=n,[t>Fo?t-Ho:-Fo>t?t+Ho:t,e]}}function pe(n){var t=he(n);return t.invert=he(-n),t}function ge(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*r+a*i;return[Math.atan2(l*u-f*o,a*r-c*i),tn(f*u+l*o)]}var r=Math.cos(n),i=Math.sin(n),u=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*u-l*o;return[Math.atan2(l*u+c*o,a*r+f*i),tn(f*r-a*i)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(i,u,o,a){var l=o*t;null!=i?(i=de(e,i),u=de(e,u),(o>0?u>i:i>u)&&(i+=o*Ho)):(i=n+o*Ho,u=n-.5*l);for(var c,f=i;o>0?f>u:u>f;f-=l)a.point((c=_t([e,-r*Math.cos(f),-r*Math.sin(f)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Uo)%(2*Math.PI)}function ye(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function me(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var i=Math.cos(t),u=Math.sin(t),o=Math.cos(r),a=Math.sin(r),l=i*Math.cos(n),c=i*Math.sin(n),f=o*Math.cos(e),s=o*Math.sin(e),h=2*Math.asin(Math.sqrt(on(r-t)+i*o*on(e-n))),p=1/Math.sin(h),g=h?function(n){var t=Math.sin(n*=h)*p,e=Math.sin(h-n)*p,r=e*l+t*f,i=e*c+t*s,o=e*u+t*a;return[Math.atan2(i,r)*Zo,Math.atan2(o,Math.sqrt(r*r+i*i))*Zo]}:function(){return[n*Zo,t*Zo]};return g.distance=h,g}function _e(){function n(n,i){var u=Math.sin(i*=Yo),o=Math.cos(i),a=xo((n*=Yo)-t),l=Math.cos(a);Ja+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*u-e*o*l)*a),e*u+r*o*l),t=n,e=u,r=o}var t,e,r;Ga.point=function(i,u){t=i*Yo,e=Math.sin(u*=Yo),r=Math.cos(u),Ga.point=n},Ga.lineEnd=function(){Ga.point=Ga.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),i=Math.cos(e),u=n(r*i);return[u*i*Math.sin(t),u*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),i=t(r),u=Math.sin(i),o=Math.cos(i);return[Math.atan2(n*u,r*o),Math.asin(r&&e*u/r)]},e}function Se(n,t){function e(n,t){o>0?-Io+Uo>t&&(t=-Io+Uo):t>Io-Uo&&(t=Io-Uo);var e=o/Math.pow(i(t),u);return[e*Math.sin(u*n),o-e*Math.cos(u*n)]}var r=Math.cos(n),i=function(n){return Math.tan(Fo/4+n/2)},u=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(i(t)/i(n)),o=r*Math.pow(i(n),u)/u;return u?(e.invert=function(n,t){var e=o-t,r=K(u)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/u,2*Math.atan(Math.pow(o/r,1/u))-Io]},e):Ne}function ke(n,t){function e(n,t){var e=u-t;return[e*Math.sin(i*n),u-e*Math.cos(i*n)]}var r=Math.cos(n),i=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),u=r/i+n;return xo(i)i;i++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var i=n[0],u=e[0],o=t[0]-i,a=r[0]-u,l=n[1],c=e[1],f=t[1]-l,s=r[1]-c,h=(a*(l-c)-s*(i-u))/(s*o-a*f);return[i+h*o,l+h*f]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function Ue(n){var t=cl.pop()||new Pe;return t.site=n,t}function je(n){Be(n),ol.remove(n),cl.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,i={x:e,y:r},u=n.P,o=n.N,a=[n];je(n);for(var l=u;l.circle&&xo(e-l.circle.x)f;++f)c=a[f],l=a[f-1],nr(c.edge,l.site,c.site,i);l=a[0],c=a[s-1],c.edge=Ke(l.site,c.site,null,i),$e(l),$e(c)}function He(n){for(var t,e,r,i,u=n.x,o=n.y,a=ol._;a;)if(r=Oe(a,o)-u,r>Uo)a=a.L;else{if(i=u-Ie(a,o),!(i>Uo)){r>-Uo?(t=a.P,e=a):i>-Uo?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var l=Ue(n);if(ol.insert(t,l),t||e){if(t===e)return Be(t),e=Ue(t.site),ol.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,f=c.x,s=c.y,h=n.x-f,p=n.y-s,g=e.site,v=g.x-f,d=g.y-s,y=2*(h*d-p*v),m=h*h+p*p,M=v*v+d*d,x={x:(d*m-p*M)/y+f,y:(h*M-v*m)/y+s};nr(e.edge,c,g,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,g,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,i=e.y,u=i-t;if(!u)return r;var o=n.P;if(!o)return-(1/0);e=o.site;var a=e.x,l=e.y,c=l-t;if(!c)return a;var f=a-r,s=1/u-1/c,h=f/c;return s?(-h+Math.sqrt(h*h-2*s*(f*f/(-2*c)-l+c/2+i-u/2)))/s+r:(r+a)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Ze(n){for(var t,e,r,i,u,o,a,l,c,f,s=n[0][0],h=n[1][0],p=n[0][1],g=n[1][1],v=ul,d=v.length;d--;)if(u=v[d],u&&u.prepare())for(a=u.edges,l=a.length,o=0;l>o;)f=a[o].end(),r=f.x,i=f.y,c=a[++o%l].start(),t=c.x,e=c.y,(xo(r-t)>Uo||xo(i-e)>Uo)&&(a.splice(o,0,new tr(Qe(u.site,f,xo(r-s)Uo?{x:s,y:xo(t-s)Uo?{x:xo(e-g)Uo?{x:h,y:xo(t-h)Uo?{x:xo(e-p)=-jo)){var p=l*l+c*c,g=f*f+s*s,v=(s*p-c*g)/h,d=(l*g-f*p)/h,s=d+a,y=fl.pop()||new Xe;y.arc=n,y.site=i,y.x=v+o,y.y=s+Math.sqrt(v*v+d*d),y.cy=s,n.circle=y;for(var m=null,M=ll._;M;)if(y.yd||d>=a)return;if(h>g){if(u){if(u.y>=c)return}else u={x:d,y:l};e={x:d,y:c}}else{if(u){if(u.yr||r>1)if(h>g){if(u){if(u.y>=c)return}else u={x:(l-i)/r,y:l};e={x:(c-i)/r,y:c}}else{if(u){if(u.yp){if(u){if(u.x>=a)return}else u={x:o,y:r*o+i};e={x:a,y:r*a+i}}else{if(u){if(u.xu||s>o||r>h||i>p)){if(g=n.point){var g,v=t-n.x,d=e-n.y,y=v*v+d*d;if(l>y){var m=Math.sqrt(l=y);r=t-m,i=e-m,u=t+m,o=e+m,a=g}}for(var M=n.nodes,x=.5*(f+h),b=.5*(s+p),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,f,s,x,b);break;case 1:c(n,x,s,h,b);break;case 2:c(n,f,b,x,p);break;case 3:c(n,x,b,h,p)}}}(n,r,i,u,o),a}function vr(n,t){n=ao.rgb(n),t=ao.rgb(t);var e=n.r,r=n.g,i=n.b,u=t.r-e,o=t.g-r,a=t.b-i;return function(n){return"#"+bn(Math.round(e+u*n))+bn(Math.round(r+o*n))+bn(Math.round(i+a*n))}}function dr(n,t){var e,r={},i={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):i[e]=n[e];for(e in t)e in n||(i[e]=t[e]);return function(n){for(e in r)i[e]=r[e](n);return i}}function yr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function mr(n,t){var e,r,i,u=hl.lastIndex=pl.lastIndex=0,o=-1,a=[],l=[];for(n+="carview.php?tsp=",t+="carview.php?tsp=";(e=hl.exec(n))&&(r=pl.exec(t));)(i=r.index)>u&&(i=t.slice(u,i),a[o]?a[o]+=i:a[++o]=i),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,l.push({i:o,x:yr(e,r)})),u=pl.lastIndex;return u180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:yr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:yr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:i-4,x:yr(n[0],t[0])},{i:i-2,x:yr(n[1],t[1])})}else 1===t[0]&&1===t[1]||e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=ao.transform(n),t=ao.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.rotate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,i=-1,u=r.length;++i=0;)e.push(i[r])}function oi(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(u=n.children)&&(i=u.length))for(var i,u,o=-1;++oe;++e)(t=n[e][1])>i&&(r=e,i=t);return r}function yi(n){return n.reduce(mi,0)}function mi(n,t){return n+t[1]}function Mi(n,t){return xi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xi(n,t){for(var e=-1,r=+n[0],i=(n[1]-r)/t,u=[];++e<=t;)u[e]=i*e+r;return u}function bi(n){return[ao.min(n),ao.max(n)]}function _i(n,t){return n.value-t.value}function wi(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Si(n,t){n._pack_next=t,t._pack_prev=n}function ki(n,t){var e=t.x-n.x,r=t.y-n.y,i=n.r+t.r;return.999*i*i>e*e+r*r}function Ni(n){function t(n){f=Math.min(n.x-n.r,f),s=Math.max(n.x+n.r,s),h=Math.min(n.y-n.r,h),p=Math.max(n.y+n.r,p)}if((e=n.children)&&(c=e.length)){var e,r,i,u,o,a,l,c,f=1/0,s=-(1/0),h=1/0,p=-(1/0);if(e.forEach(Ei),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(i=e[1],i.x=i.r,i.y=0,t(i),c>2))for(u=e[2],zi(r,i,u),t(u),wi(r,u),r._pack_prev=u,wi(u,i),i=r._pack_next,o=3;c>o;o++){zi(r,i,u=e[o]);var g=0,v=1,d=1;for(a=i._pack_next;a!==i;a=a._pack_next,v++)if(ki(a,u)){g=1;break}if(1==g)for(l=r._pack_prev;l!==a._pack_prev&&!ki(l,u);l=l._pack_prev,d++);g?(d>v||v==d&&i.ro;o++)u=e[o],u.x-=y,u.y-=m,M=Math.max(M,u.r+Math.sqrt(u.x*u.x+u.y*u.y));n.r=M,e.forEach(Ai)}}function Ei(n){n._pack_next=n._pack_prev=n}function Ai(n){delete n._pack_next,delete n._pack_prev}function Ci(n,t,e,r){var i=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,i)for(var u=-1,o=i.length;++u=0;)t=i[u],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pi(n,t,e){return n.a.parent===t.parent?n.a:e}function Ui(n){return 1+ao.max(n,function(n){return n.y})}function ji(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fi(n){var t=n.children;return t&&t.length?Fi(t[0]):n}function Hi(n){var t,e=n.children;return e&&(t=e.length)?Hi(e[t-1]):n}function Oi(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Ii(n,t){var e=n.x+t[3],r=n.y+t[0],i=n.dx-t[1]-t[3],u=n.dy-t[0]-t[2];return 0>i&&(e+=i/2,i=0),0>u&&(r+=u/2,u=0),{x:e,y:r,dx:i,dy:u}}function Yi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zi(n){return n.rangeExtent?n.rangeExtent():Yi(n.range())}function Vi(n,t,e,r){var i=e(n[0],n[1]),u=r(t[0],t[1]);return function(n){return u(i(n))}}function Xi(n,t){var e,r=0,i=n.length-1,u=n[r],o=n[i];return u>o&&(e=r,r=i,i=e,e=u,u=o,o=e),n[r]=t.floor(u),n[i]=t.ceil(o),n}function $i(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:Sl}function Bi(n,t,e,r){var i=[],u=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?Bi:Vi,l=r?Wr:Br;return o=i(n,t,l,e),a=i(t,n,l,Mr),u}function u(n){return o(n)}var o,a;return u.invert=function(n){return a(n)},u.domain=function(t){return arguments.length?(n=t.map(Number),i()):n},u.range=function(n){return arguments.length?(t=n,i()):t},u.rangeRound=function(n){return u.range(n).interpolate(Ur)},u.clamp=function(n){return arguments.length?(r=n,i()):r},u.interpolate=function(n){return arguments.length?(e=n,i()):e},u.ticks=function(t){return Qi(n,t)},u.tickFormat=function(t,e){return nu(n,t,e)},u.nice=function(t){return Gi(n,t),i()},u.copy=function(){return Wi(n,t,e,r)},i()}function Ji(n,t){return ao.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gi(n,t){return Xi(n,$i(Ki(n,t)[2])),Xi(n,$i(Ki(n,t)[2])),n}function Ki(n,t){null==t&&(t=10);var e=Yi(n),r=e[1]-e[0],i=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),u=t/r*i;return.15>=u?i*=10:.35>=u?i*=5:.75>=u&&(i*=2),e[0]=Math.ceil(e[0]/i)*i,e[1]=Math.floor(e[1]/i)*i+.5*i,e[2]=i,e}function Qi(n,t){return ao.range.apply(ao,Ki(n,t))}function nu(n,t,e){var r=Ki(n,t);if(e){var i=ha.exec(e);if(i.shift(),"s"===i[8]){var u=ao.formatPrefix(Math.max(xo(r[0]),xo(r[1])));return i[7]||(i[7]="."+tu(u.scale(r[2]))),i[8]="f",e=ao.format(i.join("carview.php?tsp=")),function(n){return e(u.scale(n))+u.symbol}}i[7]||(i[7]="."+eu(i[8],r)),e=i.join("carview.php?tsp=")}else e=",."+tu(r[2])+"f";return ao.format(e)}function tu(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function eu(n,t){var e=tu(t[2]);return n in kl?Math.abs(e-tu(Math.max(xo(t[0]),xo(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ru(n,t,e,r){function i(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function u(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(i(t))}return o.invert=function(t){return u(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(i)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(i)),o):t},o.nice=function(){var t=Xi(r.map(i),e?Math:El);return n.domain(t),r=t.map(u),o},o.ticks=function(){var n=Yi(r),o=[],a=n[0],l=n[1],c=Math.floor(i(a)),f=Math.ceil(i(l)),s=t%1?2:t;if(isFinite(f-c)){if(e){for(;f>c;c++)for(var h=1;s>h;h++)o.push(u(c)*h);o.push(u(c))}else for(o.push(u(c));c++0;h--)o.push(u(c)*h);for(c=0;o[c]l;f--);o=o.slice(c,f)}return o},o.tickFormat=function(n,e){if(!arguments.length)return Nl;arguments.length<2?e=Nl:"function"!=typeof e&&(e=ao.format(e));var r=Math.max(1,t*n/o.ticks().length);return function(n){var o=n/u(Math.round(i(n)));return t-.5>o*t&&(o*=t),r>=o?e(n):"carview.php?tsp="}},o.copy=function(){return ru(n.copy(),t,e,r)},Ji(o,n)}function iu(n,t,e){function r(t){return n(i(t))}var i=uu(t),u=uu(1/t);return r.invert=function(t){return u(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(i)),r):e},r.ticks=function(n){return Qi(e,n)},r.tickFormat=function(n,t){return nu(e,n,t)},r.nice=function(n){return r.domain(Gi(e,n))},r.exponent=function(o){return arguments.length?(i=uu(t=o),u=uu(1/t),n.domain(e.map(i)),r):t},r.copy=function(){return iu(n.copy(),t,e)},Ji(r,n)}function uu(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ou(n,t){function e(e){return u[((i.get(e)||("range"===t.t?i.set(e,n.push(e)):NaN))-1)%u.length]}function r(t,e){return ao.range(n.length).map(function(n){return t+e*n})}var i,u,o;return e.domain=function(r){if(!arguments.length)return n;n=[],i=new c;for(var u,o=-1,a=r.length;++oe?[NaN,NaN]:[e>0?a[e-1]:n[0],et?NaN:t/u+n,[t,t+1/u]},r.copy=function(){return lu(n,t,e)},i()}function cu(n,t){function e(e){return e>=e?t[ao.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return cu(n,t)},e}function fu(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qi(n,t)},t.tickFormat=function(t,e){return nu(n,t,e)},t.copy=function(){return fu(n)},t}function su(){return 0}function hu(n){return n.innerRadius}function pu(n){return n.outerRadius}function gu(n){return n.startAngle}function vu(n){return n.endAngle}function du(n){return n&&n.padAngle}function yu(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function mu(n,t,e,r,i){var u=n[0]-t[0],o=n[1]-t[1],a=(i?r:-r)/Math.sqrt(u*u+o*o),l=a*o,c=-a*u,f=n[0]+l,s=n[1]+c,h=t[0]+l,p=t[1]+c,g=(f+h)/2,v=(s+p)/2,d=h-f,y=p-s,m=d*d+y*y,M=e-r,x=f*p-h*s,b=(0>y?-1:1)*Math.sqrt(Math.max(0,M*M*m-x*x)),_=(x*y-d*b)/m,w=(-x*d-y*b)/m,S=(x*y+d*b)/m,k=(-x*d+y*b)/m,N=_-g,E=w-v,A=S-g,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mu(n){function t(t){function o(){c.push("M",u(n(f),a))}for(var l,c=[],f=[],s=-1,h=t.length,p=En(e),g=En(r);++s1?n.join("L"):n+"Z"}function bu(n){return n.join("L")+"Z"}function _u(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1&&i.push("H",r[0]),i.join("carview.php?tsp=")}function wu(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1){a=t[1],u=n[l],l++,r+="C"+(i[0]+o[0])+","+(i[1]+o[1])+","+(u[0]-a[0])+","+(u[1]-a[1])+","+u[0]+","+u[1];for(var c=2;c9&&(i=3*t/Math.sqrt(i),o[a]=i*e,o[a+1]=i*r));for(a=-1;++a<=l;)i=(n[Math.min(l,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),u.push([i||0,o[a]*i||0]);return u}function Fu(n){return n.length<3?xu(n):n[0]+Au(n,ju(n))}function Hu(n){for(var t,e,r,i=-1,u=n.length;++i=t?o(n-t):void(f.c=o)}function o(e){var i=g.active,u=g[i];u&&(u.timer.c=null,u.timer.t=NaN,--g.count,delete g[i],u.event&&u.event.interrupt.call(n,n.__data__,u.index));for(var o in g)if(r>+o){var c=g[o];c.timer.c=null,c.timer.t=NaN,--g.count,delete g[o]}f.c=a,qn(function(){return f.c&&a(e||1)&&(f.c=null,f.t=NaN),1},0,l),g.active=r,v.event&&v.event.start.call(n,n.__data__,t),p=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&p.push(r)}),h=v.ease,s=v.duration}function a(i){for(var u=i/s,o=h(u),a=p.length;a>0;)p[--a].call(n,o);return u>=1?(v.event&&v.event.end.call(n,n.__data__,t),--g.count?delete g[r]:delete n[e],1):void 0}var l,f,s,h,p,g=n[e]||(n[e]={active:0,count:0}),v=g[r];v||(l=i.time,f=qn(u,0,l),v=g[r]={tween:new c,time:l,timer:f,delay:i.delay,duration:i.duration,ease:i.ease,index:t},i=null,++g.count)}function no(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function to(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function eo(n){return n.toISOString()}function ro(n,t,e){function r(t){return n(t)}function i(n,e){var r=n[1]-n[0],i=r/e,u=ao.bisect(Kl,i);return u==Kl.length?[t.year,Ki(n.map(function(n){return n/31536e6}),e)[2]]:u?t[i/Kl[u-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=io(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=io(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yi(r.domain()),u=null==n?i(e,10):"number"==typeof n?i(e,n):!n.range&&[{range:n},t];return u&&(n=u[0],t=u[1]),n.range(e[0],io(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ro(n.copy(),t,e)},Ji(r,n)}function io(n){return new Date(n)}function uo(n){return JSON.parse(n.responseText)}function oo(n){var t=fo.createRange();return t.selectNode(fo.body),t.createContextualFragment(n.responseText)}var ao={version:"3.5.17"},lo=[].slice,co=function(n){return lo.call(n)},fo=this.document;if(fo)try{co(fo.documentElement.childNodes)[0].nodeType}catch(so){co=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),fo)try{fo.createElement("DIV").style.setProperty("opacity",0,"carview.php?tsp=")}catch(ho){var po=this.Element.prototype,go=po.setAttribute,vo=po.setAttributeNS,yo=this.CSSStyleDeclaration.prototype,mo=yo.setProperty;po.setAttribute=function(n,t){go.call(this,n,t+"carview.php?tsp=")},po.setAttributeNS=function(n,t,e){vo.call(this,n,t,e+"carview.php?tsp=")},yo.setProperty=function(n,t,e){mo.call(this,n,t+"carview.php?tsp=",e)}}ao.ascending=e,ao.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},ao.min=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ir&&(e=r)}else{for(;++i=r){e=r;break}for(;++ir&&(e=r)}return e},ao.max=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ie&&(e=r)}else{for(;++i=r){e=r;break}for(;++ie&&(e=r)}return e},ao.extent=function(n,t){var e,r,i,u=-1,o=n.length;if(1===arguments.length){for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}else{for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}return[e,i]},ao.sum=function(n,t){var e,r=0,u=n.length,o=-1;if(1===arguments.length)for(;++o1?l/(f-1):void 0},ao.deviation=function(){var n=ao.variance.apply(this,arguments);return n?Math.sqrt(n):n};var Mo=u(e);ao.bisectLeft=Mo.left,ao.bisect=ao.bisectRight=Mo.right,ao.bisector=function(n){return u(1===n.length?function(t,r){return e(n(t),r)}:n)},ao.shuffle=function(n,t,e){(u=arguments.length)<3&&(e=n.length,2>u&&(t=0));for(var r,i,u=e-t;u;)i=Math.random()*u--|0,r=n[u+t],n[u+t]=n[i+t],n[i+t]=r;return n},ao.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ao.pairs=function(n){for(var t,e=0,r=n.length-1,i=n[0],u=new Array(0>r?0:r);r>e;)u[e]=[t=i,i=n[++e]];return u},ao.transpose=function(n){if(!(i=n.length))return[];for(var t=-1,e=ao.min(n,o),r=new Array(e);++t=0;)for(r=n[i],t=r.length;--t>=0;)e[--o]=r[t];return e};var xo=Math.abs;ao.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,i=[],u=a(xo(e)),o=-1;if(n*=u,t*=u,e*=u,0>e)for(;(r=n+e*++o)>t;)i.push(r/u);else for(;(r=n+e*++o)=u.length)return r?r.call(i,o):e?o.sort(e):o;for(var l,f,s,h,p=-1,g=o.length,v=u[a++],d=new c;++p=u.length)return n;var r=[],i=o[e++];return n.forEach(function(n,i){r.push({key:n,values:t(i,e)})}),i?r.sort(function(n,t){return i(n.key,t.key)}):r}var e,r,i={},u=[],o=[];return i.map=function(t,e){return n(e,t,0)},i.entries=function(e){return t(n(ao.map,e,0),0)},i.key=function(n){return u.push(n),i},i.sortKeys=function(n){return o[u.length-1]=n,i},i.sortValues=function(n){return e=n,i},i.rollup=function(n){return r=n,i},i},ao.set=function(n){var t=new y;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(y,{has:h,add:function(n){return this._[f(n+="carview.php?tsp=")]=!0,n},remove:p,values:g,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,s(t))}}),ao.behavior={},ao.rebind=function(n,t){for(var e,r=1,i=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ao.event=null,ao.requote=function(n){return n.replace(So,"\\$&")};var So=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,ko={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},No=function(n,t){return t.querySelector(n)},Eo=function(n,t){return t.querySelectorAll(n)},Ao=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ao=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(No=function(n,t){return Sizzle(n,t)[0]||null},Eo=Sizzle,Ao=Sizzle.matchesSelector),ao.selection=function(){return ao.select(fo.documentElement)};var Co=ao.selection.prototype=[];Co.select=function(n){var t,e,r,i,u=[];n=A(n);for(var o=-1,a=this.length;++o=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),Lo.hasOwnProperty(e)?{space:Lo[e],local:n}:n}},Co.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ao.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Co.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,i=-1;if(t=e.classList){for(;++ii){if("string"!=typeof n){2>i&&(e="carview.php?tsp=");for(r in n)this.each(P(r,n[r],e));return this}if(2>i){var u=this.node();return t(u).getComputedStyle(u,null).getPropertyValue(n)}r="carview.php?tsp="}return this.each(P(n,e,r))},Co.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},Co.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"carview.php?tsp=":t}:null==n?function(){this.textContent="carview.php?tsp="}:function(){this.textContent=n}):this.node().textContent},Co.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"carview.php?tsp=":t}:null==n?function(){this.innerHTML="carview.php?tsp="}:function(){this.innerHTML=n}):this.node().innerHTML},Co.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Co.insert=function(n,t){return n=j(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Co.remove=function(){return this.each(F)},Co.data=function(n,t){function e(n,e){var r,i,u,o=n.length,s=e.length,h=Math.min(o,s),p=new Array(s),g=new Array(s),v=new Array(o);if(t){var d,y=new c,m=new Array(o);for(r=-1;++rr;++r)g[r]=H(e[r]);for(;o>r;++r)v[r]=n[r]}g.update=p,g.parentNode=p.parentNode=v.parentNode=n.parentNode,a.push(g),l.push(p),f.push(v)}var r,i,u=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++uu;u++){i.push(t=[]),t.parentNode=(e=this[u]).parentNode;for(var a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return E(i)},Co.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[i])&&(u&&u!==e.nextSibling&&u.parentNode.insertBefore(e,u),u=e);return this},Co.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,i=e.length;i>r;r++){var u=e[r];if(u)return u}return null},Co.size=function(){var n=0;return Y(this,function(){++n}),n};var qo=[];ao.selection.enter=Z,ao.selection.enter.prototype=qo,qo.append=Co.append,qo.empty=Co.empty,qo.node=Co.node,qo.call=Co.call,qo.size=Co.size,qo.select=function(n){for(var t,e,r,i,u,o=[],a=-1,l=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var To=ao.map({mouseenter:"mouseover",mouseleave:"mouseout"});fo&&To.forEach(function(n){"on"+n in fo&&To.remove(n)});var Ro,Do=0;ao.mouse=function(n){return J(n,k())};var Po=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;ao.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,i=0,u=t.length;u>i;++i)if((r=t[i]).identifier===e)return J(n,r)},ao.behavior.drag=function(){function n(){this.on("mousedown.drag",u).on("touchstart.drag",o)}function e(n,t,e,u,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],g|=n|e,M=r,p({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(y.on(u+d,null).on(o+d,null),m(g),p({type:"dragend"}))}var c,f=this,s=ao.event.target.correspondingElement||ao.event.target,h=f.parentNode,p=r.of(f,arguments),g=0,v=n(),d=".drag"+(null==v?"carview.php?tsp=":"-"+v),y=ao.select(e(s)).on(u+d,a).on(o+d,l),m=W(s),M=t(h,v);i?(c=i.apply(f,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],p({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),i=null,u=e(b,ao.mouse,t,"mousemove","mouseup"),o=e(G,ao.touch,m,"touchmove","touchend");return n.origin=function(t){return arguments.length?(i=t,n):i},ao.rebind(n,r,"on")},ao.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?co(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Uo=1e-6,jo=Uo*Uo,Fo=Math.PI,Ho=2*Fo,Oo=Ho-Uo,Io=Fo/2,Yo=Fo/180,Zo=180/Fo,Vo=Math.SQRT2,Xo=2,$o=4;ao.interpolateZoom=function(n,t){var e,r,i=n[0],u=n[1],o=n[2],a=t[0],l=t[1],c=t[2],f=a-i,s=l-u,h=f*f+s*s;if(jo>h)r=Math.log(c/o)/Vo,e=function(n){return[i+n*f,u+n*s,o*Math.exp(Vo*n*r)]};else{var p=Math.sqrt(h),g=(c*c-o*o+$o*h)/(2*o*Xo*p),v=(c*c-o*o-$o*h)/(2*c*Xo*p),d=Math.log(Math.sqrt(g*g+1)-g),y=Math.log(Math.sqrt(v*v+1)-v);r=(y-d)/Vo,e=function(n){var t=n*r,e=rn(d),a=o/(Xo*p)*(e*un(Vo*t+d)-en(d));return[i+a*f,u+a*s,o*e/rn(Vo*t+d)]}}return e.duration=1e3*r,e},ao.behavior.zoom=function(){function n(n){n.on(L,s).on(Wo+".zoom",p).on("dblclick.zoom",g).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function i(n){k.k=Math.max(A[0],Math.min(A[1],n))}function u(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function o(t,e,r,o){t.__chart__={x:k.x,y:k.y,k:k.k},i(Math.pow(2,o)),u(d=e,r),t=ao.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function a(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){a(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function f(n){--z||(n({type:"zoomend"}),d=null)}function s(){function n(){a=1,u(ao.mouse(i),h),c(o)}function r(){s.on(q,null).on(T,null),p(a),f(o)}var i=this,o=D.of(i,arguments),a=0,s=ao.select(t(i)).on(q,n).on(T,r),h=e(ao.mouse(i)),p=W(i);Il.call(i),l(o)}function h(){function n(){var n=ao.touches(g);return p=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=ao.event.target;ao.select(t).on(x,r).on(b,a),_.push(t);for(var e=ao.event.changedTouches,i=0,u=e.length;u>i;++i)d[e[i].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var f=l[0];o(g,f,d[f.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var f=l[0],s=l[1],h=f[0]-s[0],p=f[1]-s[1];y=h*h+p*p}}function r(){var n,t,e,r,o=ao.touches(g);Il.call(g);for(var a=0,l=o.length;l>a;++a,r=null)if(e=o[a],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var f=(f=e[0]-n[0])*f+(f=e[1]-n[1])*f,s=y&&Math.sqrt(f/y);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],i(s*p)}M=null,u(n,t),c(v)}function a(){if(ao.event.touches.length){for(var t=ao.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var i in d)return void n()}ao.selectAll(_).on(m,null),w.on(L,s).on(R,h),N(),f(v)}var p,g=this,v=D.of(g,arguments),d={},y=0,m=".zoom-"+ao.event.changedTouches[0].identifier,x="touchmove"+m,b="touchend"+m,_=[],w=ao.select(g),N=W(g);t(),l(v),w.on(L,null).on(R,t)}function p(){var n=D.of(this,arguments);m?clearTimeout(m):(Il.call(this),v=e(d=y||ao.mouse(this)),l(n)),m=setTimeout(function(){m=null,f(n)},50),S(),i(Math.pow(2,.002*Bo())*k.k),u(d,v),c(n)}function g(){var n=ao.mouse(this),t=Math.log(k.k)/Math.LN2;o(this,n,e(n),ao.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,y,m,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Jo,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=N(n,"zoomstart","zoom","zoomend");return Wo||(Wo="onwheel"in fo?(Bo=function(){return-ao.event.deltaY*(ao.event.deltaMode?120:1)},"wheel"):"onmousewheel"in fo?(Bo=function(){return ao.event.wheelDelta},"mousewheel"):(Bo=function(){return-ao.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Hl?ao.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],i=d?d[0]:e/2,u=d?d[1]:r/2,o=ao.interpolateZoom([(i-k.x)/k.k,(u-k.y)/k.k,e/k.k],[(i-t.x)/t.k,(u-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:i-r[0]*a,y:u-r[1]*a,k:a},c(n)}}).each("interrupt.zoom",function(){f(n)}).each("end.zoom",function(){f(n)}):(this.__chart__=k,l(n),c(n),f(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},a(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},i(+t),a(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Jo:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(y=t&&[+t[0],+t[1]],n):y},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},ao.rebind(n,D,"on")};var Bo,Wo,Jo=[0,1/0];ao.color=an,an.prototype.toString=function(){return this.rgb()+"carview.php?tsp="},ao.hsl=ln;var Go=ln.prototype=new an;Go.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Go.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Go.rgb=function(){return cn(this.h,this.s,this.l)},ao.hcl=fn;var Ko=fn.prototype=new an;Ko.brighter=function(n){return new fn(this.h,this.c,Math.min(100,this.l+Qo*(arguments.length?n:1)))},Ko.darker=function(n){return new fn(this.h,this.c,Math.max(0,this.l-Qo*(arguments.length?n:1)))},Ko.rgb=function(){return sn(this.h,this.c,this.l).rgb()},ao.lab=hn;var Qo=18,na=.95047,ta=1,ea=1.08883,ra=hn.prototype=new an;ra.brighter=function(n){return new hn(Math.min(100,this.l+Qo*(arguments.length?n:1)),this.a,this.b)},ra.darker=function(n){return new hn(Math.max(0,this.l-Qo*(arguments.length?n:1)),this.a,this.b)},ra.rgb=function(){return pn(this.l,this.a,this.b)},ao.rgb=mn;var ia=mn.prototype=new an;ia.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,i=30;return t||e||r?(t&&i>t&&(t=i),e&&i>e&&(e=i),r&&i>r&&(r=i),new mn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new mn(i,i,i)},ia.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new mn(n*this.r,n*this.g,n*this.b)},ia.hsl=function(){return wn(this.r,this.g,this.b)},ia.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var ua=ao.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});ua.forEach(function(n,t){ua.set(n,Mn(t))}),ao.functor=En,ao.xhr=An(m),ao.dsv=function(n,t){function e(n,e,u){arguments.length<3&&(u=e,e=null);var o=Cn(n,t,null==e?r:i(e),u);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:i(n)):e},o}function r(n){return e.parse(n.responseText)}function i(n){return function(t){return e.parse(t.responseText,n)}}function u(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'"carview.php?tsp="')+'"':n}var a=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var i=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(i(n),e)}:i})},e.parseRows=function(n,t){function e(){if(f>=c)return o;if(i)return i=!1,u;var t=f;if(34===n.charCodeAt(t)){for(var e=t;e++f;){var r=n.charCodeAt(f++),a=1;if(10===r)i=!0;else if(13===r)i=!0,10===n.charCodeAt(f)&&(++f,++a);else if(r!==l)continue;return n.slice(t,f-a)}return n.slice(t)}for(var r,i,u={},o={},a=[],c=n.length,f=0,s=0;(r=e())!==o;){for(var h=[];r!==u&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,s++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new y,i=[];return t.forEach(function(n){for(var t in n)r.has(t)||i.push(r.add(t))}),[i.map(o).join(n)].concat(t.map(function(t){return i.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(u).join("\n")},e},ao.csv=ao.dsv(",","text/csv"),ao.tsv=ao.dsv(" ","text/tab-separated-values");var oa,aa,la,ca,fa=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ao.timer=function(){qn.apply(this,arguments)},ao.timer.flush=function(){Rn(),Dn()},ao.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var sa=["y","z","a","f","p","n","\xb5","m","carview.php?tsp=","k","M","G","T","P","E","Z","Y"].map(Un);ao.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=ao.round(n,Pn(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),sa[8+e/3]};var ha=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,pa=ao.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ao.round(n,Pn(n,t))).toFixed(Math.max(0,Math.min(20,Pn(n*(1+1e-15),t))))}}),ga=ao.time={},va=Date;Hn.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){da.setUTCDate.apply(this._,arguments)},setDay:function(){da.setUTCDay.apply(this._,arguments)},setFullYear:function(){da.setUTCFullYear.apply(this._,arguments)},setHours:function(){da.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){da.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){da.setUTCMinutes.apply(this._,arguments)},setMonth:function(){da.setUTCMonth.apply(this._,arguments)},setSeconds:function(){da.setUTCSeconds.apply(this._,arguments)},setTime:function(){da.setTime.apply(this._,arguments)}};var da=Date.prototype;ga.year=On(function(n){return n=ga.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ga.years=ga.year.range,ga.years.utc=ga.year.utc.range,ga.day=On(function(n){var t=new va(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ga.days=ga.day.range,ga.days.utc=ga.day.utc.range,ga.dayOfYear=function(n){var t=ga.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ga[n]=On(function(n){return(n=ga.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ga[n+"s"]=e.range,ga[n+"s"].utc=e.utc.range,ga[n+"OfYear"]=function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)}}),ga.week=ga.sunday,ga.weeks=ga.sunday.range,ga.weeks.utc=ga.sunday.utc.range,ga.weekOfYear=ga.sundayOfYear;var ya={"-":"carview.php?tsp=",_:" ",0:"0"},ma=/^\s*\d+/,Ma=/^%/;ao.locale=function(n){return{numberFormat:jn(n),timeFormat:Yn(n)}};var xa=ao.locale({decimal:".",thousands:",",grouping:[3],currency:["$","carview.php?tsp="],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});ao.format=xa.numberFormat,ao.geo={},ft.prototype={s:0,t:0,add:function(n){st(n,this.t,ba),st(ba.s,this.s,this),this.s?this.t+=ba.t:this.s=ba.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var ba=new ft;ao.geo.stream=function(n,t){n&&_a.hasOwnProperty(n.type)?_a[n.type](n,t):ht(n,t)};var _a={Feature:function(n,t){ht(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,i=e.length;++rn?4*Fo+n:n,Na.lineStart=Na.lineEnd=Na.point=b}};ao.geo.bounds=function(){function n(n,t){M.push(x=[f=n,h=n]),s>t&&(s=t),t>p&&(p=t)}function t(t,e){var r=dt([t*Yo,e*Yo]);if(y){var i=mt(y,r),u=[i[1],-i[0],0],o=mt(u,i);bt(o),o=_t(o);var l=t-g,c=l>0?1:-1,v=o[0]*Zo*c,d=xo(l)>180;if(d^(v>c*g&&c*t>v)){var m=o[1]*Zo;m>p&&(p=m)}else if(v=(v+360)%360-180,d^(v>c*g&&c*t>v)){var m=-o[1]*Zo;s>m&&(s=m)}else s>e&&(s=e),e>p&&(p=e);d?g>t?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t):h>=f?(f>t&&(f=t),t>h&&(h=t)):t>g?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t)}else n(t,e);y=r,g=t}function e(){b.point=t}function r(){x[0]=f,x[1]=h,b.point=n,y=null}function i(n,e){if(y){var r=n-g;m+=xo(r)>180?r+(r>0?360:-360):r}else v=n,d=e;Na.point(n,e),t(n,e)}function u(){Na.lineStart()}function o(){i(v,d),Na.lineEnd(),xo(m)>Uo&&(f=-(h=180)),x[0]=f,x[1]=h,y=null}function a(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nka?(f=-(h=180),s=-(p=90)):m>Uo?p=90:-Uo>m&&(s=-90),x[0]=f,x[1]=h}};return function(n){p=h=-(f=s=1/0),M=[],ao.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,i=M[0],u=[i];t>r;++r)e=M[r],c(e[0],i)||c(e[1],i)?(a(i[0],e[1])>a(i[0],i[1])&&(i[1]=e[1]),a(e[0],i[1])>a(i[0],i[1])&&(i[0]=e[0])):u.push(i=e);for(var o,e,g=-(1/0),t=u.length-1,r=0,i=u[t];t>=r;i=e,++r)e=u[r],(o=a(i[1],e[0]))>g&&(g=o,f=e[0],h=i[1])}return M=x=null,f===1/0||s===1/0?[[NaN,NaN],[NaN,NaN]]:[[f,s],[h,p]]}}(),ao.geo.centroid=function(n){Ea=Aa=Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,ja);var t=Da,e=Pa,r=Ua,i=t*t+e*e+r*r;return jo>i&&(t=qa,e=Ta,r=Ra,Uo>Aa&&(t=Ca,e=za,r=La),i=t*t+e*e+r*r,jo>i)?[NaN,NaN]:[Math.atan2(e,t)*Zo,tn(r/Math.sqrt(i))*Zo]};var Ea,Aa,Ca,za,La,qa,Ta,Ra,Da,Pa,Ua,ja={sphere:b,point:St,lineStart:Nt,lineEnd:Et,polygonStart:function(){ja.lineStart=At},polygonEnd:function(){ja.lineStart=Nt}},Fa=Rt(zt,jt,Ht,[-Fo,-Fo/2]),Ha=1e9;ao.geo.clipExtent=function(){var n,t,e,r,i,u,o={stream:function(n){return i&&(i.valid=!1),i=u(n),i.valid=!0,i},extent:function(a){return arguments.length?(u=Zt(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),i&&(i.valid=!1,i=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(ao.geo.conicEqualArea=function(){return Vt(Xt)}).raw=Xt,ao.geo.albers=function(){return ao.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ao.geo.albersUsa=function(){function n(n){var u=n[0],o=n[1];return t=null,e(u,o),t||(r(u,o),t)||i(u,o),t}var t,e,r,i,u=ao.geo.albers(),o=ao.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=ao.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=u.scale(),e=u.translate(),r=(n[0]-e[0])/t,i=(n[1]-e[1])/t;return(i>=.12&&.234>i&&r>=-.425&&-.214>r?o:i>=.166&&.234>i&&r>=-.214&&-.115>r?a:u).invert(n)},n.stream=function(n){var t=u.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,i){t.point(n,i),e.point(n,i),r.point(n,i)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(u.precision(t),o.precision(t),a.precision(t),n):u.precision()},n.scale=function(t){return arguments.length?(u.scale(t),o.scale(.35*t),a.scale(t),n.translate(u.translate())):u.scale()},n.translate=function(t){if(!arguments.length)return u.translate();var c=u.scale(),f=+t[0],s=+t[1];return e=u.translate(t).clipExtent([[f-.455*c,s-.238*c],[f+.455*c,s+.238*c]]).stream(l).point,r=o.translate([f-.307*c,s+.201*c]).clipExtent([[f-.425*c+Uo,s+.12*c+Uo],[f-.214*c-Uo,s+.234*c-Uo]]).stream(l).point,i=a.translate([f-.205*c,s+.212*c]).clipExtent([[f-.214*c+Uo,s+.166*c+Uo],[f-.115*c-Uo,s+.234*c-Uo]]).stream(l).point,n},n.scale(1070)};var Oa,Ia,Ya,Za,Va,Xa,$a={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Ia=0,$a.lineStart=$t},polygonEnd:function(){$a.lineStart=$a.lineEnd=$a.point=b,Oa+=xo(Ia/2)}},Ba={point:Bt,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Wa={point:Gt,lineStart:Kt,lineEnd:Qt,polygonStart:function(){Wa.lineStart=ne},polygonEnd:function(){Wa.point=Gt,Wa.lineStart=Kt,Wa.lineEnd=Qt}};ao.geo.path=function(){function n(n){return n&&("function"==typeof a&&u.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=i(u)),ao.geo.stream(n,o)),u.result()}function t(){return o=null,n}var e,r,i,u,o,a=4.5;return n.area=function(n){return Oa=0,ao.geo.stream(n,i($a)),Oa},n.centroid=function(n){return Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,i(Wa)),Ua?[Da/Ua,Pa/Ua]:Ra?[qa/Ra,Ta/Ra]:La?[Ca/La,za/La]:[NaN,NaN]},n.bounds=function(n){return Va=Xa=-(Ya=Za=1/0),ao.geo.stream(n,i(Ba)),[[Ya,Za],[Va,Xa]]},n.projection=function(n){return arguments.length?(i=(e=n)?n.stream||re(n):m,t()):e},n.context=function(n){return arguments.length?(u=null==(r=n)?new Wt:new te(n),"function"!=typeof a&&u.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(u.pointRadius(+t),+t),n):a},n.projection(ao.geo.albersUsa()).context(null)},ao.geo.transform=function(n){return{stream:function(t){var e=new ie(t);for(var r in n)e[r]=n[r];return e}}},ie.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},ao.geo.projection=oe,ao.geo.projectionMutator=ae,(ao.geo.equirectangular=function(){return oe(ce)}).raw=ce.invert=ce,ao.geo.rotation=function(n){function t(t){return t=n(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t}return n=se(n[0]%360*Yo,n[1]*Yo,n.length>2?n[2]*Yo:0),t.invert=function(t){return t=n.invert(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t},t},fe.invert=ce,ao.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=se(-n[0]*Yo,-n[1]*Yo,0).invert,i=[];return e(null,null,1,{point:function(n,e){i.push(n=t(n,e)),n[0]*=Zo,n[1]*=Zo}}),{type:"Polygon",coordinates:[i]}}var t,e,r=[0,0],i=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=ve((t=+r)*Yo,i*Yo),n):t},n.precision=function(r){return arguments.length?(e=ve(t*Yo,(i=+r)*Yo),n):i},n.angle(90)},ao.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Yo,i=n[1]*Yo,u=t[1]*Yo,o=Math.sin(r),a=Math.cos(r),l=Math.sin(i),c=Math.cos(i),f=Math.sin(u),s=Math.cos(u);return Math.atan2(Math.sqrt((e=s*o)*e+(e=c*f-l*s*a)*e),l*f+c*s*a)},ao.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ao.range(Math.ceil(u/d)*d,i,d).map(h).concat(ao.range(Math.ceil(c/y)*y,l,y).map(p)).concat(ao.range(Math.ceil(r/g)*g,e,g).filter(function(n){return xo(n%d)>Uo}).map(f)).concat(ao.range(Math.ceil(a/v)*v,o,v).filter(function(n){return xo(n%y)>Uo}).map(s))}var e,r,i,u,o,a,l,c,f,s,h,p,g=10,v=g,d=90,y=360,m=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(u).concat(p(l).slice(1),h(i).reverse().slice(1),p(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(u=+t[0][0],i=+t[1][0],c=+t[0][1],l=+t[1][1],u>i&&(t=u,u=i,i=t),c>l&&(t=c,c=l,l=t),n.precision(m)):[[u,c],[i,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(m)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],y=+t[1],n):[d,y]},n.minorStep=function(t){return arguments.length?(g=+t[0],v=+t[1],n):[g,v]},n.precision=function(t){return arguments.length?(m=+t,f=ye(a,o,90),s=me(r,e,m),h=ye(c,l,90),p=me(u,i,m),n):m},n.majorExtent([[-180,-90+Uo],[180,90-Uo]]).minorExtent([[-180,-80-Uo],[180,80+Uo]])},ao.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||i.apply(this,arguments)]}}var t,e,r=Me,i=xe;return n.distance=function(){return ao.geo.distance(t||r.apply(this,arguments),e||i.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(i=t,e="function"==typeof t?null:t,n):i},n.precision=function(){return arguments.length?n:0},n},ao.geo.interpolate=function(n,t){return be(n[0]*Yo,n[1]*Yo,t[0]*Yo,t[1]*Yo)},ao.geo.length=function(n){return Ja=0,ao.geo.stream(n,Ga),Ja};var Ja,Ga={sphere:b,point:b,lineStart:_e,lineEnd:b,polygonStart:b,polygonEnd:b},Ka=we(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ao.geo.azimuthalEqualArea=function(){return oe(Ka)}).raw=Ka;var Qa=we(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},m);(ao.geo.azimuthalEquidistant=function(){return oe(Qa)}).raw=Qa,(ao.geo.conicConformal=function(){return Vt(Se)}).raw=Se,(ao.geo.conicEquidistant=function(){return Vt(ke)}).raw=ke;var nl=we(function(n){return 1/n},Math.atan);(ao.geo.gnomonic=function(){return oe(nl)}).raw=nl,Ne.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Io]},(ao.geo.mercator=function(){return Ee(Ne)}).raw=Ne;var tl=we(function(){return 1},Math.asin);(ao.geo.orthographic=function(){return oe(tl)}).raw=tl;var el=we(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ao.geo.stereographic=function(){return oe(el)}).raw=el,Ae.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Io]},(ao.geo.transverseMercator=function(){var n=Ee(Ae),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ae,ao.geom={},ao.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,i=En(e),u=En(r),o=n.length,a=[],l=[];for(t=0;o>t;t++)a.push([+i.call(this,n[t],t),+u.call(this,n[t],t),t]);for(a.sort(qe),t=0;o>t;t++)l.push([a[t][0],-a[t][1]]);var c=Le(a),f=Le(l),s=f[0]===c[0],h=f[f.length-1]===c[c.length-1],p=[];for(t=c.length-1;t>=0;--t)p.push(n[a[c[t]][2]]);for(t=+s;t=r&&c.x<=u&&c.y>=i&&c.y<=o?[[r,o],[u,o],[u,i],[r,i]]:[];f.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(u(n,t)/Uo)*Uo,y:Math.round(o(n,t)/Uo)*Uo,i:t}})}var r=Ce,i=ze,u=r,o=i,a=sl;return n?t(n):(t.links=function(n){return ar(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return ar(e(n)).cells.forEach(function(e,r){for(var i,u,o=e.site,a=e.edges.sort(Ve),l=-1,c=a.length,f=a[c-1].edge,s=f.l===o?f.r:f.l;++l=c,h=r>=f,p=h<<1|s;n.leaf=!1,n=n.nodes[p]||(n.nodes[p]=hr()),s?i=c:a=c,h?o=f:l=f,u(n,t,e,r,i,o,a,l)}var f,s,h,p,g,v,d,y,m,M=En(a),x=En(l);if(null!=t)v=t,d=e,y=r,m=i;else if(y=m=-(v=d=1/0),s=[],h=[],g=n.length,o)for(p=0;g>p;++p)f=n[p],f.xy&&(y=f.x),f.y>m&&(m=f.y),s.push(f.x),h.push(f.y);else for(p=0;g>p;++p){var b=+M(f=n[p],p),_=+x(f,p);v>b&&(v=b),d>_&&(d=_),b>y&&(y=b),_>m&&(m=_),s.push(b),h.push(_)}var w=y-v,S=m-d;w>S?m=d+w:y=v+S;var k=hr();if(k.add=function(n){u(k,n,+M(n,++p),+x(n,p),v,d,y,m)},k.visit=function(n){pr(n,k,v,d,y,m)},k.find=function(n){return gr(k,n[0],n[1],v,d,y,m)},p=-1,null==t){for(;++p=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=vl.get(e)||gl,r=dl.get(r)||m,br(r(e.apply(null,lo.call(arguments,1))))},ao.interpolateHcl=Rr,ao.interpolateHsl=Dr,ao.interpolateLab=Pr,ao.interpolateRound=Ur,ao.transform=function(n){var t=fo.createElementNS(ao.ns.prefix.svg,"g");return(ao.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new jr(e?e.matrix:yl)})(n)},jr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var yl={a:1,b:0,c:0,d:1,e:0,f:0};ao.interpolateTransform=$r,ao.layout={},ao.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++ea*a/y){if(v>l){var c=t.charge/l;n.px-=u*c,n.py-=o*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=u*c,n.py-=o*c}}return!t.charge}}function t(n){n.px=ao.event.x,n.py=ao.event.y,l.resume()}var e,r,i,u,o,a,l={},c=ao.dispatch("start","tick","end"),f=[1,1],s=.9,h=ml,p=Ml,g=-30,v=xl,d=.1,y=.64,M=[],x=[];return l.tick=function(){if((i*=.99)<.005)return e=null,c.end({type:"end",alpha:i=0}),!0;var t,r,l,h,p,v,y,m,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,p=l.target,m=p.x-h.x,b=p.y-h.y,(v=m*m+b*b)&&(v=i*o[r]*((v=Math.sqrt(v))-u[r])/v,m*=v,b*=v,p.x-=m*(y=h.weight+p.weight?h.weight/(h.weight+p.weight):.5),p.y-=b*y,h.x+=m*(y=1-y),h.y+=b*y);if((y=i*d)&&(m=f[0]/2,b=f[1]/2,r=-1,y))for(;++r<_;)l=M[r],l.x+=(m-l.x)*y,l.y+=(b-l.y)*y;if(g)for(ri(t=ao.geom.quadtree(M),i,a),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*s,l.y-=(l.py-(l.py=l.y))*s);c.tick({type:"tick",alpha:i})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(f=n,l):f},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.friction=function(n){return arguments.length?(s=+n,l):s},l.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(y=n*n,l):Math.sqrt(y)},l.alpha=function(n){return arguments.length?(n=+n,i?n>0?i=n:(e.c=null,e.t=NaN,e=null,c.end({type:"end",alpha:i=0})):n>0&&(c.start({type:"start",alpha:i=n}),e=qn(l.tick)),l):i},l.start=function(){function n(n,r){if(!e){for(e=new Array(i),l=0;i>l;++l)e[l]=[];for(l=0;c>l;++l){var u=x[l];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var o,a=e[t],l=-1,f=a.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;i>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",s)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof h)for(t=0;c>t;++t)u[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)u[t]=h;if(o=[],"function"==typeof p)for(t=0;c>t;++t)o[t]=+p.call(this,x[t],t);else for(t=0;c>t;++t)o[t]=p;if(a=[],"function"==typeof g)for(t=0;i>t;++t)a[t]=+g.call(this,M[t],t);else for(t=0;i>t;++t)a[t]=g;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=ao.behavior.drag().origin(m).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",ni)),arguments.length?void this.on("mouseover.force",ti).on("mouseout.force",ei).call(r):r},ao.rebind(l,c,"on")};var ml=20,Ml=1,xl=1/0;ao.layout.hierarchy=function(){function n(i){var u,o=[i],a=[];for(i.depth=0;null!=(u=o.pop());)if(a.push(u),(c=e.call(n,u,u.depth))&&(l=c.length)){for(var l,c,f;--l>=0;)o.push(f=c[l]),f.parent=u,f.depth=u.depth+1;r&&(u.value=0),u.children=c}else r&&(u.value=+r.call(n,u,u.depth)||0),delete u.children;return oi(i,function(n){var e,i;t&&(e=n.children)&&e.sort(t),r&&(i=n.parent)&&(i.value+=n.value)}),a}var t=ci,e=ai,r=li;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(ui(t,function(n){n.children&&(n.value=0)}),oi(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},ao.layout.partition=function(){function n(t,e,r,i){var u=t.children;if(t.x=e,t.y=t.depth*i,t.dx=r,t.dy=i,u&&(o=u.length)){var o,a,l,c=-1;for(r=t.value?r/t.value:0;++cs?-1:1),g=ao.sum(c),v=g?(s-l*p)/g:0,d=ao.range(l),y=[];return null!=e&&d.sort(e===bl?function(n,t){return c[t]-c[n]}:function(n,t){return e(o[n],o[t])}),d.forEach(function(n){y[n]={data:o[n],value:a=c[n],startAngle:f,endAngle:f+=a*v+p,padAngle:h}}),y}var t=Number,e=bl,r=0,i=Ho,u=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(i=t,n):i},n.padAngle=function(t){return arguments.length?(u=t,n):u},n};var bl={};ao.layout.stack=function(){function n(a,l){if(!(h=a.length))return a;var c=a.map(function(e,r){return t.call(n,e,r)}),f=c.map(function(t){return t.map(function(t,e){return[u.call(n,t,e),o.call(n,t,e)]})}),s=e.call(n,f,l);c=ao.permute(c,s),f=ao.permute(f,s);var h,p,g,v,d=r.call(n,f,l),y=c[0].length;for(g=0;y>g;++g)for(i.call(n,c[0][g],v=d[g],f[0][g][1]),p=1;h>p;++p)i.call(n,c[p][g],v+=f[p-1][g][1],f[p][g][1]);return a}var t=m,e=gi,r=vi,i=pi,u=si,o=hi;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:_l.get(t)||gi,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:wl.get(t)||vi,n):r},n.x=function(t){return arguments.length?(u=t,n):u},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(i=t,n):i},n};var _l=ao.map({"inside-out":function(n){var t,e,r=n.length,i=n.map(di),u=n.map(yi),o=ao.range(r).sort(function(n,t){return i[n]-i[t]}),a=0,l=0,c=[],f=[];for(t=0;r>t;++t)e=o[t],l>a?(a+=u[e],c.push(e)):(l+=u[e],f.push(e));return f.reverse().concat(c)},reverse:function(n){return ao.range(n.length).reverse()},"default":gi}),wl=ao.map({silhouette:function(n){var t,e,r,i=n.length,u=n[0].length,o=[],a=0,l=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;u>e;++e)l[e]=(a-o[e])/2;return l},wiggle:function(n){var t,e,r,i,u,o,a,l,c,f=n.length,s=n[0],h=s.length,p=[];for(p[0]=l=c=0,e=1;h>e;++e){for(t=0,i=0;f>t;++t)i+=n[t][e][1];for(t=0,u=0,a=s[e][0]-s[e-1][0];f>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;u+=o*n[t][e][1]}p[e]=l-=i?u/i*a:0,c>l&&(c=l)}for(e=0;h>e;++e)p[e]-=c;return p},expand:function(n){var t,e,r,i=n.length,u=n[0].length,o=1/i,a=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];if(r)for(t=0;i>t;t++)n[t][e][1]/=r;else for(t=0;i>t;t++)n[t][e][1]=o}for(e=0;u>e;++e)a[e]=0;return a},zero:vi});ao.layout.histogram=function(){function n(n,u){for(var o,a,l=[],c=n.map(e,this),f=r.call(this,c,u),s=i.call(this,f,c,u),u=-1,h=c.length,p=s.length-1,g=t?1:1/h;++u0)for(u=-1;++u=f[0]&&a<=f[1]&&(o=l[ao.bisect(s,a,1,p)-1],o.y+=g,o.push(n[u]));return l}var t=!0,e=Number,r=bi,i=Mi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(i="number"==typeof t?function(n){return xi(n,t)}:En(t),n):i},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ao.layout.pack=function(){function n(n,u){var o=e.call(this,n,u),a=o[0],l=i[0],c=i[1],f=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,oi(a,function(n){n.r=+f(n.value)}),oi(a,Ni),r){var s=r*(t?1:Math.max(2*a.r/l,2*a.r/c))/2;oi(a,function(n){n.r+=s}),oi(a,Ni),oi(a,function(n){n.r-=s})}return Ci(a,l/2,c/2,t?1:1/Math.max(2*a.r/l,2*a.r/c)),o}var t,e=ao.layout.hierarchy().sort(_i),r=0,i=[1,1];return n.size=function(t){return arguments.length?(i=t,n):i},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},ii(n,e)},ao.layout.tree=function(){function n(n,i){var f=o.call(this,n,i),s=f[0],h=t(s);if(oi(h,e),h.parent.m=-h.z,ui(h,r),c)ui(s,u);else{var p=s,g=s,v=s;ui(s,function(n){n.xg.x&&(g=n),n.depth>v.depth&&(v=n)});var d=a(p,g)/2-p.x,y=l[0]/(g.x+a(g,p)/2+d),m=l[1]/(v.depth||1);ui(s,function(n){n.x=(n.x+d)*y,n.y=n.depth*m})}return f}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var i,u=t.children,o=0,a=u.length;a>o;++o)r.push((u[o]=i={_:u[o],parent:t,children:(i=u[o].children)&&i.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=i);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Di(n);var u=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-u):n.z=u}else r&&(n.z=r.z+a(n._,r._));n.parent.A=i(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function i(n,t,e){if(t){for(var r,i=n,u=n,o=t,l=i.parent.children[0],c=i.m,f=u.m,s=o.m,h=l.m;o=Ti(o),i=qi(i),o&&i;)l=qi(l),u=Ti(u),u.a=n,r=o.z+s-i.z-c+a(o._,i._),r>0&&(Ri(Pi(o,n,e),n,r),c+=r,f+=r),s+=o.m,c+=i.m,h+=l.m,f+=u.m;o&&!Ti(u)&&(u.t=o,u.m+=s-f),i&&!qi(l)&&(l.t=i,l.m+=c-h,e=n)}return e}function u(n){n.x*=l[0],n.y=n.depth*l[1]}var o=ao.layout.hierarchy().sort(null).value(null),a=Li,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(c=null==(l=t)?u:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:u,n):c?l:null},ii(n,o)},ao.layout.cluster=function(){function n(n,u){var o,a=t.call(this,n,u),l=a[0],c=0;oi(l,function(n){var t=n.children;t&&t.length?(n.x=ji(t),n.y=Ui(t)):(n.x=o?c+=e(n,o):0,n.y=0,o=n)});var f=Fi(l),s=Hi(l),h=f.x-e(f,s)/2,p=s.x+e(s,f)/2;return oi(l,i?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(p-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),a}var t=ao.layout.hierarchy().sort(null).value(null),e=Li,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ao.layout.treemap=function(){function n(n,t){for(var e,r,i=-1,u=n.length;++it?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var u=e.children;if(u&&u.length){var o,a,l,c=s(e),f=[],h=u.slice(),g=1/0,v="slice"===p?c.dx:"dice"===p?c.dy:"slice-dice"===p?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),f.area=0;(l=h.length)>0;)f.push(o=h[l-1]),f.area+=o.area,"squarify"!==p||(a=r(f,v))<=g?(h.pop(),g=a):(f.area-=f.pop().area,i(f,v,c,!1),v=Math.min(c.dx,c.dy),f.length=f.area=0,g=1/0);f.length&&(i(f,v,c,!0),f.length=f.area=0),u.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var u,o=s(t),a=r.slice(),l=[];for(n(a,o.dx*o.dy/t.value),l.area=0;u=a.pop();)l.push(u),l.area+=u.area,null!=u.z&&(i(l,u.z?o.dx:o.dy,o,!a.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,i=0,u=1/0,o=-1,a=n.length;++oe&&(u=e),e>i&&(i=e));return r*=r,t*=t,r?Math.max(t*i*g/r,r/(t*u*g)):1/0}function i(n,t,e,r){var i,u=-1,o=n.length,a=e.x,c=e.y,f=t?l(n.area/t):0; if(t==e.dx){for((r||f>e.dy)&&(f=e.dy);++ue.dx)&&(f=e.dx);++ue&&(t=1),1>e&&(n=0),function(){var e,r,i;do e=2*Math.random()-1,r=2*Math.random()-1,i=e*e+r*r;while(!i||i>1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var n=ao.random.normal.apply(ao,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ao.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ao.scale={};var Sl={floor:m,ceil:m};ao.scale.linear=function(){return Wi([0,1],[0,1],Mr,!1)};var kl={s:1,g:1,p:1,r:1,e:1};ao.scale.log=function(){return ru(ao.scale.linear().domain([0,1]),10,!0,[1,10])};var Nl=ao.format(".0e"),El={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ao.scale.pow=function(){return iu(ao.scale.linear(),1,[0,1])},ao.scale.sqrt=function(){return ao.scale.pow().exponent(.5)},ao.scale.ordinal=function(){return ou([],{t:"range",a:[[]]})},ao.scale.category10=function(){return ao.scale.ordinal().range(Al)},ao.scale.category20=function(){return ao.scale.ordinal().range(Cl)},ao.scale.category20b=function(){return ao.scale.ordinal().range(zl)},ao.scale.category20c=function(){return ao.scale.ordinal().range(Ll)};var Al=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Cl=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),zl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),Ll=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);ao.scale.quantile=function(){return au([],[])},ao.scale.quantize=function(){return lu(0,1,[0,1])},ao.scale.threshold=function(){return cu([.5],[0,1])},ao.scale.identity=function(){return fu([0,1])},ao.svg={},ao.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),f=o.apply(this,arguments)-Io,s=a.apply(this,arguments)-Io,h=Math.abs(s-f),p=f>s?0:1;if(n>c&&(g=c,c=n,n=g),h>=Oo)return t(c,p)+(n?t(n,1-p):"carview.php?tsp=")+"Z";var g,v,d,y,m,M,x,b,_,w,S,k,N=0,E=0,A=[];if((y=(+l.apply(this,arguments)||0)/2)&&(d=u===ql?Math.sqrt(n*n+c*c):+u.apply(this,arguments),p||(E*=-1),c&&(E=tn(d/c*Math.sin(y))),n&&(N=tn(d/n*Math.sin(y)))),c){m=c*Math.cos(f+E),M=c*Math.sin(f+E),x=c*Math.cos(s-E),b=c*Math.sin(s-E);var C=Math.abs(s-f-2*E)<=Fo?0:1;if(E&&yu(m,M,x,b)===p^C){var z=(f+s)/2;m=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else m=M=0;if(n){_=n*Math.cos(s-N),w=n*Math.sin(s-N),S=n*Math.cos(f+N),k=n*Math.sin(f+N);var L=Math.abs(f-s+2*N)<=Fo?0:1;if(N&&yu(_,w,S,k)===1-p^L){var q=(f+s)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Uo&&(g=Math.min(Math.abs(c-n)/2,+i.apply(this,arguments)))>.001){v=c>n^p?0:1;var T=g,R=g;if(Fo>h){var D=null==S?[_,w]:null==x?[m,M]:Re([m,M],[S,k],[x,b],[_,w]),P=m-D[0],U=M-D[1],j=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*j+U*F)/(Math.sqrt(P*P+U*U)*Math.sqrt(j*j+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(g,(n-O)/(H-1)),T=Math.min(g,(c-O)/(H+1))}if(null!=x){var I=mu(null==S?[_,w]:[S,k],[m,M],c,T,p),Y=mu([x,b],[_,w],c,T,p);g===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-p^yu(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",p," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",m,",",M);if(null!=S){var Z=mu([m,M],[S,k],n,-R,p),V=mu([_,w],null==x?[m,M]:[x,b],n,-R,p);g===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",p^yu(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-p," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",m,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",p," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-p," ",S,",",k);return A.push("Z"),A.join("carview.php?tsp=")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hu,r=pu,i=su,u=ql,o=gu,a=vu,l=du;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(i=En(t),n):i},n.padRadius=function(t){return arguments.length?(u=t==ql?ql:En(t),n):u},n.startAngle=function(t){return arguments.length?(o=En(t),n):o},n.endAngle=function(t){return arguments.length?(a=En(t),n):a},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Io;return[Math.cos(t)*n,Math.sin(t)*n]},n};var ql="auto";ao.svg.line=function(){return Mu(m)};var Tl=ao.map({linear:xu,"linear-closed":bu,step:_u,"step-before":wu,"step-after":Su,basis:zu,"basis-open":Lu,"basis-closed":qu,bundle:Tu,cardinal:Eu,"cardinal-open":ku,"cardinal-closed":Nu,monotone:Fu});Tl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Rl=[0,2/3,1/3,0],Dl=[0,1/3,2/3,0],Pl=[0,1/6,2/3,1/6];ao.svg.line.radial=function(){var n=Mu(Hu);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wu.reverse=Su,Su.reverse=wu,ao.svg.area=function(){return Ou(m)},ao.svg.area.radial=function(){var n=Ou(Hu);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ao.svg.chord=function(){function n(n,a){var l=t(this,u,n,a),c=t(this,o,n,a);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?i(l.r,l.p1,l.r,l.p0):i(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+i(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var i=t.call(n,e,r),u=a.call(n,i,r),o=l.call(n,i,r)-Io,f=c.call(n,i,r)-Io;return{r:u,a0:o,a1:f,p0:[u*Math.cos(o),u*Math.sin(o)],p1:[u*Math.cos(f),u*Math.sin(f)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Fo)+",1 "+t}function i(n,t,e,r){return"Q 0,0 "+r}var u=Me,o=xe,a=Iu,l=gu,c=vu;return n.radius=function(t){return arguments.length?(a=En(t),n):a},n.source=function(t){return arguments.length?(u=En(t),n):u},n.target=function(t){return arguments.length?(o=En(t),n):o},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},ao.svg.diagonal=function(){function n(n,i){var u=t.call(this,n,i),o=e.call(this,n,i),a=(u.y+o.y)/2,l=[u,{x:u.x,y:a},{x:o.x,y:a},o];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yu;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ao.svg.diagonal.radial=function(){var n=ao.svg.diagonal(),t=Yu,e=n.projection;return n.projection=function(n){return arguments.length?e(Zu(t=n)):t},n},ao.svg.symbol=function(){function n(n,r){return(Ul.get(t.call(this,n,r))||$u)(e.call(this,n,r))}var t=Xu,e=Vu;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Ul=ao.map({circle:$u,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Fl)),e=t*Fl;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ao.svg.symbolTypes=Ul.keys();var jl=Math.sqrt(3),Fl=Math.tan(30*Yo);Co.transition=function(n){for(var t,e,r=Hl||++Zl,i=Ku(n),u=[],o=Ol||{time:Date.now(),ease:Nr,delay:0,duration:250},a=-1,l=this.length;++au;u++){i.push(t=[]);for(var e=this[u],a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return Wu(i,this.namespace,this.id)},Yl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(i){i[r][e].tween.set(n,t)})},Yl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function i(n){return null==n?e:(n+="carview.php?tsp=",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function u(n){return null==n?r:(n+="carview.php?tsp=",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?$r:Mr,a=ao.ns.qualify(n);return Ju(this,"attr."+n,t,a.local?u:i)},Yl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(i));return r&&function(n){this.setAttribute(i,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(i.space,i.local));return r&&function(n){this.setAttributeNS(i.space,i.local,r(n))}}var i=ao.ns.qualify(n);return this.tween("attr."+n,i.local?r:e)},Yl.style=function(n,e,r){function i(){this.style.removeProperty(n)}function u(e){return null==e?i:(e+="carview.php?tsp=",function(){var i,u=t(this).getComputedStyle(this,null).getPropertyValue(n);return u!==e&&(i=Mr(u,e),function(t){this.style.setProperty(n,i(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="carview.php?tsp=");for(r in n)this.style(r,n[r],e);return this}r="carview.php?tsp="}return Ju(this,"style."+n,e,u)},Yl.styleTween=function(n,e,r){function i(i,u){var o=e.call(this,i,u,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r="carview.php?tsp="),this.tween("style."+n,i)},Yl.text=function(n){return Ju(this,"text",n,Gu)},Yl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Yl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ao.ease.apply(ao,arguments)),Y(this,function(r){r[e][t].ease=n}))},Yl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,i,u){r[e][t].delay=+n.call(r,r.__data__,i,u)}:(n=+n,function(r){r[e][t].delay=n}))},Yl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,i,u){r[e][t].duration=Math.max(1,n.call(r,r.__data__,i,u))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Yl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var i=Ol,u=Hl;try{Hl=e,Y(this,function(t,i,u){Ol=t[r][e],n.call(t,t.__data__,i,u)})}finally{Ol=i,Hl=u}}else Y(this,function(i){var u=i[r][e];(u.event||(u.event=ao.dispatch("start","end","interrupt"))).on(n,t)});return this},Yl.transition=function(){for(var n,t,e,r,i=this.id,u=++Zl,o=this.namespace,a=[],l=0,c=this.length;c>l;l++){a.push(n=[]);for(var t=this[l],f=0,s=t.length;s>f;f++)(e=t[f])&&(r=e[o][i],Qu(e,f,o,u,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wu(a,o,u)},ao.svg.axis=function(){function n(n){n.each(function(){var n,c=ao.select(this),f=this.__chart__||e,s=this.__chart__=e.copy(),h=null==l?s.ticks?s.ticks.apply(s,a):s.domain():l,p=null==t?s.tickFormat?s.tickFormat.apply(s,a):m:t,g=c.selectAll(".tick").data(h,s),v=g.enter().insert("g",".domain").attr("class","tick").style("opacity",Uo),d=ao.transition(g.exit()).style("opacity",Uo).remove(),y=ao.transition(g.order()).style("opacity",1),M=Math.max(i,0)+o,x=Zi(s),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),ao.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=y.select("line"),C=g.select("text").text(p),z=v.select("text"),L=y.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=no,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*u+"V0H"+x[1]+"V"+q*u)):(n=to,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*u+","+x[0]+"H0V"+x[1]+"H"+q*u)),E.attr(N,q*i),z.attr(k,q*M),A.attr(S,0).attr(N,q*i),L.attr(w,0).attr(k,q*M),s.rangeBand){var T=s,R=T.rangeBand()/2;f=s=function(n){return T(n)+R}}else f.rangeBand?f=s:d.call(n,s,f);v.call(n,f,s),y.call(n,s,s)})}var t,e=ao.scale.linear(),r=Vl,i=6,u=6,o=3,a=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Xl?t+"carview.php?tsp=":Vl,n):r},n.ticks=function(){return arguments.length?(a=co(arguments),n):a},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(i=+t,u=+arguments[e-1],n):i},n.innerTickSize=function(t){return arguments.length?(i=+t,n):i},n.outerTickSize=function(t){return arguments.length?(u=+t,n):u},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Vl="bottom",Xl={top:1,right:1,bottom:1,left:1};ao.svg.brush=function(){function n(t){t.each(function(){var t=ao.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,m);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return $l[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,s=ao.transition(t),h=ao.transition(o);c&&(l=Zi(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(s)),f&&(l=Zi(f),h.attr("y",l[0]).attr("height",l[1]-l[0]),i(s)),e(s)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",s[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1]-s[0])}function i(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function u(){function u(){32==ao.event.keyCode&&(C||(M=null,L[0]-=s[1],L[1]-=h[1],C=2),S())}function v(){32==ao.event.keyCode&&2==C&&(L[0]+=s[1],L[1]+=h[1],C=0,S())}function d(){var n=ao.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(ao.event.altKey?(M||(M=[(s[0]+s[1])/2,(h[0]+h[1])/2]),L[0]=s[+(n[0]f?(i=r,r=f):i=f),v[0]!=r||v[1]!=i?(e?a=null:o=null,v[0]=r,v[1]=i,!0):void 0}function m(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ao.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=ao.select(ao.event.target),w=l.of(b,arguments),k=ao.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&f,C=_.classed("extent"),z=W(b),L=ao.mouse(b),q=ao.select(t(b)).on("keydown.brush",u).on("keyup.brush",v);if(ao.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",m):q.on("mousemove.brush",d).on("mouseup.brush",m),k.interrupt().selectAll("*").interrupt(),C)L[0]=s[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[s[1-T]-L[0],h[1-R]-L[1]],L[0]=s[T],L[1]=h[R]}else ao.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),ao.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,l=N(n,"brushstart","brush","brushend"),c=null,f=null,s=[0,0],h=[0,0],p=!0,g=!0,v=Bl[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:s,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Hl?ao.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,s=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(s,t.x),r=xr(h,t.y);return o=a=null,function(i){s=t.x=e(i),h=t.y=r(i),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=Bl[!c<<1|!f],n):c},n.y=function(t){return arguments.length?(f=t,v=Bl[!c<<1|!f],n):f},n.clamp=function(t){return arguments.length?(c&&f?(p=!!t[0],g=!!t[1]):c?p=!!t:f&&(g=!!t),n):c&&f?[p,g]:c?p:f?g:null},n.extent=function(t){var e,r,i,u,l;return arguments.length?(c&&(e=t[0],r=t[1],f&&(e=e[0],r=r[0]),o=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),e==s[0]&&r==s[1]||(s=[e,r])),f&&(i=t[0],u=t[1],c&&(i=i[1],u=u[1]),a=[i,u],f.invert&&(i=f(i),u=f(u)),i>u&&(l=i,i=u,u=l),i==h[0]&&u==h[1]||(h=[i,u])),n):(c&&(o?(e=o[0],r=o[1]):(e=s[0],r=s[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),f&&(a?(i=a[0],u=a[1]):(i=h[0],u=h[1],f.invert&&(i=f.invert(i),u=f.invert(u)),i>u&&(l=i,i=u,u=l))),c&&f?[[e,i],[r,u]]:c?[e,r]:f&&[i,u])},n.clear=function(){return n.empty()||(s=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!c&&s[0]==s[1]||!!f&&h[0]==h[1]},ao.rebind(n,l,"on")};var $l={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Bl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Wl=ga.format=xa.timeFormat,Jl=Wl.utc,Gl=Jl("%Y-%m-%dT%H:%M:%S.%LZ");Wl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?eo:Gl,eo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},eo.toString=Gl.toString,ga.second=On(function(n){return new va(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ga.seconds=ga.second.range,ga.seconds.utc=ga.second.utc.range,ga.minute=On(function(n){return new va(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ga.minutes=ga.minute.range,ga.minutes.utc=ga.minute.utc.range,ga.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new va(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ga.hours=ga.hour.range,ga.hours.utc=ga.hour.utc.range,ga.month=On(function(n){return n=ga.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ga.months=ga.month.range,ga.months.utc=ga.month.utc.range;var Kl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ql=[[ga.second,1],[ga.second,5],[ga.second,15],[ga.second,30],[ga.minute,1],[ga.minute,5],[ga.minute,15],[ga.minute,30],[ga.hour,1],[ga.hour,3],[ga.hour,6],[ga.hour,12],[ga.day,1],[ga.day,2],[ga.week,1],[ga.month,1],[ga.month,3],[ga.year,1]],nc=Wl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),tc={range:function(n,t,e){return ao.range(Math.ceil(n/e)*e,+t,e).map(io)},floor:m,ceil:m};Ql.year=ga.year,ga.scale=function(){return ro(ao.scale.linear(),Ql,nc)};var ec=Ql.map(function(n){return[n[0].utc,n[1]]}),rc=Jl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);ec.year=ga.year.utc,ga.scale.utc=function(){return ro(ao.scale.linear(),ec,rc)},ao.text=An(function(n){return n.responseText}),ao.json=function(n,t){return Cn(n,"application/json",uo,t)},ao.html=function(n,t){return Cn(n,"text/html",oo,t)},ao.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=ao,define(ao)):"object"==typeof module&&module.exports?module.exports=ao:this.d3=ao}(); $(function() { var $window = $(window) , $top_link = $('#toplink') , $body = $('body, html') , offset = $('#code').offset().top , hidePopover = function ($target) { $target.data('popover-hover', false); setTimeout(function () { if (!$target.data('popover-hover')) { $target.popover('hide'); } }, 300); }; $top_link.hide().click(function(event) { event.preventDefault(); $body.animate({scrollTop:0}, 800); }); $window.scroll(function() { if($window.scrollTop() > offset) { $top_link.fadeIn(); } else { $top_link.fadeOut(); } }).scroll(); $('.popin') .popover({trigger: 'manual'}) .on({ 'mouseenter.popover': function () { var $target = $(this); var $container = $target.children().first(); $target.data('popover-hover', true); // popover already displayed if ($target.next('.popover').length) { return; } // show the popover $container.popover('show'); // register mouse events on the popover $target.next('.popover:not(.popover-initialized)') .on({ 'mouseenter': function () { $target.data('popover-hover', true); }, 'mouseleave': function () { hidePopover($container); } }) .addClass('popover-initialized'); }, 'mouseleave.popover': function () { hidePopover($(this).children().first()); } }); });
{{percent}}% covered ({{level}})
templatePath . 'file.html', '{{', '}}'); $template->setVar( [ 'items' => $this->renderItems($node), 'lines' => $this->renderSource($node), ] ); $this->setCommonTemplateVariables($template, $node); $template->renderTo($file); } protected function renderItems(FileNode $node): string { $template = new \Text_Template($this->templatePath . 'file_item.html', '{{', '}}'); $methodItemTemplate = new \Text_Template( $this->templatePath . 'method_item.html', '{{', '}}' ); $items = $this->renderItemTemplate( $template, [ 'name' => 'Total', 'numClasses' => $node->getNumClassesAndTraits(), 'numTestedClasses' => $node->getNumTestedClassesAndTraits(), 'numMethods' => $node->getNumFunctionsAndMethods(), 'numTestedMethods' => $node->getNumTestedFunctionsAndMethods(), 'linesExecutedPercent' => $node->getLineExecutedPercent(false), 'linesExecutedPercentAsString' => $node->getLineExecutedPercent(), 'numExecutedLines' => $node->getNumExecutedLines(), 'numExecutableLines' => $node->getNumExecutableLines(), 'testedMethodsPercent' => $node->getTestedFunctionsAndMethodsPercent(false), 'testedMethodsPercentAsString' => $node->getTestedFunctionsAndMethodsPercent(), 'testedClassesPercent' => $node->getTestedClassesAndTraitsPercent(false), 'testedClassesPercentAsString' => $node->getTestedClassesAndTraitsPercent(), 'crap' => 'CRAP', ] ); $items .= $this->renderFunctionItems( $node->getFunctions(), $methodItemTemplate ); $items .= $this->renderTraitOrClassItems( $node->getTraits(), $template, $methodItemTemplate ); $items .= $this->renderTraitOrClassItems( $node->getClasses(), $template, $methodItemTemplate ); return $items; } protected function renderTraitOrClassItems(array $items, \Text_Template $template, \Text_Template $methodItemTemplate): string { $buffer = ''; if (empty($items)) { return $buffer; } foreach ($items as $name => $item) { $numMethods = 0; $numTestedMethods = 0; foreach ($item['methods'] as $method) { if ($method['executableLines'] > 0) { $numMethods++; if ($method['executedLines'] === $method['executableLines']) { $numTestedMethods++; } } } if ($item['executableLines'] > 0) { $numClasses = 1; $numTestedClasses = $numTestedMethods == $numMethods ? 1 : 0; $linesExecutedPercentAsString = Util::percent( $item['executedLines'], $item['executableLines'], true ); } else { $numClasses = 'n/a'; $numTestedClasses = 'n/a'; $linesExecutedPercentAsString = 'n/a'; } $buffer .= $this->renderItemTemplate( $template, [ 'name' => $this->abbreviateClassName($name), 'numClasses' => $numClasses, 'numTestedClasses' => $numTestedClasses, 'numMethods' => $numMethods, 'numTestedMethods' => $numTestedMethods, 'linesExecutedPercent' => Util::percent( $item['executedLines'], $item['executableLines'], false ), 'linesExecutedPercentAsString' => $linesExecutedPercentAsString, 'numExecutedLines' => $item['executedLines'], 'numExecutableLines' => $item['executableLines'], 'testedMethodsPercent' => Util::percent( $numTestedMethods, $numMethods ), 'testedMethodsPercentAsString' => Util::percent( $numTestedMethods, $numMethods, true ), 'testedClassesPercent' => Util::percent( $numTestedMethods == $numMethods ? 1 : 0, 1 ), 'testedClassesPercentAsString' => Util::percent( $numTestedMethods == $numMethods ? 1 : 0, 1, true ), 'crap' => $item['crap'], ] ); foreach ($item['methods'] as $method) { $buffer .= $this->renderFunctionOrMethodItem( $methodItemTemplate, $method, ' ' ); } } return $buffer; } protected function renderFunctionItems(array $functions, \Text_Template $template): string { if (empty($functions)) { return ''; } $buffer = ''; foreach ($functions as $function) { $buffer .= $this->renderFunctionOrMethodItem( $template, $function ); } return $buffer; } protected function renderFunctionOrMethodItem(\Text_Template $template, array $item, string $indent = ''): string { $numMethods = 0; $numTestedMethods = 0; if ($item['executableLines'] > 0) { $numMethods = 1; if ($item['executedLines'] === $item['executableLines']) { $numTestedMethods = 1; } } return $this->renderItemTemplate( $template, [ 'name' => \sprintf( '%s%s', $indent, $item['startLine'], \htmlspecialchars($item['signature'], $this->htmlSpecialCharsFlags), $item['functionName'] ?? $item['methodName'] ), 'numMethods' => $numMethods, 'numTestedMethods' => $numTestedMethods, 'linesExecutedPercent' => Util::percent( $item['executedLines'], $item['executableLines'] ), 'linesExecutedPercentAsString' => Util::percent( $item['executedLines'], $item['executableLines'], true ), 'numExecutedLines' => $item['executedLines'], 'numExecutableLines' => $item['executableLines'], 'testedMethodsPercent' => Util::percent( $numTestedMethods, 1 ), 'testedMethodsPercentAsString' => Util::percent( $numTestedMethods, 1, true ), 'crap' => $item['crap'], ] ); } protected function renderSource(FileNode $node): string { $coverageData = $node->getCoverageData(); $testData = $node->getTestData(); $codeLines = $this->loadFile($node->getPath()); $lines = ''; $i = 1; foreach ($codeLines as $line) { $trClass = ''; $popoverContent = ''; $popoverTitle = ''; if (\array_key_exists($i, $coverageData)) { $numTests = ($coverageData[$i] ? \count($coverageData[$i]) : 0); if ($coverageData[$i] === null) { $trClass = ' class="warning"'; } elseif ($numTests == 0) { $trClass = ' class="danger"'; } else { $lineCss = 'covered-by-large-tests'; $popoverContent = '
    '; if ($numTests > 1) { $popoverTitle = $numTests . ' tests cover line ' . $i; } else { $popoverTitle = '1 test covers line ' . $i; } foreach ($coverageData[$i] as $test) { if ($lineCss == 'covered-by-large-tests' && $testData[$test]['size'] == 'medium') { $lineCss = 'covered-by-medium-tests'; } elseif ($testData[$test]['size'] == 'small') { $lineCss = 'covered-by-small-tests'; } switch ($testData[$test]['status']) { case 0: switch ($testData[$test]['size']) { case 'small': $testCSS = ' class="covered-by-small-tests"'; break; case 'medium': $testCSS = ' class="covered-by-medium-tests"'; break; default: $testCSS = ' class="covered-by-large-tests"'; break; } break; case 1: case 2: $testCSS = ' class="warning"'; break; case 3: $testCSS = ' class="danger"'; break; case 4: $testCSS = ' class="danger"'; break; default: $testCSS = ''; } $popoverContent .= \sprintf( '%s', $testCSS, \htmlspecialchars($test, $this->htmlSpecialCharsFlags) ); } $popoverContent .= '
'; $trClass = ' class="' . $lineCss . ' popin"'; } } $popover = ''; if (!empty($popoverTitle)) { $popover = \sprintf( ' data-title="carview.php?tsp=%s" data-content="carview.php?tsp=%s" data-placement="top" data-html="true"', $popoverTitle, \htmlspecialchars($popoverContent, $this->htmlSpecialCharsFlags) ); } $lines .= \sprintf( ' %s' . "\n", $trClass, $popover, $i, $i, $i, $line ); $i++; } return $lines; } protected function loadFile($file): array { $buffer = \file_get_contents($file); $tokens = \token_get_all($buffer); $result = ['']; $i = 0; $stringFlag = false; $fileEndsWithNewLine = \substr($buffer, -1) == "\n"; unset($buffer); foreach ($tokens as $j => $token) { if (\is_string($token)) { if ($token === '"' && $tokens[$j - 1] !== '\\') { $result[$i] .= \sprintf( '%s', \htmlspecialchars($token, $this->htmlSpecialCharsFlags) ); $stringFlag = !$stringFlag; } else { $result[$i] .= \sprintf( '%s', \htmlspecialchars($token, $this->htmlSpecialCharsFlags) ); } continue; } [$token, $value] = $token; $value = \str_replace( ["\t", ' '], ['    ', ' '], \htmlspecialchars($value, $this->htmlSpecialCharsFlags) ); if ($value === "\n") { $result[++$i] = ''; } else { $lines = \explode("\n", $value); foreach ($lines as $jj => $line) { $line = \trim($line); if ($line !== '') { if ($stringFlag) { $colour = 'string'; } else { switch ($token) { case \T_INLINE_HTML: $colour = 'html'; break; case \T_COMMENT: case \T_DOC_COMMENT: $colour = 'comment'; break; case \T_ABSTRACT: case \T_ARRAY: case \T_AS: case \T_BREAK: case \T_CALLABLE: case \T_CASE: case \T_CATCH: case \T_CLASS: case \T_CLONE: case \T_CONTINUE: case \T_DEFAULT: case \T_ECHO: case \T_ELSE: case \T_ELSEIF: case \T_EMPTY: case \T_ENDDECLARE: case \T_ENDFOR: case \T_ENDFOREACH: case \T_ENDIF: case \T_ENDSWITCH: case \T_ENDWHILE: case \T_EXIT: case \T_EXTENDS: case \T_FINAL: case \T_FINALLY: case \T_FOREACH: case \T_FUNCTION: case \T_GLOBAL: case \T_IF: case \T_IMPLEMENTS: case \T_INCLUDE: case \T_INCLUDE_ONCE: case \T_INSTANCEOF: case \T_INSTEADOF: case \T_INTERFACE: case \T_ISSET: case \T_LOGICAL_AND: case \T_LOGICAL_OR: case \T_LOGICAL_XOR: case \T_NAMESPACE: case \T_NEW: case \T_PRIVATE: case \T_PROTECTED: case \T_PUBLIC: case \T_REQUIRE: case \T_REQUIRE_ONCE: case \T_RETURN: case \T_STATIC: case \T_THROW: case \T_TRAIT: case \T_TRY: case \T_UNSET: case \T_USE: case \T_VAR: case \T_WHILE: case \T_YIELD: $colour = 'keyword'; break; default: $colour = 'default'; } } $result[$i] .= \sprintf( '%s', $colour, $line ); } if (isset($lines[$jj + 1])) { $result[++$i] = ''; } } } } if ($fileEndsWithNewLine) { unset($result[\count($result) - 1]); } return $result; } private function abbreviateClassName(string $className): string { $tmp = \explode('\\', $className); if (\count($tmp) > 1) { $className = \sprintf( '%s', $className, \array_pop($tmp) ); } return $className; } } formatOutput = true; $xmlCoverage = $xmlDocument->createElement('coverage'); $xmlCoverage->setAttribute('generated', (string) $_SERVER['REQUEST_TIME']); $xmlDocument->appendChild($xmlCoverage); $xmlProject = $xmlDocument->createElement('project'); $xmlProject->setAttribute('timestamp', (string) $_SERVER['REQUEST_TIME']); if (\is_string($name)) { $xmlProject->setAttribute('name', $name); } $xmlCoverage->appendChild($xmlProject); $packages = []; $report = $coverage->getReport(); foreach ($report as $item) { if (!$item instanceof File) { continue; } $xmlFile = $xmlDocument->createElement('file'); $xmlFile->setAttribute('name', $item->getPath()); $classes = $item->getClassesAndTraits(); $coverageData = $item->getCoverageData(); $lines = []; $namespace = 'global'; foreach ($classes as $className => $class) { $classStatements = 0; $coveredClassStatements = 0; $coveredMethods = 0; $classMethods = 0; foreach ($class['methods'] as $methodName => $method) { if ($method['executableLines'] == 0) { continue; } $classMethods++; $classStatements += $method['executableLines']; $coveredClassStatements += $method['executedLines']; if ($method['coverage'] == 100) { $coveredMethods++; } $methodCount = 0; foreach (\range($method['startLine'], $method['endLine']) as $line) { if (isset($coverageData[$line]) && ($coverageData[$line] !== null)) { $methodCount = \max($methodCount, \count($coverageData[$line])); } } $lines[$method['startLine']] = [ 'ccn' => $method['ccn'], 'count' => $methodCount, 'crap' => $method['crap'], 'type' => 'method', 'visibility' => $method['visibility'], 'name' => $methodName, ]; } if (!empty($class['package']['namespace'])) { $namespace = $class['package']['namespace']; } $xmlClass = $xmlDocument->createElement('class'); $xmlClass->setAttribute('name', $className); $xmlClass->setAttribute('namespace', $namespace); if (!empty($class['package']['fullPackage'])) { $xmlClass->setAttribute( 'fullPackage', $class['package']['fullPackage'] ); } if (!empty($class['package']['category'])) { $xmlClass->setAttribute( 'category', $class['package']['category'] ); } if (!empty($class['package']['package'])) { $xmlClass->setAttribute( 'package', $class['package']['package'] ); } if (!empty($class['package']['subpackage'])) { $xmlClass->setAttribute( 'subpackage', $class['package']['subpackage'] ); } $xmlFile->appendChild($xmlClass); $xmlMetrics = $xmlDocument->createElement('metrics'); $xmlMetrics->setAttribute('complexity', (string) $class['ccn']); $xmlMetrics->setAttribute('methods', (string) $classMethods); $xmlMetrics->setAttribute('coveredmethods', (string) $coveredMethods); $xmlMetrics->setAttribute('conditionals', '0'); $xmlMetrics->setAttribute('coveredconditionals', '0'); $xmlMetrics->setAttribute('statements', (string) $classStatements); $xmlMetrics->setAttribute('coveredstatements', (string) $coveredClassStatements); $xmlMetrics->setAttribute('elements', (string) ($classMethods + $classStatements )); $xmlMetrics->setAttribute('coveredelements', (string) ($coveredMethods + $coveredClassStatements )); $xmlClass->appendChild($xmlMetrics); } foreach ($coverageData as $line => $data) { if ($data === null || isset($lines[$line])) { continue; } $lines[$line] = [ 'count' => \count($data), 'type' => 'stmt', ]; } \ksort($lines); foreach ($lines as $line => $data) { $xmlLine = $xmlDocument->createElement('line'); $xmlLine->setAttribute('num', (string) $line); $xmlLine->setAttribute('type', $data['type']); if (isset($data['name'])) { $xmlLine->setAttribute('name', $data['name']); } if (isset($data['visibility'])) { $xmlLine->setAttribute('visibility', $data['visibility']); } if (isset($data['ccn'])) { $xmlLine->setAttribute('complexity', (string) $data['ccn']); } if (isset($data['crap'])) { $xmlLine->setAttribute('crap', (string) $data['crap']); } $xmlLine->setAttribute('count', (string) $data['count']); $xmlFile->appendChild($xmlLine); } $linesOfCode = $item->getLinesOfCode(); $xmlMetrics = $xmlDocument->createElement('metrics'); $xmlMetrics->setAttribute('loc', (string) $linesOfCode['loc']); $xmlMetrics->setAttribute('ncloc', (string) $linesOfCode['ncloc']); $xmlMetrics->setAttribute('classes', (string) $item->getNumClassesAndTraits()); $xmlMetrics->setAttribute('methods', (string) $item->getNumMethods()); $xmlMetrics->setAttribute('coveredmethods', (string) $item->getNumTestedMethods()); $xmlMetrics->setAttribute('conditionals', '0'); $xmlMetrics->setAttribute('coveredconditionals', '0'); $xmlMetrics->setAttribute('statements', (string) $item->getNumExecutableLines()); $xmlMetrics->setAttribute('coveredstatements', (string) $item->getNumExecutedLines()); $xmlMetrics->setAttribute('elements', (string) ($item->getNumMethods() + $item->getNumExecutableLines() )); $xmlMetrics->setAttribute('coveredelements', (string) ($item->getNumTestedMethods() + $item->getNumExecutedLines() )); $xmlFile->appendChild($xmlMetrics); if ($namespace === 'global') { $xmlProject->appendChild($xmlFile); } else { if (!isset($packages[$namespace])) { $packages[$namespace] = $xmlDocument->createElement( 'package' ); $packages[$namespace]->setAttribute('name', $namespace); $xmlProject->appendChild($packages[$namespace]); } $packages[$namespace]->appendChild($xmlFile); } } $linesOfCode = $report->getLinesOfCode(); $xmlMetrics = $xmlDocument->createElement('metrics'); $xmlMetrics->setAttribute('files', (string) \count($report)); $xmlMetrics->setAttribute('loc', (string) $linesOfCode['loc']); $xmlMetrics->setAttribute('ncloc', (string) $linesOfCode['ncloc']); $xmlMetrics->setAttribute('classes', (string) $report->getNumClassesAndTraits()); $xmlMetrics->setAttribute('methods', (string) $report->getNumMethods()); $xmlMetrics->setAttribute('coveredmethods', (string) $report->getNumTestedMethods()); $xmlMetrics->setAttribute('conditionals', '0'); $xmlMetrics->setAttribute('coveredconditionals', '0'); $xmlMetrics->setAttribute('statements', (string) $report->getNumExecutableLines()); $xmlMetrics->setAttribute('coveredstatements', (string) $report->getNumExecutedLines()); $xmlMetrics->setAttribute('elements', (string) ($report->getNumMethods() + $report->getNumExecutableLines() )); $xmlMetrics->setAttribute('coveredelements', (string) ($report->getNumTestedMethods() + $report->getNumExecutedLines() )); $xmlProject->appendChild($xmlMetrics); $buffer = $xmlDocument->saveXML(); if ($target !== null) { if (!$this->createDirectory(\dirname($target))) { throw new \RuntimeException(\sprintf('Directory "carview.php?tsp=%s" was not created', \dirname($target))); } if (@\file_put_contents($target, $buffer) === false) { throw new RuntimeException( \sprintf( 'Could not write to "%s', $target ) ); } } return $buffer; } private function createDirectory(string $directory): bool { return !(!\is_dir($directory) && !@\mkdir($directory, 0777, true) && !\is_dir($directory)); } } threshold = $threshold; } public function process(CodeCoverage $coverage, ?string $target = null, ?string $name = null): string { $document = new \DOMDocument('1.0', 'UTF-8'); $document->formatOutput = true; $root = $document->createElement('crap_result'); $document->appendChild($root); $project = $document->createElement('project', \is_string($name) ? $name : ''); $root->appendChild($project); $root->appendChild($document->createElement('timestamp', \date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']))); $stats = $document->createElement('stats'); $methodsNode = $document->createElement('methods'); $report = $coverage->getReport(); unset($coverage); $fullMethodCount = 0; $fullCrapMethodCount = 0; $fullCrapLoad = 0; $fullCrap = 0; foreach ($report as $item) { $namespace = 'global'; if (!$item instanceof File) { continue; } $file = $document->createElement('file'); $file->setAttribute('name', $item->getPath()); $classes = $item->getClassesAndTraits(); foreach ($classes as $className => $class) { foreach ($class['methods'] as $methodName => $method) { $crapLoad = $this->getCrapLoad($method['crap'], $method['ccn'], $method['coverage']); $fullCrap += $method['crap']; $fullCrapLoad += $crapLoad; $fullMethodCount++; if ($method['crap'] >= $this->threshold) { $fullCrapMethodCount++; } $methodNode = $document->createElement('method'); if (!empty($class['package']['namespace'])) { $namespace = $class['package']['namespace']; } $methodNode->appendChild($document->createElement('package', $namespace)); $methodNode->appendChild($document->createElement('className', $className)); $methodNode->appendChild($document->createElement('methodName', $methodName)); $methodNode->appendChild($document->createElement('methodSignature', \htmlspecialchars($method['signature']))); $methodNode->appendChild($document->createElement('fullMethod', \htmlspecialchars($method['signature']))); $methodNode->appendChild($document->createElement('crap', (string) $this->roundValue($method['crap']))); $methodNode->appendChild($document->createElement('complexity', (string) $method['ccn'])); $methodNode->appendChild($document->createElement('coverage', (string) $this->roundValue($method['coverage']))); $methodNode->appendChild($document->createElement('crapLoad', (string) \round($crapLoad))); $methodsNode->appendChild($methodNode); } } } $stats->appendChild($document->createElement('name', 'Method Crap Stats')); $stats->appendChild($document->createElement('methodCount', (string) $fullMethodCount)); $stats->appendChild($document->createElement('crapMethodCount', (string) $fullCrapMethodCount)); $stats->appendChild($document->createElement('crapLoad', (string) \round($fullCrapLoad))); $stats->appendChild($document->createElement('totalCrap', (string) $fullCrap)); $crapMethodPercent = 0; if ($fullMethodCount > 0) { $crapMethodPercent = $this->roundValue((100 * $fullCrapMethodCount) / $fullMethodCount); } $stats->appendChild($document->createElement('crapMethodPercent', (string) $crapMethodPercent)); $root->appendChild($stats); $root->appendChild($methodsNode); $buffer = $document->saveXML(); if ($target !== null) { if (!$this->createDirectory(\dirname($target))) { throw new \RuntimeException(\sprintf('Directory "carview.php?tsp=%s" was not created', \dirname($target))); } if (@\file_put_contents($target, $buffer) === false) { throw new RuntimeException( \sprintf( 'Could not write to "%s', $target ) ); } } return $buffer; } private function getCrapLoad($crapValue, $cyclomaticComplexity, $coveragePercent): float { $crapLoad = 0; if ($crapValue >= $this->threshold) { $crapLoad += $cyclomaticComplexity * (1.0 - $coveragePercent / 100); $crapLoad += $cyclomaticComplexity / $this->threshold; } return $crapLoad; } private function roundValue($value): float { return \round($value, 2); } private function createDirectory(string $directory): bool { return !(!\is_dir($directory) && !@\mkdir($directory, 0777, true) && !\is_dir($directory)); } } unintentionallyCoveredUnits = $unintentionallyCoveredUnits; parent::__construct($this->toString()); } public function getUnintentionallyCoveredUnits(): array { return $this->unintentionallyCoveredUnits; } private function toString(): string { $message = ''; foreach ($this->unintentionallyCoveredUnits as $unit) { $message .= '- ' . $unit . "\n"; } return $message; } } tokens[] = $token; } public function current(): Token { return \current($this->tokens); } public function key(): int { return \key($this->tokens); } public function next(): void { \next($this->tokens); $this->pos++; } public function valid(): bool { return $this->count() > $this->pos; } public function rewind(): void { \reset($this->tokens); $this->pos = 0; } public function count(): int { return \count($this->tokens); } public function offsetExists($offset): bool { return isset($this->tokens[$offset]); } public function offsetGet($offset): Token { if (!$this->offsetExists($offset)) { throw new TokenCollectionException( \sprintf('No Token at offest %s', $offset) ); } return $this->tokens[$offset]; } public function offsetSet($offset, $value): void { if (!\is_int($offset)) { $type = \gettype($offset); throw new TokenCollectionException( \sprintf( 'Offset must be of type integer, %s given', $type === 'object' ? \get_class($value) : $type ) ); } if (!$value instanceof Token) { $type = \gettype($value); throw new TokenCollectionException( \sprintf( 'Value must be of type %s, %s given', Token::class, $type === 'object' ? \get_class($value) : $type ) ); } $this->tokens[$offset] = $value; } public function offsetUnset($offset): void { unset($this->tokens[$offset]); } } 'T_OPEN_BRACKET', ')' => 'T_CLOSE_BRACKET', '[' => 'T_OPEN_SQUARE', ']' => 'T_CLOSE_SQUARE', '{' => 'T_OPEN_CURLY', '}' => 'T_CLOSE_CURLY', ';' => 'T_SEMICOLON', '.' => 'T_DOT', ',' => 'T_COMMA', '=' => 'T_EQUAL', '<' => 'T_LT', '>' => 'T_GT', '+' => 'T_PLUS', '-' => 'T_MINUS', '*' => 'T_MULT', '/' => 'T_DIV', '?' => 'T_QUESTION_MARK', '!' => 'T_EXCLAMATION_MARK', ':' => 'T_COLON', '"' => 'T_DOUBLE_QUOTES', '@' => 'T_AT', '&' => 'T_AMPERSAND', '%' => 'T_PERCENT', '|' => 'T_PIPE', '$' => 'T_DOLLAR', '^' => 'T_CARET', '~' => 'T_TILDE', '`' => 'T_BACKTICK' ]; public function parse(string $source): TokenCollection { $result = new TokenCollection(); if ($source === '') { return $result; } $tokens = \token_get_all($source); $lastToken = new Token( $tokens[0][2], 'Placeholder', '' ); foreach ($tokens as $pos => $tok) { if (\is_string($tok)) { $token = new Token( $lastToken->getLine(), $this->map[$tok], $tok ); $result->addToken($token); $lastToken = $token; continue; } $line = $tok[2]; $values = \preg_split('/\R+/Uu', $tok[1]); foreach ($values as $v) { $token = new Token( $line, \token_name($tok[0]), $v ); $lastToken = $token; $line++; if ($v === '') { continue; } $result->addToken($token); } } return $this->fillBlanks($result, $lastToken->getLine()); } private function fillBlanks(TokenCollection $tokens, int $maxLine): TokenCollection { $prev = new Token( 0, 'Placeholder', '' ); $final = new TokenCollection(); foreach ($tokens as $token) { if ($prev === null) { $final->addToken($token); $prev = $token; continue; } $gap = $token->getLine() - $prev->getLine(); while ($gap > 1) { $linebreak = new Token( $prev->getLine() + 1, 'T_WHITESPACE', '' ); $final->addToken($linebreak); $prev = $linebreak; $gap--; } $final->addToken($token); $prev = $token; } $gap = $maxLine - $prev->getLine(); while ($gap > 0) { $linebreak = new Token( $prev->getLine() + 1, 'T_WHITESPACE', '' ); $final->addToken($linebreak); $prev = $linebreak; $gap--; } return $final; } } xmlns = $xmlns; } public function toDom(TokenCollection $tokens): DOMDocument { $dom = new DOMDocument(); $dom->preserveWhiteSpace = false; $dom->loadXML($this->toXML($tokens)); return $dom; } public function toXML(TokenCollection $tokens): string { $this->writer = new \XMLWriter(); $this->writer->openMemory(); $this->writer->setIndent(true); $this->writer->startDocument(); $this->writer->startElement('source'); $this->writer->writeAttribute('xmlns', $this->xmlns->asString()); if (\count($tokens) > 0) { $this->writer->startElement('line'); $this->writer->writeAttribute('no', '1'); $this->previousToken = $tokens[0]; foreach ($tokens as $token) { $this->addToken($token); } } $this->writer->endElement(); $this->writer->endElement(); $this->writer->endDocument(); return $this->writer->outputMemory(); } private function addToken(Token $token): void { if ($this->previousToken->getLine() < $token->getLine()) { $this->writer->endElement(); $this->writer->startElement('line'); $this->writer->writeAttribute('no', (string)$token->getLine()); $this->previousToken = $token; } if ($token->getValue() !== '') { $this->writer->startElement('token'); $this->writer->writeAttribute('name', $token->getName()); $this->writer->writeRaw(\htmlspecialchars($token->getValue(), \ENT_NOQUOTES | \ENT_DISALLOWED | \ENT_XML1)); $this->writer->endElement(); } } } ensureValidUri($value); $this->value = $value; } public function asString(): string { return $this->value; } private function ensureValidUri($value): void { if (\strpos($value, ':') === false) { throw new NamespaceUriException( \sprintf("Namespace URI '%s' must contain at least one colon", $value) ); } } } line = $line; $this->name = $name; $this->value = $value; } public function getLine(): int { return $this->line; } public function getName(): string { return $this->name; } public function getValue(): string { return $this->value; } } _source = $source; $this->addIds($ids); return; } public function addIds(array $ids) { foreach ($ids as $id) { $this->_callables[$id] = []; } return; } public function attach($listenerId, $callable) { if (false === $this->listenerExists($listenerId)) { throw new Exception( 'Cannot listen %s because it is not defined.', 0, $listenerId ); } $callable = xcallable($callable); $this->_callables[$listenerId][$callable->getHash()] = $callable; return $this; } public function detach($listenerId, $callable) { unset($this->_callables[$listenerId][xcallable($callable)->getHash()]); return $this; } public function detachAll($listenerId) { unset($this->_callables[$listenerId]); return $this; } public function listenerExists($listenerId) { return array_key_exists($listenerId, $this->_callables); } public function fire($listenerId, Bucket $data) { if (false === $this->listenerExists($listenerId)) { throw new Exception( 'Cannot fire on %s because it is not defined.', 1, $listenerId ); } $data->setSource($this->_source); $out = []; foreach ($this->_callables[$listenerId] as $callable) { $out[] = $callable($data); } return $out; } } when($result = new \Mock\Hoa\Event\Source()) ->then ->object($result) ->isInstanceOf('Hoa\Event\Source'); } } given( $source = new \Mock\Hoa\Event\Listenable(), $ids = ['foo', 'bar', 'baz'] ) ->when($result = new SUT($source, $ids)) ->then ->object($result) ->isInstanceOf('Hoa\Event\Listener') ->boolean($result->listenerExists('foo')) ->isTrue() ->boolean($result->listenerExists('bar')) ->isTrue() ->boolean($result->listenerExists('baz')) ->isTrue(); } public function case_attach() { $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $listenerId = 'foo', $listener = new SUT($source, ['foo', 'bar']), $callable = function () { return 42; } ) ->when($result = $listener->attach($listenerId, $callable)) ->then ->object($result) ->isIdenticalTo($listener) ->array($listener->fire($listenerId, new LUT\Bucket())) ->isEqualTo([42]); } public function case_attach_to_an_undefined_listener() { $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $listenerId = 'bar', $listener = new SUT($source, ['foo', 'baz']), $callable = function () { } ) ->exception(function () use ($listener, $listenerId, $callable) { $listener->attach($listenerId, $callable); }) ->isInstanceOf('Hoa\Event\Exception'); } public function case_detach() { $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $listenerId = 'foo', $listener = new SUT($source, ['foo', 'bar']), $callable = function () { return 42; }, $listener->attach($listenerId, $callable) ) ->when($result = $listener->detach($listenerId, $callable)) ->then ->object($result) ->isIdenticalTo($listener) ->array($listener->fire($listenerId, new LUT\Bucket())) ->isEmpty(); } public function case_detach_an_undefined_listener() { $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $listenerId = 'bar', $listener = new SUT($source, ['foo', 'baz']), $callable = function () { } ) ->when($result = $listener->detach($listenerId, $callable)) ->then ->object($result) ->isIdenticalTo($listener); } public function case_detach_all() { $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $listenerId = 'foo', $listener = new SUT($source, ['foo', 'bar']) ) ->when($result = $listener->detachAll($listenerId)) ->then ->object($result) ->isIdenticalTo($listener) ->boolean($listener->listenerExists($listenerId)) ->isFalse(); } public function case_detach_all_with_an_undefined_listener() { $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $listenerId = 'bar', $listener = new SUT($source, ['foo', 'baz']) ) ->when($result = $listener->detachAll($listenerId)) ->then ->object($result) ->isIdenticalTo($listener); } public function case_listener_exists() { $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $ids = [], $listener = new SUT($source, $ids) ) ->when($listener->addIds(['foo'])) ->then ->boolean($listener->listenerExists('foo')) ->isTrue() ->boolean($listener->listenerExists('bar')) ->isFalse() ->when($listener->addIds(['bar'])) ->then ->boolean($listener->listenerExists('bar')) ->isTrue(); } public function case_fire() { $self = $this; $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $ids = ['foo', 'bar'], $listener = new SUT($source, $ids), $listenerId = 'foo', $bucket = new LUT\Bucket(), $listener->attach( $listenerId, function (LUT\Bucket $receivedBucket) use ($self, $bucket, $source, &$called) { $called = true; $self ->object($receivedBucket) ->isIdenticalTo($bucket) ->object($receivedBucket->getSource()) ->isIdenticalTo($source); return 42; } ) ) ->when($result = $listener->fire($listenerId, $bucket)) ->then ->array($result) ->isEqualTo([42]) ->boolean($called) ->isTrue(); } public function case_fire_an_undefined_listenerId() { $this ->given( $source = new \Mock\Hoa\Event\Listenable(), $ids = [], $listener = new SUT($source, $ids) ) ->exception(function () use ($listener) { $listener->fire('foo', new LUT\Bucket()); }) ->isInstanceOf('Hoa\Event\Exception'); } } when($result = new SUT('foo', 0)) ->then ->object($result) ->isInstanceOf('Hoa\Exception\Exception'); } } when($result = new SUT('foo')) ->then ->object($result) ->isInstanceOf('Hoa\Event\Bucket') ->string($result->getData()) ->isEqualTo('foo'); } public function case_send() { $self = $this; $this ->given( $eventId = 'hoa://Event/Test', $source = new \Mock\Hoa\Event\Source(), LUT::register($eventId, $source), $bucket = new SUT('foo'), LUT::getEvent($eventId)->attach( function (SUT $receivedBucket) use ($self, $bucket, &$called) { $called = true; $self ->object($receivedBucket) ->isIdenticalTo($bucket); } ) ) ->when($result = $bucket->send($eventId, $source)) ->then ->variable($result) ->isNull() ->boolean($called) ->isTrue(); } public function case_set_source() { $this ->given( $bucket = new SUT(), $sourceA = new \Mock\Hoa\Event\Source() ) ->when($result = $bucket->setSource($sourceA)) ->then ->variable($result) ->isNull() ->object($bucket->getSource()) ->isIdenticalTo($sourceA) ->given($sourceB = new \Mock\Hoa\Event\Source()) ->when($result = $bucket->setSource($sourceB)) ->then ->object($result) ->isIdenticalTo($sourceA) ->object($bucket->getSource()) ->isIdenticalTo($sourceB); } public function case_set_data() { $this ->given( $bucket = new SUT(), $datumA = 'foo' ) ->when($result = $bucket->setData($datumA)) ->then ->variable($result) ->isNull() ->string($bucket->getData()) ->isEqualTo($datumA) ->given($datumB = 'bar') ->when($result = $bucket->setData($datumB)) ->then ->string($result) ->isEqualTo($datumA) ->string($bucket->getData()) ->isEqualTo($datumB); } } given( $listenable = new _Listenable(), $listener = new LUT\Listener($listenable, ['foo']) ) ->when($result = $listenable->_setListener($listener)) ->then ->variable($result) ->isNull(); } public function case_get_listener() { $this ->given( $listenable = new _Listenable(), $listener = new LUT\Listener($listenable, ['foo']), $listenable->_setListener($listener) ) ->when($result = $listenable->_getListener()) ->then ->object($result) ->isIdenticalTo($listener); } public function case_on() { $this ->given( $listenable = new _Listenable(), $listener = new LUT\Listener($listenable, ['foo']), $listenable->_setListener($listener), $callable = function () use (&$called) { $called = true; return 42; } ) ->when($result = $listenable->on('foo', $callable)) ->then ->object($result) ->isIdenticalTo($listenable) ->when($listenable->doSomethingThatFires()) ->then ->boolean($called) ->isTrue(); } public function case_on_unregistered_listener() { $this ->given( $listenable = new _Listenable(), $listener = new LUT\Listener($listenable, ['foo']), $listenable->_setListener($listener) ) ->exception(function () use ($listenable) { $listenable->on('bar', null); }) ->isInstanceOf('Hoa\Event\Exception'); } } class _Listenable implements LUT\Listenable { use SUT; public function _setListener(LUT\Listener $listener) { return $this->setListener($listener); } public function _getListener() { return $this->getListener(); } public function doSomethingThatFires() { $this->getListener()->fire('foo', new LUT\Bucket('bar')); return; } } when($result = new \Mock\Hoa\Event\Listenable()) ->then ->object($result) ->isInstanceOf('Hoa\Event\Listenable'); } } given($eventId = 'hoa://Event/Test') ->when($result = SUT::getEvent($eventId)) ->then ->object($result) ->isInstanceOf('Hoa\Event\Event') ->object(SUT::getEvent($eventId)) ->isIdenticalTo($result); } public function case_register_source_instance() { $this ->given( $eventId = 'hoa://Event/Test', $source = new \Mock\Hoa\Event\Source() ) ->when($result = SUT::register($eventId, $source)) ->then ->variable($result) ->isNull() ->boolean(SUT::eventExists($eventId)) ->isTrue(); } public function case_register_source_name() { $this ->given( $eventId = 'hoa://Event/Test', $source = 'Mock\Hoa\Event\Source' ) ->when($result = SUT::register($eventId, $source)) ->then ->variable($result) ->isNull() ->boolean(SUT::eventExists($eventId)) ->isTrue(); } public function case_register_redeclare() { $this ->given( $eventId = 'hoa://Event/Test', $source = new \Mock\Hoa\Event\Source(), SUT::register($eventId, $source) ) ->exception(function () use ($eventId, $source) { SUT::register($eventId, $source); }) ->isInstanceOf('Hoa\Event\Exception'); } public function case_register_not_a_source_instance() { $this ->given( $eventId = 'hoa://Event/Test', $source = new \StdClass() ) ->exception(function () use ($eventId, $source) { $result = SUT::register($eventId, $source); }) ->isInstanceOf('Hoa\Event\Exception'); } public function case_register_not_a_source_name() { $this ->given( $eventId = 'hoa://Event/Test', $source = 'StdClass' ) ->exception(function () use ($eventId, $source) { $result = SUT::register($eventId, $source); }) ->isInstanceOf('Hoa\Event\Exception'); } public function case_unregister() { $this ->given( $eventId = 'hoa://Event/Test', $source = new \Mock\Hoa\Event\Source(), SUT::register($eventId, $source) ) ->when($result = SUT::unregister($eventId)) ->then ->boolean(SUT::eventExists($eventId)) ->isFalse(); } public function case_unregister_hard() { $this ->given( $eventId = 'hoa://Event/Test', $source = new \Mock\Hoa\Event\Source(), SUT::register($eventId, $source), $event = SUT::getEvent($eventId) ) ->when($result = SUT::unregister($eventId, true)) ->then ->boolean(SUT::eventExists($eventId)) ->isFalse() ->object(SUT::getEvent($eventId)) ->isNotIdenticalTo($event); } public function case_unregister_not_registered() { $this ->given($eventId = 'hoa://Event/Test') ->when($result = SUT::unregister($eventId)) ->then ->variable($result) ->isNull(); } public function case_attach() { $this ->given( $event = SUT::getEvent('hoa://Event/Test'), $callable = function () { } ) ->when($result = $event->attach($callable)) ->then ->object($result) ->isIdenticalTo($event) ->boolean($event->isListened()) ->isTrue(); } public function case_detach() { $this ->given( $event = SUT::getEvent('hoa://Event/Test'), $callable = function () { }, $event->attach($callable) ) ->when($result = $event->detach($callable)) ->then ->object($result) ->isIdenticalTo($event) ->boolean($event->isListened()) ->isFalse(); } public function case_detach_unattached() { $this ->given( $event = SUT::getEvent('hoa://Event/Test'), $callable = function () { } ) ->when($result = $event->detach($callable)) ->then ->object($result) ->isIdenticalTo($event) ->boolean($event->isListened()) ->isFalse(); } public function case_is_listened() { $this ->given($event = SUT::getEvent('hoa://Event/Test')) ->when($result = $event->isListened()) ->then ->boolean($event->isListened()) ->isFalse(); } public function case_notify() { $self = $this; $this ->given( $eventId = 'hoa://Event/Test', $source = new \Mock\Hoa\Event\Source(), $bucket = new LUT\Bucket(), SUT::register($eventId, $source), SUT::getEvent($eventId)->attach( function (LUT\Bucket $receivedBucket) use ($self, $source, $bucket, &$called) { $called = true; $this ->object($receivedBucket) ->isIdenticalTo($bucket) ->object($receivedBucket->getSource()) ->isIdenticalTo($source); } ) ) ->when($result = SUT::notify($eventId, $source, $bucket)) ->then ->variable($result) ->isNull() ->boolean($called) ->isTrue(); } public function case_notify_unregistered_event_id() { $this ->given( $eventId = 'hoa://Event/Test', $source = new \Mock\Hoa\Event\Source(), $data = new LUT\Bucket() ) ->exception(function () use ($eventId, $source, $data) { SUT::notify($eventId, $source, $data); }) ->isInstanceOf('Hoa\Event\Exception'); } public function case_event_exists() { $this ->given( $eventId = 'hoa://Event/Test', $source = new \Mock\Hoa\Event\Source(), SUT::register($eventId, $source) ) ->when($result = SUT::eventExists($eventId)) ->then ->boolean($result) ->isTrue(); } public function case_event_not_exists() { $this ->given($eventId = 'hoa://Event/Test') ->when($result = SUT::eventExists($eventId)) ->then ->boolean($result) ->isFalse(); } } setData($data); return; } public function send($eventId, Source $source) { return Event::notify($eventId, $source, $this); } public function setSource(Source $source) { $old = $this->_source; $this->_source = $source; return $old; } public function getSource() { return $this->_source; } public function setData($data) { $old = $this->_data; $this->_data = $data; return $old; } public function getData() { return $this->_data; } } getListener(); if (null === $listener) { throw new Exception( 'Cannot attach a callable to the listener %s because ' . 'it has not been initialized yet.', 0, get_class($this) ); } $listener->attach($listenerId, $callable); return $this; } protected function setListener(Listener $listener) { $old = $this->_listener; $this->_listener = $listener; return $old; } protected function getListener() { return $this->_listener; } } new self(), self::KEY_SOURCE => null ]; } return self::$_register[$eventId][self::KEY_EVENT]; } public static function register($eventId, $source) { if (true === self::eventExists($eventId)) { throw new Exception( 'Cannot redeclare an event with the same ID, i.e. the event ' . 'ID %s already exists.', 0, $eventId ); } if (is_object($source) && !($source instanceof Source)) { throw new Exception( 'The source must implement \Hoa\Event\Source ' . 'interface; given %s.', 1, get_class($source) ); } else { $reflection = new \ReflectionClass($source); if (false === $reflection->implementsInterface('\Hoa\Event\Source')) { throw new Exception( 'The source must implement \Hoa\Event\Source ' . 'interface; given %s.', 2, $source ); } } if (!isset(self::$_register[$eventId][self::KEY_EVENT])) { self::$_register[$eventId][self::KEY_EVENT] = new self(); } self::$_register[$eventId][self::KEY_SOURCE] = $source; return; } public static function unregister($eventId, $hard = false) { if (false !== $hard) { unset(self::$_register[$eventId]); } else { self::$_register[$eventId][self::KEY_SOURCE] = null; } return; } public function attach($callable) { $callable = xcallable($callable); $this->_callable[$callable->getHash()] = $callable; return $this; } public function detach($callable) { unset($this->_callable[xcallable($callable)->getHash()]); return $this; } public function isListened() { return !empty($this->_callable); } public static function notify($eventId, Source $source, Bucket $data) { if (false === self::eventExists($eventId)) { throw new Exception( 'Event ID %s does not exist, cannot send notification.', 3, $eventId ); } $data->setSource($source); $event = self::getEvent($eventId); foreach ($event->_callable as $callable) { $callable($data); } return; } public static function eventExists($eventId) { return array_key_exists($eventId, self::$_register) && self::$_register[$eventId][self::KEY_SOURCE] !== null; } } Consistency::flexEntity('Hoa\Event\Event'); array_fill(-1, $n - $m + $k + 3, -2)]; for ($q = 0, $max = $k - 1; $q <= $max; ++$q) { $L[$q][-$q - 1] = $L[$q][-$q - 2] = $q - 1; } for ($q = 0; $q <= $k; ++$q) { for ($d = -$q, $max = $n - $m + $k - $q; $d <= $max; ++$d) { $l = min( max( $L[$q - 1][$d - 1], $L[$q - 1][$d ] + 1, $L[$q - 1][$d + 1] + 1 ), $m - 1 ); $a = substr($x, $l + 1, $m - $l); $b = substr($y, $l + 1 + $d, $n - $l - $d); $L[$q][$d] = $l + static::lcp($a, $b); if ($L[$q][$d] == $m - 1 || $d + $L[$q][$d] == $n - 1) { $j = $m + $d; $i = max(0, $j - $m); $offset[$q][] = ['i' => $i, 'j' => $j, 'l' => $j - $i]; } } } return empty($offset) ? $offset : $offset[$k]; } public static function lcp($x, $y) { $max = min(strlen($x), strlen($y)); $i = 0; while ($i < $max && $x[$i] == $y[$i]) { ++$i; } return $i; } } when($result = LUT::toCode(chr(160))) ->then ->integer($result) ->isEqualTo(0xa0); } } given( $x = 'GATAA', $y = 'CAGATAAGAGAA', $k = 1 ) ->when($result = LUT\Search::approximated($y, $x, $k)) ->then ->array($result) ->isEqualTo([ 0 => [ 'i' => 1, 'j' => 6, 'l' => 5 ], 1 => [ 'i' => 2, 'j' => 7, 'l' => 5 ], 2 => [ 'i' => 3, 'j' => 8, 'l' => 5 ], 3 => [ 'i' => 7, 'j' => 12, 'l' => 5 ] ]); } } given($this->function->function_exists = true) ->then ->boolean(LUT::checkMbString()) ->isTrue(); } public function case_check_no_mbstring() { $this ->given( $this->function->function_exists = function ($name) { return 'mb_substr' !== $name; } ) ->exception(function () { new LUT(); }) ->isInstanceOf('Hoa\Ustring\Exception'); } public function case_append_ltr() { $this ->given($string = new LUT('je')) ->when($result = $string->append(' t\'aime')) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('je t\'aime'); } public function case_append_rtl() { $this ->given($string = new LUT('أ')) ->when($result = $string->append('حبك')) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('أحبك'); } public function case_prepend_ltr() { $this ->given($string = new LUT(' t\'aime')) ->when($result = $string->prepend('je')) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('je t\'aime'); } public function case_prepend_rtl() { $this ->given($string = new LUT('ك')) ->when($result = $string->prepend('أحب')) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('أحبك'); } public function case_pad_beginning_ltr() { $this ->given($string = new LUT('je t\'aime')) ->when($result = $string->pad(20, '👍 💩 😄 ❤️ ', LUT::BEGINNING)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('👍 💩 😄 ❤️ 👍 je t\'aime'); } public function case_pad_beginning_rtl() { $this ->given($string = new LUT('أحبك')) ->when($result = $string->pad(20, '👍 💩 😄 ❤️ ', LUT::BEGINNING)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('👍 💩 😄 ❤️ 👍 💩 😄 ❤أحبك'); } public function case_pad_end_ltr() { $this ->given($string = new LUT('je t\'aime')) ->when($result = $string->pad(20, '👍 💩 😄 ❤️ ', LUT::END)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('je t\'aime👍 💩 😄 ❤️ 👍 '); } public function case_pad_end_rtl() { $this ->given($string = new LUT('أحبك')) ->when($result = $string->pad(20, '👍 💩 😄 ❤️ ', LUT::END)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('أحبك👍 💩 😄 ❤️ 👍 💩 😄 ❤'); } public function case_compare_no_collator() { $this ->given( $this->function->class_exists = function ($name) { return 'Collator' !== $name; }, $string = new LUT('b') ) ->case_compare(); } public function case_compare() { $this ->given($string = new LUT('b')) ->when($result = $string->compare('a')) ->then ->integer($result) ->isEqualTo(1) ->when($result = $string->compare('b')) ->then ->integer($result) ->isEqualTo(0) ->when($result = $string->compare('c')) ->then ->integer($result) ->isEqualTo(-1); } public function case_collator() { $this ->given( $this->function->setlocale = 'fr_FR', $collator = LUT::getCollator() ) ->when($result = $collator->getLocale(\Locale::VALID_LOCALE)) ->then ->string($result) ->isEqualTo('fr'); } public function case_safe_unsafe_pattern() { $this ->given($pattern = '/foo/i') ->when($result = LUT::safePattern($pattern)) ->then ->string($result) ->isEqualto('/foo/iu'); } public function case_safe_safe_pattern() { $this ->given($pattern = '/foo/ui') ->when($result = LUT::safePattern($pattern)) ->then ->string($result) ->isEqualto('/foo/ui'); } public function case_match_default() { $this ->given( $pattern = '/💩/u', $string = new LUT('foo 💩 bar') ) ->when($result = $string->match($pattern, $matches)) ->then ->integer($result) ->isEqualTo(1) ->array($matches) ->isEqualTo([ 0 => '💩' ]); } public function case_match_offset() { $this ->given( $pattern = '/💩/u', $string = new LUT('foo 💩 bar') ) ->when($result = $string->match($pattern, $matches, 0, 0)) ->then ->integer($result) ->isEqualTo(1) ->array($matches) ->isEqualTo([0 => '💩']) ->when($result = $string->match($pattern, $matches, 0, 4)) ->then ->integer($result) ->isEqualTo(1) ->array($matches) ->isEqualTo([0 => '💩']) ->when($result = $string->match($pattern, $matches, 0, 5)) ->then ->integer($result) ->isEqualTo(0) ->array($matches) ->isEmpty(); } public function case_match_with_offset() { $this ->given( $pattern = '/💩/u', $string = new LUT('foo 💩 bar') ) ->when($result = $string->match($pattern, $matches, $string::WITH_OFFSET)) ->then ->integer($result) ->isEqualTo(1) ->array($matches) ->isEqualTo([ 0 => [ 0 => '💩', 1 => 4 ] ]); } public function case_match_all_default() { $this ->given( $pattern = '/💩/u', $string = new LUT('foo 💩 bar 💩 baz') ) ->when($result = $string->match($pattern, $matches, 0, 0, true)) ->then ->integer($result) ->isEqualTo(2) ->array($matches) ->isEqualTo([ 0 => [ 0 => '💩', 1 => '💩' ] ]); } public function case_match_all_with_offset() { $this ->given( $pattern = '/💩/u', $string = new LUT('foo 💩 bar 💩 baz') ) ->when($result = $string->match($pattern, $matches, $string::WITH_OFFSET, 0, true)) ->then ->integer($result) ->isEqualTo(2) ->array($matches) ->isEqualTo([ 0 => [ 0 => [ 0 => '💩', 1 => 4 ], 1 => [ 0 => '💩', 1 => 13 ] ] ]); } public function case_match_all_grouped_by_pattern() { $this ->given( $pattern = '/(💩)/u', $string = new LUT('foo 💩 bar 💩 baz') ) ->when($result = $string->match($pattern, $matches, $string::GROUP_BY_PATTERN, 0, true)) ->then ->integer($result) ->isEqualTo(2) ->array($matches) ->isEqualTo([ 0 => [ 0 => '💩', 1 => '💩' ], 1 => [ 0 => '💩', 1 => '💩' ] ]); } public function case_match_all_grouped_by_tuple() { $this ->given( $pattern = '/(💩)/u', $string = new LUT('foo 💩 bar 💩 baz') ) ->when($result = $string->match($pattern, $matches, $string::GROUP_BY_TUPLE, 0, true)) ->then ->integer($result) ->isEqualTo(2) ->array($matches) ->isEqualTo([ 0 => [ 0 => '💩', 1 => '💩' ], 1 => [ 0 => '💩', 1 => '💩' ] ]); } public function case_replace() { $this ->given($string = new LUT('❤️ 💩 💩')) ->when($result = $string->replace('/💩/u', '😄')) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('❤️ 😄 😄'); } public function case_replace_limited() { $this ->given($string = new LUT('❤️ 💩 💩')) ->when($result = $string->replace('/💩/u', '😄', 1)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('❤️ 😄 💩'); } public function case_split_default() { $this ->given($string = new LUT('❤️💩❤️💩❤️')) ->when($result = $string->split('/💩/')) ->then ->array($result) ->isEqualTo([ 0 => '❤️', 1 => '❤️', 2 => '❤️' ]); } public function case_split_default_limited() { $this ->given($string = new LUT('❤️💩❤️💩❤️')) ->when($result = $string->split('/💩/', 1)) ->then ->array($result) ->isEqualTo([ 0 => '❤️💩❤️💩❤️' ]); } public function case_split_with_delimiters() { $this ->given($string = new LUT('❤️💩❤️💩❤️')) ->when($result = $string->split('/💩/', -1, $string::WITH_DELIMITERS)) ->then ->array($result) ->isEqualTo([ 0 => '❤️', 1 => '❤️', 2 => '❤️' ]); } public function case_split_with_offset() { $this ->given($string = new LUT('❤️💩❤️💩❤️')) ->when($result = $string->split('/💩/', -1, $string::WITH_OFFSET)) ->then ->array($result) ->isEqualTo([ 0 => [ 0 => '❤️', 1 => 0 ], 1 => [ 0 => '❤️', 1 => 10 ], 2 => [ 0 => '❤️', 1 => 20 ] ]); } public function case_iterator_ltr() { $this ->given($string = new LUT('je t\'aime')) ->when($result = iterator_to_array($string)) ->then ->array($result) ->isEqualTo([ 'j', 'e', ' ', 't', '\'', 'a', 'i', 'm', 'e' ]); } public function case_iterator_rtl() { $this ->given($string = new LUT('أحبك')) ->when($result = iterator_to_array($string)) ->then ->array($result) ->isEqualTo([ 'أ', 'ح', 'ب', 'ك' ]); } public function case_to_lower() { $this ->given($string = new LUT('Σ \'ΑΓΑΠΏ')) ->when($result = $string->toLowerCase()) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('σ \'αγαπώ') ->given($string = new LUT('JE T\'AIME')) ->when($result = $string->toLowerCase()) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('je t\'aime'); } public function case_to_upper() { $this ->given($string = new LUT('σ \'αγαπώ')) ->when($result = $string->toUpperCase()) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('Σ \'ΑΓΑΠΏ') ->given($string = new LUT('je t\'aime')) ->when($result = $string->toUpperCase()) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('JE T\'AIME'); } public function case_trim_default() { $this ->given($string = new LUT('💩💩❤️💩💩')) ->when($result = $string->trim('💩')) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('❤️'); } public function case_trim_beginning() { $this ->given($string = new LUT('💩💩❤️💩💩')) ->when($result = $string->trim('💩', $string::BEGINNING)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('❤️💩💩'); } public function case_trim_end() { $this ->given($string = new LUT('💩💩❤️💩💩')) ->when($result = $string->trim('💩', $string::END)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('💩💩❤️'); } public function case_offset_get_ltr() { $this ->given($string = new LUT('je t\'aime')) ->when($result = $string[0]) ->then ->string($result) ->isEqualTo('j') ->when($result = $string[-1]) ->then ->string($result) ->isEqualTo('e'); } public function case_offset_get_rtl() { $this ->given($string = new LUT('أحبك')) ->when($result = $string[0]) ->then ->string($result) ->isEqualTo('أ') ->when($result = $string[-1]) ->then ->string($result) ->isEqualTo('ك'); } public function case_offset_set() { $this ->given($string = new LUT('أحبﻙ')) ->when($string[-1] = 'ك') ->then ->string((string) $string) ->isEqualTo('أحبك'); } public function case_offset_unset() { $this ->given($string = new LUT('أحبك😄')) ->when(function () use ($string) { unset($string[-1]); }) ->then ->string((string) $string) ->isEqualTo('أحبك'); } public function case_reduce() { $this ->given($string = new LUT('أحبك')) ->when($result = $string->reduce(0, 1)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('أ'); } public function case_count() { $this ->given($string = new LUT('je t\'aime')) ->when($result = count($string)) ->then ->integer($result) ->isEqualTo(9) ->given($string = new LUT('أحبك')) ->when($result = count($string)) ->then ->integer($result) ->isEqualTo(4) ->given($string = new LUT('💩')) ->when($result = count($string)) ->then ->integer($result) ->isEqualTo(1); } public function case_byte_at() { $this ->given($string = new LUT('💩')) ->when($result = $string->getByteAt(0)) ->then ->integer(ord($result)) ->isEqualTo(0xf0) ->when($result = $string->getByteAt(1)) ->then ->integer(ord($result)) ->isEqualTo(0x9f) ->when($result = $string->getByteAt(2)) ->then ->integer(ord($result)) ->isEqualTo(0x92) ->when($result = $string->getByteAt(3)) ->then ->integer(ord($result)) ->isEqualTo(0xa9) ->when($result = $string->getByteAt(-1)) ->then ->integer(ord($result)) ->isEqualTo(0xa9); } public function case_bytes_length() { $this ->given($string = new LUT('💩')) ->when($result = $string->getBytesLength()) ->then ->integer($result) ->isEqualTo(4); } public function case_get_width() { $this ->given($string = new LUT('💩')) ->when($result = $string->getWidth()) ->then ->integer($result) ->isEqualTo(1) ->given($string = new LUT('習')) ->when($result = $string->getWidth()) ->then ->integer($result) ->isEqualTo(2); } public function case_get_char_direction() { $this ->when($result = LUT::getCharDirection('A')) ->then ->integer($result) ->isEqualTo(LUT::LTR) ->when($result = LUT::getCharDirection('ا')) ->then ->integer($result) ->isEqualTo(LUT::RTL); } public function case_get_char_width() { $this ->given( $data = [ [0x0, 0], [0x19, -1], [0x7f, -1], [0x9f, -1], [0xa0, 1], [0x300, 0], [0x488, 0], [0x600, 0], [0xad, 1], [0x1160, 0], [0x11ff, 0], [0x200b, 0], [0x1100, 2], [0x2160, 1], [0x3f60, 2], [0x303f, 1], [0x2329, 2], [0xaed0, 2], [0x232a, 2], [0xffa4, 1], [0xfe10, 2], [0xfe30, 2], [0xff00, 2], [0xf900, 2] ] ) ->when(function () use ($data) { foreach ($data as $datum) { list($code, $width) = $datum; $this ->when($result = LUT::getCharWidth(LUT::fromCode($code))) ->then ->integer($result) ->isEqualTo($width); } }); } public function case_is_char_printable() { $this ->when($result = LUT::isCharPrintable(LUT::fromCode(0x7f))) ->then ->boolean($result) ->isFalse() ->when($result = LUT::isCharPrintable(LUT::fromCode(0xa0))) ->then ->boolean($result) ->isTrue() ->when($result = LUT::isCharPrintable(LUT::fromCode(0x1100))) ->then ->boolean($result) ->isTrue(); } public function case_from_code() { $this ->when($result = LUT::fromCode(0x7e)) ->then ->string($result) ->isEqualTo('~') ->when($result = LUT::fromCode(0xa7)) ->then ->string($result) ->isEqualTo('§') ->when($result = LUT::fromCode(0x1207)) ->then ->string($result) ->isEqualTo('ሇ') ->when($result = LUT::fromCode(0x1f4a9)) ->then ->string($result) ->isEqualTo('💩'); } public function case_to_code() { $this ->when($result = LUT::toCode('~')) ->then ->integer($result) ->isEqualTo(0x7e) ->when($result = LUT::toCode('§')) ->then ->integer($result) ->isEqualTo(0xa7) ->when($result = LUT::toCode('ሇ')) ->then ->integer($result) ->isEqualTo(0x1207) ->when($result = LUT::toCode('💩')) ->then ->integer($result) ->isEqualTo(0x1f4a9); } public function case_to_binary_code() { $this ->when($result = LUT::toBinaryCode('~')) ->then ->string($result) ->isEqualTo('01111110') ->when($result = LUT::toBinaryCode('§')) ->then ->string($result) ->isEqualTo('1100001010100111') ->when($result = LUT::toBinaryCode('ሇ')) ->then ->string($result) ->isEqualTo('111000011000100010000111') ->when($result = LUT::toBinaryCode('💩')) ->then ->string($result) ->isEqualTo('11110000100111111001001010101001'); } public function case_transcode_no_iconv() { $this ->given( $this->function->function_exists = function ($name) { return 'iconv' !== $name; } ) ->exception(function () { LUT::transcode('foo', 'UTF-8'); }) ->isInstanceOf('Hoa\Ustring\Exception'); } public function case_transcode_and_isUtf8() { $this ->given($uΣ = 'Σ') ->when($Σ = LUT::transcode($uΣ, 'UTF-8', 'UTF-16')) ->then ->string($Σ) ->isNotEqualTo($uΣ) ->boolean(LUT::isUtf8($Σ)) ->isFalse() ->when($Σ = LUT::transcode($Σ, 'UTF-16', 'UTF-8')) ->string($Σ) ->isEqualTo($uΣ) ->boolean(LUT::isUtf8($Σ)) ->isTrue() ->boolean(LUT::isUtf8($uΣ)) ->isTrue(); } public function case_to_ascii_no_transliterator_no_normalizer() { $this ->given( $this->function->class_exists = function ($name) { return false === in_array($name, ['Transliterator', 'Normalizer']); }, $string = new LUT('Un été brûlant sur la côte') ) ->exception(function () use ($string) { $string->toAscii(); }) ->isInstanceOf('Hoa\Ustring\Exception'); } public function case_to_ascii_no_transliterator_no_normalizer_try() { $this ->given( $this->function->class_exists = function ($name) { return false === in_array($name, ['Transliterator', 'Normalizer']); }, $string = new LUT('Un été brûlant sur la côte') ) ->when($result = $string->toAscii(true)) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('Un ete brulant sur la cote'); } public function case_to_ascii_no_transliterator() { $this ->given( $this->function->class_exists = function ($name) { return 'Transliterator' !== $name; }, $string = new LUT('Un été brûlant sur la côte') ) ->when($result = $string->toAscii()) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo('Un ete brulant sur la cote'); } public function case_to_ascii() { $this ->given( $strings = [ 'Un été brûlant sur la côte' => 'Un ete brulant sur la cote', 'Αυτή είναι μια δοκιμή' => 'Aute einai mia dokime', 'أحبك' => 'ahbk', 'キャンパス' => 'kyanpasu', 'биологическом' => 'biologiceskom', '정, 병호' => 'jeong, byeongho', 'ますだ, よしひこ' => 'masuda, yoshihiko', 'मोनिच' => 'monica', 'क्ष' => 'ksa', 'أحبك 😀' => 'ahbk (grinning face)', '∀ i ∈ ℕ' => '(for all) i (element of) N' ] ) ->when(function () use ($strings) { foreach ($strings as $original => $asciied) { $this ->given($string = new LUT($original)) ->when($result = $string->toAscii()) ->then ->object($result) ->isIdenticalTo($string) ->string((string) $result) ->isEqualTo($asciied); } }); } public function case_copy() { $this ->given($string = new LUT('foo')) ->when($result = $string->copy()) ->then ->object($result) ->isEqualTo($string); } public function case_toString() { $this ->given($datum = $this->sample($this->realdom->regex('/\w{7,42}/'))) ->when($result = new LUT($datum)) ->then ->castToString($result) ->isEqualTo($datum); } } getOption($v)) { switch ($c) { case 'b': $base = intval($v); break; case '__ambiguous': $this->resolveOptionAmbiguity($v); break; case 'h': case '?': default: return $this->usage(); } } $this->parser->listInputs($char); $code = base_convert((string) Ustring::toCode($char), 10, $base); echo $code, "\n"; return; } public function usage() { echo 'Usage : ustring:tocode ', "\n", 'Options :', "\n", $this->makeUsageOptionsList([ 'b' => 'Get the code in a specific base (16 by default).', 'help' => 'This help.' ]), "\n"; return; } } __halt_compiler(); Transform a character into its code. getOption($v)) { switch ($c) { case 'b': $base = intval($v); break; case '__ambiguous': $this->resolveOptionAmbiguity($v); break; case 'h': case '?': default: return $this->usage(); } } $this->parser->listInputs($code); $char = Ustring::fromCode(base_convert($code, $base, 10)); echo $char; return; } public function usage() { echo 'Usage : ustring:fromcode ', "\n", 'Options :', "\n", $this->makeUsageOptionsList([ 'b' => 'Specify the base of the code (16 by default).', 'help' => 'This help.' ]), "\n"; return; } } __halt_compiler(); Get a character from its code. append($string); } return; } public static function checkMbString() { return function_exists('mb_substr'); } public static function checkIconv() { return function_exists('iconv'); } public function append($substring) { $this->_string .= $substring; return $this; } public function prepend($substring) { $this->_string = $substring . $this->_string; return $this; } public function pad($length, $piece, $side = self::END) { $difference = $length - $this->count(); if (0 >= $difference) { return $this; } $handle = null; for ($i = $difference / mb_strlen($piece) - 1; $i >= 0; --$i) { $handle .= $piece; } $handle .= mb_substr($piece, 0, $difference - mb_strlen($handle)); return static::END === $side ? $this->append($handle) : $this->prepend($handle); } public function compare($string) { if (null === $collator = static::getCollator()) { return strcmp($this->_string, (string) $string); } return $collator->compare($this->_string, $string); } public static function getCollator() { if (false === class_exists('Collator')) { return null; } if (null === static::$_collator) { static::$_collator = new \Collator(setlocale(LC_COLLATE, null)); } return static::$_collator; } public static function safePattern($pattern) { $delimiter = mb_substr($pattern, 0, 1); $options = mb_substr( mb_strrchr($pattern, $delimiter, false), mb_strlen($delimiter) ); if (false === strpos($options, 'u')) { $pattern .= 'u'; } return $pattern; } public function match( $pattern, &$matches = null, $flags = 0, $offset = 0, $global = false ) { $pattern = static::safePattern($pattern); if (0 === $flags) { if (true === $global) { $flags = static::GROUP_BY_PATTERN; } } else { $flags &= ~PREG_SPLIT_OFFSET_CAPTURE; } $offset = strlen(mb_substr($this->_string, 0, $offset)); if (true === $global) { return preg_match_all( $pattern, $this->_string, $matches, $flags, $offset ); } return preg_match($pattern, $this->_string, $matches, $flags, $offset); } public function replace($pattern, $replacement, $limit = -1) { $pattern = static::safePattern($pattern); if (false === is_callable($replacement)) { $this->_string = preg_replace( $pattern, $replacement, $this->_string, $limit ); } else { $this->_string = preg_replace_callback( $pattern, $replacement, $this->_string, $limit ); } return $this; } public function split( $pattern, $limit = -1, $flags = self::WITHOUT_EMPTY ) { return preg_split( static::safePattern($pattern), $this->_string, $limit, $flags ); } public function getIterator() { return new \ArrayIterator(preg_split('#(?_string)); } public function toLowerCase() { $this->_string = mb_strtolower($this->_string); return $this; } public function toUpperCase() { $this->_string = mb_strtoupper($this->_string); return $this; } public function toAscii($try = false) { if (0 === preg_match('#[\x80-\xff]#', $this->_string)) { return $this; } $string = $this->_string; $transId = 'Any-Latin; ' . '[\p{S}] Name; ' . 'Latin-ASCII'; if (null !== $transliterator = static::getTransliterator($transId)) { $this->_string = preg_replace_callback( '#\\\N\{([A-Z ]+)\}#u', function (array $matches) { return '(' . strtolower($matches[1]) . ')'; }, $transliterator->transliterate($string) ); return $this; } if (false === class_exists('Normalizer')) { if (false === $try) { throw new Exception( '%s needs the class Normalizer to work properly, ' . 'or you can force a try by using %1$s(true).', 0, __METHOD__ ); } $string = static::transcode($string, 'UTF-8', 'ASCII//IGNORE//TRANSLIT'); $this->_string = preg_replace('#(?:[\'"`^](\w))#u', '\1', $string); return $this; } $string = \Normalizer::normalize($string, \Normalizer::NFKD); $string = preg_replace('#\p{Mn}+#u', '', $string); $this->_string = static::transcode($string, 'UTF-8', 'ASCII//IGNORE//TRANSLIT'); return $this; } public function transliterate($identifier, $start = 0, $end = null) { if (null === $transliterator = static::getTransliterator($identifier)) { throw new Exception( '%s needs the class Transliterator to work properly.', 1, __METHOD__ ); } $this->_string = $transliterator->transliterate($this->_string, $start, $end); return $this; } public static function getTransliterator($identifier) { if (false === class_exists('Transliterator')) { return null; } return \Transliterator::create($identifier); } public function trim($regex = '\s', $side = 3 ) { $regex = '(?:' . $regex . ')+'; $handle = null; if (0 !== ($side & static::BEGINNING)) { $handle .= '(^' . $regex . ')'; } if (0 !== ($side & static::END)) { if (null !== $handle) { $handle .= '|'; } $handle .= '(' . $regex . '$)'; } $this->_string = preg_replace('#' . $handle . '#u', '', $this->_string); $this->_direction = null; return $this; } protected function computeOffset($offset) { $length = mb_strlen($this->_string); if (0 > $offset) { $offset = -$offset % $length; if (0 !== $offset) { $offset = $length - $offset; } } elseif ($offset >= $length) { $offset %= $length; } return $offset; } public function offsetGet($offset) { return mb_substr($this->_string, $this->computeOffset($offset), 1); } public function offsetSet($offset, $value) { $head = null; $offset = $this->computeOffset($offset); if (0 < $offset) { $head = mb_substr($this->_string, 0, $offset); } $tail = mb_substr($this->_string, $offset + 1); $this->_string = $head . $value . $tail; $this->_direction = null; return $this; } public function offsetUnset($offset) { return $this->offsetSet($offset, null); } public function offsetExists($offset) { return true; } public function reduce($start, $length = null) { $this->_string = mb_substr($this->_string, $start, $length); return $this; } public function count() { return mb_strlen($this->_string); } public function getByteAt($offset) { $length = strlen($this->_string); if (0 > $offset) { $offset = -$offset % $length; if (0 !== $offset) { $offset = $length - $offset; } } elseif ($offset >= $length) { $offset %= $length; } return $this->_string[$offset]; } public function getBytesLength() { return strlen($this->_string); } public function getWidth() { return mb_strwidth($this->_string); } public function getDirection() { if (null === $this->_direction) { if (null === $this->_string) { $this->_direction = static::LTR; } else { $this->_direction = static::getCharDirection( mb_substr($this->_string, 0, 1) ); } } return $this->_direction; } public static function getCharDirection($char) { $c = static::toCode($char); if (!(0x5be <= $c && 0x10b7f >= $c)) { return static::LTR; } if (0x85e >= $c) { if (0x5be === $c || 0x5c0 === $c || 0x5c3 === $c || 0x5c6 === $c || (0x5d0 <= $c && 0x5ea >= $c) || (0x5f0 <= $c && 0x5f4 >= $c) || 0x608 === $c || 0x60b === $c || 0x60d === $c || 0x61b === $c || (0x61e <= $c && 0x64a >= $c) || (0x66d <= $c && 0x66f >= $c) || (0x671 <= $c && 0x6d5 >= $c) || (0x6e5 <= $c && 0x6e6 >= $c) || (0x6ee <= $c && 0x6ef >= $c) || (0x6fa <= $c && 0x70d >= $c) || 0x710 === $c || (0x712 <= $c && 0x72f >= $c) || (0x74d <= $c && 0x7a5 >= $c) || 0x7b1 === $c || (0x7c0 <= $c && 0x7ea >= $c) || (0x7f4 <= $c && 0x7f5 >= $c) || 0x7fa === $c || (0x800 <= $c && 0x815 >= $c) || 0x81a === $c || 0x824 === $c || 0x828 === $c || (0x830 <= $c && 0x83e >= $c) || (0x840 <= $c && 0x858 >= $c) || 0x85e === $c) { return static::RTL; } } elseif (0x200f === $c) { return static::RTL; } elseif (0xfb1d <= $c) { if (0xfb1d === $c || (0xfb1f <= $c && 0xfb28 >= $c) || (0xfb2a <= $c && 0xfb36 >= $c) || (0xfb38 <= $c && 0xfb3c >= $c) || 0xfb3e === $c || (0xfb40 <= $c && 0xfb41 >= $c) || (0xfb43 <= $c && 0xfb44 >= $c) || (0xfb46 <= $c && 0xfbc1 >= $c) || (0xfbd3 <= $c && 0xfd3d >= $c) || (0xfd50 <= $c && 0xfd8f >= $c) || (0xfd92 <= $c && 0xfdc7 >= $c) || (0xfdf0 <= $c && 0xfdfc >= $c) || (0xfe70 <= $c && 0xfe74 >= $c) || (0xfe76 <= $c && 0xfefc >= $c) || (0x10800 <= $c && 0x10805 >= $c) || 0x10808 === $c || (0x1080a <= $c && 0x10835 >= $c) || (0x10837 <= $c && 0x10838 >= $c) || 0x1083c === $c || (0x1083f <= $c && 0x10855 >= $c) || (0x10857 <= $c && 0x1085f >= $c) || (0x10900 <= $c && 0x1091b >= $c) || (0x10920 <= $c && 0x10939 >= $c) || 0x1093f === $c || 0x10a00 === $c || (0x10a10 <= $c && 0x10a13 >= $c) || (0x10a15 <= $c && 0x10a17 >= $c) || (0x10a19 <= $c && 0x10a33 >= $c) || (0x10a40 <= $c && 0x10a47 >= $c) || (0x10a50 <= $c && 0x10a58 >= $c) || (0x10a60 <= $c && 0x10a7f >= $c) || (0x10b00 <= $c && 0x10b35 >= $c) || (0x10b40 <= $c && 0x10b55 >= $c) || (0x10b58 <= $c && 0x10b72 >= $c) || (0x10b78 <= $c && 0x10b7f >= $c)) { return static::RTL; } } return static::LTR; } public static function getCharWidth($char) { $char = (string) $char; $c = static::toCode($char); if (0x0 === $c) { return 0; } if (0x20 > $c || (0x7f <= $c && $c < 0xa0)) { return -1; } if (0xad !== $c && 0 !== preg_match('#^[\p{Mn}\p{Me}\p{Cf}\x{1160}-\x{11ff}\x{200b}]#u', $char)) { return 0; } return 1 + (0x1100 <= $c && (0x115f >= $c || 0x2329 === $c || 0x232a === $c || (0x2e80 <= $c && 0xa4cf >= $c && 0x303f !== $c) || (0xac00 <= $c && 0xd7a3 >= $c) || (0xf900 <= $c && 0xfaff >= $c) || (0xfe10 <= $c && 0xfe19 >= $c) || (0xfe30 <= $c && 0xfe6f >= $c) || (0xff00 <= $c && 0xff60 >= $c) || (0xffe0 <= $c && 0xffe6 >= $c) || (0x20000 <= $c && 0x2fffd >= $c) || (0x30000 <= $c && 0x3fffd >= $c))); } public static function isCharPrintable($char) { return 1 <= static::getCharWidth($char); } public static function fromCode($code) { return mb_convert_encoding( '&#x' . dechex($code) . ';', 'UTF-8', 'HTML-ENTITIES' ); } public static function toCode($char) { $char = (string) $char; $code = ord($char[0]); $bytes = 1; if (!($code & 0x80)) { return $code; } if (($code & 0xe0) === 0xc0) { $bytes = 2; $code = $code & ~0xc0; } elseif (($code & 0xf0) == 0xe0) { $bytes = 3; $code = $code & ~0xe0; } elseif (($code & 0xf8) === 0xf0) { $bytes = 4; $code = $code & ~0xf0; } for ($i = 2; $i <= $bytes; $i++) { $code = ($code << 6) + (ord($char[$i - 1]) & ~0x80); } return $code; } public static function toBinaryCode($char) { $char = (string) $char; $out = null; for ($i = 0, $max = strlen($char); $i < $max; ++$i) { $out .= vsprintf('%08b', ord($char[$i])); } return $out; } public static function transcode($string, $from, $to = 'UTF-8') { if (false === static::checkIconv()) { throw new Exception( '%s needs the iconv extension.', 2, __CLASS__ ); } return iconv($from, $to, $string); } public static function isUtf8($string) { return (bool) preg_match('##u', $string); } public function copy() { return clone $this; } public function __toString() { return $this->_string; } } Consistency::flexEntity('Hoa\Ustring\Ustring'); if (false === Ustring::checkMbString()) { throw new Exception( '%s needs the mbstring extension.', 0, __NAMESPACE__ . '\Ustring' ); } initialize(); return; } public static function getInstance() { if (null === static::$_instance) { static::$_instance = new static(); } return static::$_instance; } protected function initialize() { $root = dirname(dirname(__DIR__)); $cwd = 'cli' === PHP_SAPI ? dirname(realpath($_SERVER['argv'][0])) : getcwd(); $this[] = new Node( 'Application', $cwd . DS, [ new Node('Public', 'Public' . DS) ] ); $this[] = new Node( 'Data', dirname($cwd) . DS, [ new Node( 'Etc', 'Etc' . DS, [ new Node('Configuration', 'Configuration' . DS), new Node('Locale', 'Locale' . DS) ] ), new Node('Lost+found', 'Lost+found' . DS), new Node('Temporary', 'Temporary' . DS), new Node( 'Variable', 'Variable' . DS, [ new Node('Cache', 'Cache' . DS), new Node('Database', 'Database' . DS), new Node('Log', 'Log' . DS), new Node('Private', 'Private' . DS), new Node('Run', 'Run' . DS), new Node('Test', 'Test' . DS) ] ) ] ); $this[] = new Node\Library( 'Library', $root . DS . 'Hoathis' . DS . RS . $root . DS . 'Hoa' . DS ); return; } public function resolve($path, $exists = true, $unfold = false) { if (substr($path, 0, 6) !== 'hoa://') { if (true === is_dir($path)) { $path = rtrim($path, '/\\'); if (0 === strlen($path)) { $path = '/'; } } return $path; } if (isset(self::$_cache[$path])) { $handle = self::$_cache[$path]; } else { $out = $this->_resolve($path, $handle); if (!is_array($handle)) { return $out; } $handle = array_values(array_unique($handle, SORT_REGULAR)); foreach ($handle as &$entry) { if (true === is_dir($entry)) { $entry = rtrim($entry, '/\\'); if (0 === strlen($entry)) { $entry = '/'; } } } self::$_cache[$path] = $handle; } if (true === $unfold) { if (true !== $exists) { return $handle; } $out = []; foreach ($handle as $solution) { if (file_exists($solution)) { $out[] = $solution; } } return $out; } if (true !== $exists) { return $handle[0]; } foreach ($handle as $solution) { if (file_exists($solution)) { return $solution; } } return static::NO_RESOLUTION; } public static function clearCache() { self::$_cache = []; return; } } Consistency::flexEntity('Hoa\Protocol\Protocol'); when($result = SUT::getInstance()) ->then ->object($result) ->isInstanceOf('Hoa\Protocol\Node'); } public function case_default_tree() { $this ->when($result = SUT::getInstance()) ->then ->object($result['Application'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Application']['Public'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Etc'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Etc']['Configuration'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Etc']['Locale'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Lost+found'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Temporary'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Variable'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Variable']['Cache'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Variable']['Database'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Variable']['Log'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Variable']['Private'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Variable']['Run'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Data']['Variable']['Test'])->isInstanceOf('Hoa\Protocol\Node\Node') ->object($result['Library'])->isInstanceOf('Hoa\Protocol\Node\Library') ->string($result['Library']->reach()) ->isEqualTo( dirname(dirname(dirname(dirname(__DIR__)))) . DS . 'hoathis' . DS . RS . dirname(dirname(dirname(dirname(__DIR__)))) . DS . 'hoa' . DS ); } public function case_resolve_not_a_hoa_path() { $this ->given($protocol = SUT::getInstance()) ->when($result = $protocol->resolve('/foo/bar')) ->then ->string($result) ->isEqualTo('/foo/bar'); } public function case_resolve_to_non_existing_resource() { $this ->given($protocol = SUT::getInstance()) ->when($result = $protocol->resolve('hoa://Application/Foo/Bar')) ->then ->string($result) ->isEqualTo(SUT::NO_RESOLUTION); } public function case_resolve_does_not_test_if_exists() { $this ->given($protocol = SUT::getInstance()) ->when($result = $protocol->resolve('hoa://Application/Foo/Bar', false)) ->then ->string($result) ->isEqualTo('/Foo/Bar'); } public function case_resolve_unfold_to_existing_resources() { $this ->given($protocol = SUT::getInstance()) ->when($result = $protocol->resolve('hoa://Library', true, true)) ->then ->array($result) ->contains( dirname(dirname(dirname(dirname(__DIR__)))) . DS . 'hoa' ); } public function case_resolve_unfold_to_non_existing_resources() { $this ->given( $parentHoaDirectory = dirname(dirname(dirname(dirname(__DIR__)))), $protocol = SUT::getInstance() ) ->when($result = $protocol->resolve('hoa://Library', false, true)) ->then ->array($result) ->isEqualTo([ $parentHoaDirectory . DS . 'hoathis', $parentHoaDirectory . DS . 'hoa' ]); } } given($wrapper = new SUT()) ->when($result = $wrapper->stream_cast(STREAM_CAST_FOR_SELECT)) ->then ->boolean($result) ->isFalse(); } public function case_stream_cast_as_stream() { $this ->given($wrapper = new SUT()) ->when($result = $wrapper->stream_cast(STREAM_CAST_AS_STREAM)) ->then ->boolean($result) ->isFalse(); } public function case_stream_close() { $this ->given( $wrapper = new SUT(), $this->openFile($wrapper) ) ->when($result = $wrapper->stream_close()) ->then ->variable($result) ->isNull() ->variable($wrapper->getStream()) ->isNull() ->variable($wrapper->getStreamName()) ->isNull(); } public function case_stream_not_eof() { $this ->given( $wrapper = new SUT(), $this->openFile($wrapper, 'foo'), fseek($wrapper->getStream(), 0, SEEK_SET) ) ->when($result = $wrapper->stream_eof()) ->then ->boolean($result) ->isFalse(); } public function case_stream_eof() { $this ->given( $this->function->feof = true, $wrapper = new SUT() ) ->when($result = $wrapper->stream_eof()) ->then ->boolean($result) ->isTrue(); } public function case_stream_flush() { $this ->given( $wrapper = new SUT(), $this->openFile($wrapper) ) ->when($result = $wrapper->stream_flush()) ->then ->boolean($result) ->isTrue(); } public function _case_stream_xxx_lock($operation) { $this ->given( $this->function->flock = function ($resource, $operation) use (&$_resource, &$_operation) { $_resource = $resource; $_operation = $operation; if ($operation === LOCK_NB) { return true; } return flock($resource, $operation); }, $wrapper = new SUT(), $this->openFile($wrapper) ) ->when($result = $wrapper->stream_lock($operation)) ->then ->boolean($result) ->isTrue() ->resource($_resource) ->isStream() ->isIdenticalTo($wrapper->getStream()) ->integer($_operation) ->isEqualTo($operation); } public function case_stream_shared_lock() { return $this->_case_stream_xxx_lock(LOCK_SH); } public function case_stream_exclusive_lock() { return $this->_case_stream_xxx_lock(LOCK_EX); } public function case_stream_release_lock() { return $this->_case_stream_xxx_lock(LOCK_UN); } public function case_stream_not_blocking_lock() { return $this->_case_stream_xxx_lock(LOCK_NB); } public function _case_metadata_touch_with_xxx_arguments($arguments, $path, $time, $atime) { $this ->given( $this->function->touch = function ($path, $time, $atime) use (&$_path, &$_time, &$_atime) { $_path = $path; $_time = $time; $_atime = $atime; return true; }, $wrapper = new SUT() ) ->when($result = $wrapper->stream_metadata($path, STREAM_META_TOUCH, $arguments)) ->then ->boolean($result) ->isTrue() ->string($_path) ->isEqualTo($path) ->variable($_time) ->isEqualTo($time) ->variable($_atime) ->isEqualTo($atime); } public function case_metadata_touch_with_no_argument() { return $this->_case_metadata_touch_with_xxx_arguments([], 'foo', null, null); } public function case_metadata_touch_with_time() { return $this->_case_metadata_touch_with_xxx_arguments([42], 'foo', 42, null); } public function case_metadata_touch_with_time_and_atime() { return $this->_case_metadata_touch_with_xxx_arguments([42, 777], 'foo', 42, 777); } public function _case_metadata_owner_xxx($owner) { $this ->given( $this->function->chown = function ($path, $user) use (&$_path, &$_user) { $_path = $path; $_user = $user; return true; }, $path = 'foo', $user = 'gordon', $wrapper = new SUT() ) ->when($result = $wrapper->stream_metadata('foo', $owner, $user)) ->then ->boolean($result) ->isTrue() ->string($path) ->isEqualTo($_path) ->string($user) ->isEqualTo($_user); } public function case_metadata_owner() { return $this->_case_metadata_owner_xxx(STREAM_META_OWNER); } public function case_metadata_owner_name() { return $this->_case_metadata_owner_xxx(STREAM_META_OWNER_NAME); } public function _case_metadata_group_xxx($grp) { $this ->given( $this->function->chgrp = function ($path, $group) use (&$_path, &$_group) { $_path = $path; $_group = $group; return true; }, $path = 'foo', $group = 'root', $wrapper = new SUT() ) ->when($result = $wrapper->stream_metadata('foo', $grp, $group)) ->then ->boolean($result) ->isTrue() ->string($path) ->isEqualTo($_path) ->string($group) ->isEqualTo($_group); } public function case_metadata_group() { return $this->_case_metadata_group_xxx(STREAM_META_GROUP); } public function case_metadata_group_name() { return $this->_case_metadata_group_xxx(STREAM_META_GROUP_NAME); } public function case_metadata_access() { $this ->given( $this->function->chmod = function ($path, $mode) use (&$_path, &$_mode) { $_path = $path; $_mode = $mode; return true; }, $path = 'foo', $mode = 0755, $wrapper = new SUT() ) ->when($result = $wrapper->stream_metadata('foo', STREAM_META_ACCESS, $mode)) ->then ->boolean($result) ->isTrue() ->string($path) ->isEqualTo($_path) ->integer($mode) ->isEqualTo($_mode); } public function case_metadata_default() { $this ->given( $option = 0, $mode = 0, $wrapper = new SUT() ) ->when($result = $wrapper->stream_metadata('foo', $option, $mode)) ->then ->boolean($result) ->isFalse(); } public function case_stream_open() { $this ->given( $this->function->fopen = function ($path, $mode, $options) use (&$_path, &$_mode, &$_options, &$_openedPath) { $_path = $path; $_mode = $mode; $_options = $options; return fopen($path, $mode, $options); }, $wrapper = new SUT(), $path = 'hoa://Test/Vfs/Foo?type=file', $mode = 'r', $options = STREAM_USE_PATH ) ->when($result = $wrapper->stream_open($path, $mode, $options, $openedPath)) ->then ->boolean($result) ->isTrue() ->string(SUT::realPath($path, true)) ->isEqualTo($_path) ->string($mode) ->isEqualTo($_mode) ->integer($options) ->isEqualTo($_options & STREAM_USE_PATH) ->resource($openedPath) ->isStream() ->isIdenticalTo($wrapper->getStream()) ->string($wrapper->getStreamName()) ->isEqualTo('atoum://Foo'); } public function case_stream_open_not_hoa_protocol() { $this ->given( $wrapper = new SUT(), $path = LUT::NO_RESOLUTION, $mode = 'r', $options = STREAM_USE_PATH ) ->when($result = $wrapper->stream_open($path, $mode, $options, $openedPath)) ->then ->boolean($result) ->isFalse(); } public function case_stream_open_not_a_resource() { $this ->given( $this->function->fopen = function ($path, $mode, $options) use (&$_path, &$_mode, &$_options, &$_openedPath) { $_path = $path; $_mode = $mode; $_options = $options; return fopen($path, $mode, $options); }, $this->function->is_resource = false, $wrapper = new SUT(), $path = 'hoa://Test/Vfs/Foo?type=file', $mode = 'r', $options = STREAM_USE_PATH ) ->when($result = $wrapper->stream_open($path, $mode, $options, $openedPath)) ->then ->boolean($result) ->isFalse() ->string(SUT::realPath($path, true)) ->isEqualTo($_path) ->string($mode) ->isEqualTo($_mode) ->integer($options) ->isEqualTo($_options & STREAM_USE_PATH) ->resource($openedPath) ->isStream(); } public function case_stream_read() { $this ->given( $this->function->fread = function ($resource, $count) use (&$_resource, &$_count) { $_resource = $resource; $_count = $count; return fread($resource, $count); }, $wrapper = new SUT(), $count = 42, $this->openFile($wrapper, str_repeat('@', $count)) ) ->when($result = $wrapper->stream_read($count)) ->then ->string($result) ->hasLength($count) ->resource($_resource) ->isStream() ->isIdenticalTo($wrapper->getStream()) ->integer($_count) ->isEqualTo($count); } public function _case_stream_seek_xxx($offset, $whence) { return $this ->given( $this->function->fseek = function ($resource, $offset, $whence) use (&$_resource, &$_offset, &$_whence) { $_resource = $resource; $_offset = $offset; $_whence = $whence; return fseek($resource, $offset, $whence); }, $wrapper = new SUT(), $this->openFile($wrapper, 'foobar') ) ->when($result = $wrapper->stream_seek($offset, $whence)) ->then ->boolean($result) ->isTrue() ->resource($_resource) ->isStream() ->isIdenticalTo($wrapper->getStream()) ->integer($offset) ->isEqualTo($_offset) ->integer($whence) ->isEqualTo($_whence) ->integer(ftell($wrapper->getStream())); } public function case_stream_seek_set() { return $this ->_case_stream_seek_xxx(3, SEEK_SET) ->isEqualTo(3); } public function case_stream_seek_current() { return $this ->_case_stream_seek_xxx(4, SEEK_CUR) ->isEqualTo(4); } public function case_stream_seek_end() { return $this ->_case_stream_seek_xxx(-4, SEEK_END) ->isEqualTo(2); } public function case_stream_stat() { $this ->given( $this->function->fstat = function ($resource) use (&$_resource) { $_resource = $resource; return fstat($resource); }, $wrapper = new SUT(), $this->openFile($wrapper) ) ->when($result = $wrapper->stream_stat()) ->then ->array($result) ->resource($_resource) ->isStream() ->isIdenticalTo($wrapper->getStream()); } public function case_stream_tell() { $this ->given( $this->function->ftell = function ($resource) use (&$_resource) { $_resource = $resource; return ftell($resource); }, $wrapper = new SUT(), $this->openFile($wrapper, 'foo'), $wrapper->stream_seek(2) ) ->when($result = $wrapper->stream_tell()) ->then ->integer($result) ->isEqualTo(2) ->resource($_resource) ->isStream() ->isIdenticalTo($wrapper->getStream()); } public function case_stream_truncate() { $this ->given( $this->function->ftruncate = function ($resource, $size) use (&$_resource, &$_size) { $_resource = $resource; $_size = $size; return ftruncate($resource, $size); }, $wrapper = new SUT(), $this->openFile($wrapper, 'foobar'), $size = 3 ) ->when($result = $wrapper->stream_truncate($size)) ->then ->boolean($result) ->isTrue() ->resource($_resource) ->isStream() ->isIdenticalTo($wrapper->getStream()) ->integer($size) ->isEqualTo($_size) ->integer($wrapper->stream_tell()) ->isEqualTo(0) ->let($wrapper->stream_seek(0, SEEK_END)) ->integer($wrapper->stream_tell()) ->isEqualTo(3); } public function case_stream_write() { $this ->given( $this->function->fwrite = function ($resource, $data) use (&$_resource, &$_data) { $_resource = $resource; $_data = $data; return fwrite($resource, $data); }, $wrapper = new SUT(), $wrapper->stream_open('hoa://Test/Vfs/Foo?type=file', 'wb+', STREAM_USE_PATH, $openedPath), $data = 'foo' ) ->when($result = $wrapper->stream_write($data)) ->then ->integer($result) ->isEqualTo(strlen($data)) ->resource($_resource) ->isStream() ->isIdenticalTo($wrapper->getStream()) ->string($_data) ->isEqualTo($data) ->let($wrapper->stream_seek(0)) ->string($wrapper->stream_read(3)) ->isEqualTo($data); } public function case_dir_closedir() { $this ->given( $wrapper = new SUT(), $this->openDirectory($wrapper) ) ->when($result = $wrapper->dir_closedir()) ->then ->variable($result) ->isNull() ->variable($wrapper->getStream()) ->isNull() ->variable($wrapper->getStreamName()) ->isNull(); } public function case_dir_opendir() { $this ->given( $this->function->opendir = function ($path) use (&$_path) { $_path = $path; return opendir($path); }, $wrapper = new SUT(), $path = 'hoa://Test/Vfs/Bar?type=directory', $options = 0 ) ->when($result = $wrapper->dir_opendir($path, $options)) ->then ->boolean($result) ->isTrue() ->string(SUT::realPath($path, true)) ->isEqualTo($_path) ->resource($wrapper->getStream()) ->isStream() ->string($wrapper->getStreamName()) ->isEqualTo('atoum://Bar'); } public function case_dir_opendir_not_a_resource() { $this ->given( $this->function->opendir = function ($path) use (&$_path) { $_path = $path; return false; }, $wrapper = new SUT(), $path = 'hoa://Test/Vfs/Bar?type=directory', $options = 0 ) ->when($result = $wrapper->dir_opendir($path, $options)) ->then ->boolean($result) ->isFalse() ->string(SUT::realPath($path, true)) ->isEqualTo($_path) ->variable($wrapper->getStream()) ->isNull() ->variable($wrapper->getStreamName()) ->isNull(); } public function case_dir_readdir() { $this ->given( $this->function->readdir = function ($resource) use (&$_resource) { $_resource = $resource; return readdir($resource); }, $wrapper = new SUT(), $this->openDirectory($wrapper, ['Baz', 'Qux']) ) ->when($result = $wrapper->dir_readdir()) ->then ->string($result) ->isEqualTo('Baz') ->resource($_resource) ->isIdenticalTo($wrapper->getStream()); } public function case_dir_readdir_until_eod() { $this ->given( $this->function->readdir = function ($resource) use (&$_resource) { $_resource = $resource; return readdir($resource); }, $wrapper = new SUT(), $this->openDirectory($wrapper, ['Baz', 'Qux']) ) ->when($result = $wrapper->dir_readdir()) ->then ->string($result) ->isEqualTo('Baz') ->resource($_resource) ->isIdenticalTo($wrapper->getStream()) ->when($result = $wrapper->dir_readdir()) ->then ->string($result) ->isEqualTo('Qux') ->when($result = $wrapper->dir_readdir()) ->then ->boolean($result) ->isFalse(); } public function case_dir_rewinddir() { $this ->given( $this->function->rewinddir = function ($resource) use (&$_resource) { $_resource = $resource; return rewinddir($resource); }, $wrapper = new SUT(), $this->openDirectory($wrapper, ['Baz']), $wrapper->dir_readdir() ) ->when($result = $wrapper->dir_rewinddir()) ->then ->variable($result) ->isNull() ->when($result = $wrapper->dir_readdir()) ->then ->string($result) ->isEqualTo('Baz') ->resource($_resource) ->isIdenticalTo($wrapper->getStream()); } public function case_dir_mkdir() { $this ->given( $this->function->mkdir = function ($path, $mode, $options) use (&$_path, &$_mode, &$_options) { $_path = $path; $_mode = $mode; $_options = $options; return true; }, $wrapper = new SUT(), $this->openDirectory($wrapper), $path = 'Baz', $mode = 0755, $options = STREAM_MKDIR_RECURSIVE ) ->when($result = $wrapper->mkdir($path, $mode, $options)) ->then ->boolean($result) ->isTrue() ->string($_path) ->isEqualTo($path) ->integer($_mode) ->isEqualTo($_mode) ->integer($_options) ->isEqualTo($options | STREAM_MKDIR_RECURSIVE); } public function case_rename() { $this ->given( $this->function->rename = function ($from, $to) use (&$_from, &$_to) { $_to = $to; $_from = $from; return rename($from, $to); }, $wrapper = new SUT(), $this->openFile($wrapper), $from = 'hoa://Test/Vfs/Foo?type=file', $to = 'hoa://Test/Vfs/Oof?type=file' ) ->when($result = $wrapper->rename($from, $to)) ->then ->boolean($result) ->isTrue() ->string($_from) ->isEqualTo(SUT::realPath($from)) ->string($_to) ->isEqualTo(SUT::realPath($_to, false)); } public function case_rmdir() { $this ->given( $this->function->rmdir = function ($path) use (&$_path) { $_path = $path; return rmdir($path); }, $wrapper = new SUT(), $this->openDirectory($wrapper) ) ->when($result = $wrapper->rmdir('hoa://Test/Vfs/Bar?type=directory', 0)) ->then ->boolean($result) ->isTrue(); } public function case_rmdir_a_file() { $this ->given( $wrapper = new SUT(), $this->openFile($wrapper) ) ->when($result = $wrapper->rmdir('hoa://Test/Vfs/Foo?type=file', 0)) ->then ->boolean($result) ->isFalse(); } public function case_unlink() { $this ->given( $wrapper = new SUT(), $this->openFile($wrapper) ) ->when($result = $wrapper->unlink('hoa://Test/Vfs/Foo?type=file')) ->then ->boolean($result) ->isTrue(); } public function case_rmdir_a_directory() { $this ->given( $wrapper = new SUT(), $this->openDirectory($wrapper) ) ->when($result = $wrapper->unlink('hoa://Test/Vfs/Bar?type=directory')) ->then ->boolean($result) ->isFalse(); } public function case_url_stat() { $this ->given( $this->function->stat = function ($path) use (&$_path) { $_path = $path; return stat($path); }, $wrapper = new SUT(), $this->openFile($wrapper), $path = 'hoa://Test/Vfs/Foo?type=file' ) ->when($result = $wrapper->url_stat($path, 0)) ->then ->let( $keys = [ 'dev', 'ino', 'mode', 'nlink', 'uid', 'gid', 'rdev', 'size', 'atime', 'mtime', 'ctime', 'blksize', 'blocks' ] ) ->array($result) ->hasSize(26) ->hasKeys($keys) ->hasKeys(array_keys($keys)) ->string($_path) ->isEqualTo(SUT::realPath($path)); } public function case_url_stat_not_hoa_protocol() { $this ->given( $wrapper = new SUT(), $path = LUT::NO_RESOLUTION ) ->when(function () use ($wrapper, $path) { $wrapper->url_stat($path, 0); }) ->then ->error() ->exists(); } protected function openFile(SUT $wrapper, $content = '') { $wrapper->stream_open('hoa://Test/Vfs/Foo?type=file', 'wb+', STREAM_USE_PATH, $openedPath); fwrite($openedPath, $content, strlen($content)); fseek($openedPath, 0, SEEK_SET); return $wrapper; } protected function openDirectory(SUT $wrapper, array $children = []) { $wrapper->dir_opendir('hoa://Test/Vfs/Bar?type=directory', 0); foreach ($children as $child) { resolve('hoa://Test/Vfs/Bar/' . $child . '?type=file'); } return $wrapper; } } when($result = new SUT('foo', 0)) ->then ->object($result) ->isInstanceOf('Hoa\Exception\Exception'); } } given( $this->constant->WITH_COMPOSER = false, $node = new SUT('foo', 'bar') ) ->when($result = $node->reach()) ->then ->string($result) ->isEqualTo('bar'); } public function case_reach_without_composer_with_a_queue() { $this ->given( $this->constant->WITH_COMPOSER = false, $node = new SUT('foo', 'bar') ) ->when($result = $node->reach('baz')) ->then ->string($result) ->isEqualTo('baz'); } public function case_reach_with_composer_without_a_queue_and_a_single_reach() { $this ->given( $this->constant->WITH_COMPOSER = true, $node = new SUT('foo', 'Bar' . DS . 'Baz' . DS . 'Qux' . DS) ) ->when($result = $node->reach()) ->then ->string($result) ->isEqualTo('Bar' . DS . 'Baz' . DS . 'qux' . DS); } public function case_reach_with_composer_without_a_queue_and_a_multiple_reaches() { $this ->given( $this->constant->WITH_COMPOSER = true, $node = new SUT( 'foo', 'Bar' . DS . 'Baz' . DS . 'Qux' . DS . RS . 'Hello' . DS . 'Mister' . DS . 'Anderson' . DS ) ) ->when($result = $node->reach()) ->then ->string($result) ->isEqualTo( 'Bar' . DS . 'Baz' . DS . 'qux' . DS . RS . 'Hello' . DS . 'Mister' . DS . 'anderson' . DS ); } public function case_reach_with_composer_with_a_simple_queue() { $this ->given( $this->constant->WITH_COMPOSER = true, $node = new SUT('foo', 'Bar' . DS . 'Baz' . DS . 'Qux' . DS) ) ->when($result = $node->reach('Hello')) ->then ->string($result) ->isEqualTo( "\r" . 'Bar' . DS . 'Baz' . DS . 'Qux' . DS . 'hello' . RS . "\r" . dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))) ); } public function case_reach_with_composer_with_a_queue() { $this ->given( $this->constant->WITH_COMPOSER = true, $node = new SUT('foo', 'Bar' . DS) ) ->when($result = $node->reach('Hello/Mister/Anderson')) ->then ->string($result) ->isEqualTo( "\r" . 'Bar' . DS . 'hello' . DS . 'Mister' . DS . 'Anderson' . RS . "\r" . dirname(dirname(dirname(dirname(dirname(dirname(__DIR__)))))) . DS . 'Mister' . DS . 'Anderson' ); } } when($result = new SUT()) ->then ->object($result) ->isInstanceOf('ArrayAccess') ->isInstanceOf('IteratorAggregate'); } public function case_empty_constructor() { $this ->when($result = new SUT()) ->then ->variable($result->getName()) ->isNull() ->array(iterator_to_array($result->getIterator())) ->isEmpty(); } public function case_constructor_with_a_name() { $this ->given($name = 'foo') ->when($result = new SUT($name)) ->then ->string($result->getName()) ->isEqualTo($name) ->array(iterator_to_array($result->getIterator())) ->isEmpty(); } public function case_constructor_with_a_name_and_children() { $this ->given( $name = 'foo', $children = [new SUT('bar'), new SUT('baz')] ) ->when($result = new SUT($name, '', $children)) ->then ->string($result->getName()) ->isEqualTo($name) ->array(iterator_to_array($result->getIterator())) ->hasSize(2); } public function case_offset_set() { $this ->given( $root = new SUT(), $name = 'foo', $node = new SUT(), $oldCountChildren = count(iterator_to_array($root->getIterator())) ) ->when($result = $root->offsetSet($name, $node)) ->then ->integer(count(iterator_to_array($root->getIterator()))) ->isEqualTo($oldCountChildren + 1) ->object($root[$name]) ->isIdenticalTo($node); } public function case_offset_set_not_a_node() { $this ->given($root = new SUT()) ->exception(function () use ($root) { $root->offsetSet('foo', null); }) ->isInstanceOf('Hoa\Protocol\Exception'); } public function case_offset_set_no_name() { $this ->given($root = new SUT()) ->exception(function () use ($root) { $root->offsetSet(null, new SUT()); }) ->isInstanceOf('Hoa\Protocol\Exception'); } public function case_offset_get() { $this ->given( $root = new SUT(), $child = new SUT(), $root['foo'] = $child ) ->when($result = $root->offsetGet('foo')) ->then ->object($result) ->isIdenticalTo($child); } public function case_offset_get_an_unknown_name() { $this ->given($root = new SUT()) ->exception(function () use ($root) { $root->offsetGet('foo'); }) ->isInstanceOf('Hoa\Protocol\Exception'); } public function case_offset_exists() { $this ->given( $root = new SUT(), $child = new SUT(), $root['foo'] = $child ) ->when($result = $root->offsetExists('foo')) ->then ->boolean($result) ->isTrue(); } public function case_offset_not_exists() { $this ->given($root = new SUT()) ->when($result = $root->offsetExists('foo')) ->then ->boolean($result) ->isFalse(); } public function case_offset_unset() { $this ->given( $root = new SUT(), $child = new SUT(), $root['foo'] = $child ) ->when($result = $root->offsetUnset('foo')) ->then ->boolean($root->offsetExists('foo')) ->isFalse(); } public function case_reach() { $this ->given( $reach = 'bar', $node = new SUT('foo', $reach) ) ->when($result = $node->reach()) ->then ->string($result) ->isEqualTo($reach); } public function case_reach_with_a_queue() { $this ->given( $queue = 'baz', $node = new SUT('foo', 'bar') ) ->when($result = $node->reach('baz')) ->then ->string($result) ->isEqualTo($queue); } public function case_reach_id() { $this ->given($node = new SUT()) ->exception(function () use ($node) { $node->reachId('foo'); }) ->isInstanceOf('Hoa\Protocol\Exception'); } public function case_set_reach() { $this ->given( $reach = 'bar', $node = new SUT('foo', $reach) ) ->when($result = $node->setReach('baz')) ->then ->string($result) ->isEqualTo($reach) ->string($node->reach()) ->isEqualTo('baz'); } public function case_get_name() { $this ->given( $name = 'foo', $node = new SUT($name) ) ->when($result = $node->getName()) ->then ->string($result) ->isEqualTo($name); } public function case_get_iterator() { $this ->given( $childA = new SUT('bar'), $childB = new SUT('baz'), $children = [$childA, $childB] ) ->when($result = new SUT('foo', '', $children)) ->then ->object($result->getIterator()) ->isInstanceOf('ArrayIterator') ->array(iterator_to_array($result->getIterator())) ->isEqualTo([ 'bar' => $childA, 'baz' => $childB ]); } public function case_get_root() { $this ->when($result = SUT::getRoot()) ->then ->object($result) ->isIdenticalTo(LUT::getInstance()); } public function case_to_string_as_leaf() { $this ->given($node = new SUT('foo')) ->when($result = $node->__toString()) ->then ->string($result) ->isEqualTo('foo' . "\n"); } public function case_to_string_as_node() { $this ->given( $node = new SUT('foo'), $node[] = new SUT('bar'), $node[] = new SUT('baz') ) ->when($result = $node->__toString()) ->then ->string($result) ->isEqualTo( 'foo' . "\n" . ' bar' . "\n" . ' baz' . "\n" ); } } resolve($path, $exists); } public function stream_cast($castAs) { return false; } public function stream_close() { if (true === @fclose($this->getStream())) { $this->_stream = null; $this->_streamName = null; } return; } public function stream_eof() { return feof($this->getStream()); } public function stream_flush() { return fflush($this->getStream()); } public function stream_lock($operation) { return flock($this->getStream(), $operation); } public function stream_metadata($path, $option, $values) { $path = static::realPath($path, false); switch ($option) { case STREAM_META_TOUCH: $arity = count($values); if (0 === $arity) { $out = touch($path); } elseif (1 === $arity) { $out = touch($path, $values[0]); } else { $out = touch($path, $values[0], $values[1]); } break; case STREAM_META_OWNER_NAME: case STREAM_META_OWNER: $out = chown($path, $values); break; case STREAM_META_GROUP_NAME: case STREAM_META_GROUP: $out = chgrp($path, $values); break; case STREAM_META_ACCESS: $out = chmod($path, $values); break; default: $out = false; } return $out; } public function stream_open($path, $mode, $options, &$openedPath) { $path = static::realPath($path, 'r' === $mode[0]); if (Protocol::NO_RESOLUTION === $path) { return false; } if (null === $this->context) { $openedPath = fopen($path, $mode, $options & STREAM_USE_PATH); } else { $openedPath = fopen( $path, $mode, $options & STREAM_USE_PATH, $this->context ); } if (false === is_resource($openedPath)) { return false; } $this->_stream = $openedPath; $this->_streamName = $path; return true; } public function stream_read($count) { return fread($this->getStream(), $count); } public function stream_seek($offset, $whence = SEEK_SET) { return 0 === fseek($this->getStream(), $offset, $whence); } public function stream_stat() { return fstat($this->getStream()); } public function stream_tell() { return ftell($this->getStream()); } public function stream_truncate($size) { return ftruncate($this->getStream(), $size); } public function stream_write($data) { return fwrite($this->getStream(), $data); } public function dir_closedir() { closedir($this->getStream()); $this->_stream = null; $this->_streamName = null; return; } public function dir_opendir($path, $options) { $path = static::realPath($path); $handle = null; if (null === $this->context) { $handle = @opendir($path); } else { $handle = @opendir($path, $this->context); } if (false === $handle) { return false; } $this->_stream = $handle; $this->_streamName = $path; return true; } public function dir_readdir() { return readdir($this->getStream()); } public function dir_rewinddir() { return rewinddir($this->getStream()); } public function mkdir($path, $mode, $options) { if (null === $this->context) { return mkdir( static::realPath($path, false), $mode, $options | STREAM_MKDIR_RECURSIVE ); } return mkdir( static::realPath($path, false), $mode, $options | STREAM_MKDIR_RECURSIVE, $this->context ); } public function rename($from, $to) { if (null === $this->context) { return rename(static::realPath($from), static::realPath($to, false)); } return rename( static::realPath($from), static::realPath($to, false), $this->context ); } public function rmdir($path, $options) { if (null === $this->context) { return rmdir(static::realPath($path)); } return rmdir(static::realPath($path), $this->context); } public function unlink($path) { if (null === $this->context) { return unlink(static::realPath($path)); } return unlink(static::realPath($path), $this->context); } public function url_stat($path, $flags) { $path = static::realPath($path); if (Protocol::NO_RESOLUTION === $path) { if ($flags & STREAM_URL_STAT_QUIET) { return 0; } else { return trigger_error( 'Path ' . $path . ' cannot be resolved.', E_WARNING ); } } if ($flags & STREAM_URL_STAT_LINK) { return @lstat($path); } return @stat($path); } public function getStream() { return $this->_stream; } public function getStreamName() { return $this->_streamName; } } stream_wrapper_register('hoa', Wrapper::class); } namespace { if (!function_exists('resolve')) { function resolve($path, $exists = true, $unfold = false) { return Hoa\Protocol::getInstance()->resolve($path, $exists, $unfold); } } } getOption($v)) { switch ($c) { case 'E': $exists = false; break; case 'u': $unfold = true; break; case 't': $tree = true; break; case 'V': $verbose = false; break; case 'h': case '?': return $this->usage(); case '__ambiguous': $this->resolveOptionAmbiguity($v); break; } } $this->parser->listInputs($path); if (null === $path) { return $this->usage(); } if (true === $tree) { $protocol = Protocol::getInstance(); $foo = substr($path, 0, 6); if ('hoa://' !== $foo) { return; } $path = substr($path, 6); $current = $protocol; foreach (explode('/', $path) as $component) { if (!isset($current[$component])) { break; } $current = $current[$component]; } echo $current; return; } if (true === $verbose) { echo Console\Cursor::colorize('foreground(yellow)'), $path, Console\Cursor::colorize('normal'), ' is equivalent to:', "\n"; } $resolved = resolve($path, $exists, $unfold); foreach ((array) $resolved as $r) { echo $r, "\n"; } return; } public function usage() { echo 'Usage : protocol:resolve path', "\n", 'Options :', "\n", $this->makeUsageOptionsList([ 'E' => 'Do not check if the resolution result exists.', 'u' => 'Unfold all possible results.', 't' => 'Print the tree from the path.', 'V' => 'No-verbose, i.e. be as quiet as possible, just print ' . 'essential information.', 'help' => 'This help.' ]), "\n"; return; } } __halt_compiler(); Resolve `hoa://` paths. _reach) as $part) { $out[] = "\r" . $part . strtolower($head) . $queue; } $out[] = "\r" . dirname(dirname(dirname(dirname(__DIR__)))) . $queue; return implode(RS, $out); } $out = []; foreach (explode(RS, $this->_reach) as $part) { $pos = strrpos(rtrim($part, DIRECTORY_SEPARATOR), DIRECTORY_SEPARATOR) + 1; $head = substr($part, 0, $pos); $tail = substr($part, $pos); $out[] = $head . strtolower($tail); } $this->_reach = implode(RS, $out); return parent::reach($queue); } } _name = $name; } if (null !== $reach) { $this->_reach = $reach; } foreach ($children as $child) { $this[] = $child; } return; } public function offsetSet($name, $node) { if (!($node instanceof self)) { throw new Protocol\Exception( 'Protocol node must extend %s.', 0, __CLASS__ ); } if (empty($name)) { $name = $node->getName(); } if (empty($name)) { throw new Protocol\Exception( 'Cannot add a node to the `hoa://` protocol without a name.', 1 ); } $this->_children[$name] = $node; return; } public function offsetGet($name) { if (!isset($this[$name])) { throw new Protocol\Exception( 'Node %s does not exist.', 2, $name ); } return $this->_children[$name]; } public function offsetExists($name) { return true === array_key_exists($name, $this->_children); } public function offsetUnset($name) { unset($this->_children[$name]); return; } protected function _resolve($path, &$accumulator, $id = null) { if (substr($path, 0, 6) == 'hoa://') { $path = substr($path, 6); } if (empty($path)) { return null; } if (null === $accumulator) { $accumulator = []; $posId = strpos($path, '#'); if (false !== $posId) { $id = substr($path, $posId + 1); $path = substr($path, 0, $posId); } else { $id = null; } } $path = trim($path, '/'); $pos = strpos($path, '/'); if (false !== $pos) { $next = substr($path, 0, $pos); } else { $next = $path; } if (isset($this[$next])) { if (false === $pos) { if (null === $id) { $this->_resolveChoice($this[$next]->reach(), $accumulator); return true; } $accumulator = null; return $this[$next]->reachId($id); } $tnext = $this[$next]; $this->_resolveChoice($tnext->reach(), $accumulator); return $tnext->_resolve(substr($path, $pos + 1), $accumulator, $id); } $this->_resolveChoice($this->reach($path), $accumulator); return true; } protected function _resolveChoice($reach, array &$accumulator) { if (empty($accumulator)) { $accumulator = explode(RS, $reach); return; } if (false === strpos($reach, RS)) { if (false !== $pos = strrpos($reach, "\r")) { $reach = substr($reach, $pos + 1); foreach ($accumulator as &$entry) { $entry = null; } } foreach ($accumulator as &$entry) { $entry .= $reach; } return; } $choices = explode(RS, $reach); $ref = $accumulator; $accumulator = []; foreach ($choices as $choice) { if (false !== $pos = strrpos($choice, "\r")) { $choice = substr($choice, $pos + 1); foreach ($ref as $entry) { $accumulator[] = $choice; } } else { foreach ($ref as $entry) { $accumulator[] = $entry . $choice; } } } unset($ref); return; } public function reach($queue = null) { return empty($queue) ? $this->_reach : $queue; } public function reachId($id) { throw new Protocol\Exception( 'The node %s has no ID support (tried to reach #%s).', 4, [$this->getName(), $id] ); } public function setReach($reach) { $old = $this->_reach; $this->_reach = $reach; return $old; } public function getName() { return $this->_name; } protected function getReach() { return $this->_reach; } public function getIterator() { return new \ArrayIterator($this->_children); } public static function getRoot() { return Protocol::getInstance(); } public function __toString() { static $i = 0; $out = str_repeat(' ', $i) . $this->getName() . "\n"; foreach ($this as $node) { ++$i; $out .= $node; --$i; } return $out; } } Consistency::flexEntity('Hoa\Protocol\Node\Node'); given( $name = 'custom', SUT::register($name, CustomFilter::class), $filename = 'hoa://Test/Vfs/Foo?type=file', $content = 'Hello, World!', file_put_contents($filename, $content), $stream = fopen($filename, 'r') ) ->when( SUT::append($stream, $name), $result = stream_get_contents($stream) ) ->then ->string($result) ->isEqualTo( strtolower($content) . ' ' . strlen($content) ); } } class CustomFilter extends LUT\Filter\LateComputed { protected function compute() { $this->_buffer = strtolower($this->_buffer) . ' ' . strlen($this->_buffer); return; } } given( $filename = 'hoa://Test/Vfs/Foo?type=file', $content = 'Hello, World!', file_put_contents($filename, $content), $stream = fopen($filename, 'r'), $name = 'string.toupper' ) ->when( SUT::append($stream, $name), $result = stream_get_contents($stream) ) ->then ->string($result) ->isEqualTo(strtoupper($content)); } public function case_prepend() { $this ->given( $filename = 'hoa://Test/Vfs/Foo?type=file', $content = 'Hello, World!', file_put_contents($filename, $content), $stream = fopen($filename, 'r'), $name = 'string.toupper' ) ->when( SUT::prepend($stream, $name), $result = stream_get_contents($stream) ) ->then ->string($result) ->isEqualTo(strtoupper($content)); } public function case_append_append() { $this ->given( $filename = 'hoa://Test/Vfs/Foo?type=file', $content = 'Hello, World!', file_put_contents($filename, $content), $stream = fopen($filename, 'r'), $name1 = 'string.toupper', $name2 = 'string.tolower' ) ->when( SUT::append($stream, $name1), SUT::append($stream, $name2), $result = stream_get_contents($stream) ) ->then ->string($result) ->isEqualTo(strtolower($content)); } public function case_append_prepend() { $this ->given( $filename = 'hoa://Test/Vfs/Foo?type=file', $content = 'Hello, World!', file_put_contents($filename, $content), $stream = fopen($filename, 'r'), $name1 = 'string.toupper', $name2 = 'string.tolower' ) ->when( SUT::append($stream, $name1), SUT::prepend($stream, $name2), $result = stream_get_contents($stream) ) ->then ->string($result) ->isEqualTo(strtoupper($content)); } public function case_prepend_prepend() { $this ->given( $filename = 'hoa://Test/Vfs/Foo?type=file', $content = 'Hello, World!', file_put_contents($filename, $content), $stream = fopen($filename, 'r'), $name1 = 'string.toupper', $name2 = 'string.tolower' ) ->when( SUT::prepend($stream, $name1), SUT::prepend($stream, $name2), $result = stream_get_contents($stream) ) ->then ->string($result) ->isEqualTo(strtoupper($content)); } public function case_append_1000_filters() { $this ->given( $filename = 'hoa://Test/Vfs/Foo?type=file', $content = 'Hello, World!', file_put_contents($filename, $content), $stream = fopen($filename, 'r'), $name = 'string.toupper' ) ->when(function () use ($stream, $name) { for ($i = 1000; $i >= 0; --$i) { $this->resource(SUT::prepend($stream, $name)); } }) ->when($result = stream_get_contents($stream)) ->then ->string($result) ->isEqualTo(strtoupper($content)); } } given( $port = mt_rand(10000, 12000), exec( sprintf( 'php -S 127.0.0.1:%d -t %s > /dev/null 2>&1 & echo $! && sleep 0.2', $port, dirname(__DIR__) . DS . 'Fixtures' ), $outputs ), $pid = $outputs[0], $stream = new SUT('https://127.0.0.1:' . $port, null, true), $stream->on( 'connect', function (Event\Bucket $bucket) use ($self, &$connectCalled) { $connectCalled = true; $data = $bucket->getData(); $self ->array($data) ->isEqualTo([ 'code' => 0, 'severity' => 0, 'message' => null, 'transferred' => 0, 'max' => 0 ]); } ), $stream->on( 'mimetype', function (Event\Bucket $bucket) use ($self, &$mimetypeCalled) { $mimetypeCalled = true; $data = $bucket->getData(); $self ->array($data) ->isEqualTo([ 'code' => 0, 'severity' => 0, 'message' => 'text/html; charset=UTF-8', 'transferred' => 0, 'max' => 0 ]); } ), $stream->on( 'size', function (Event\Bucket $bucket) use ($self, &$sizeCalled) { $sizeCalled = true; $data = $bucket->getData(); $self ->array($data) ->isEqualTo([ 'code' => 0, 'severity' => 0, 'message' => 'Content-Length: 14', 'transferred' => 0, 'max' => 14 ]); } ), $stream->on( 'progress', function (Event\Bucket $bucket) use ($self, &$progressCalled) { $progressCalled = true; $data = $bucket->getData(); $self ->array($data) ->isEqualTo([ 'code' => 0, 'severity' => 0, 'message' => null, 'transferred' => 0, 'max' => 14 ]); } ) ) ->when($stream->open()) ->then ->boolean($connectCalled) ->isTrue() ->boolean($mimetypeCalled) ->isTrue() ->boolean($sizeCalled) ->isTrue() ->boolean($progressCalled) ->isTrue() ->let(!empty($pid) && exec('kill ' . $pid)); } } class SUT extends LUT\Stream { protected function &_open($streamName, LUT\Context $context = null) { if (null === $context) { $out = fopen($streamName, 'rb'); } else { $out = fopen($streamName, 'rb', false, $context->getContext()); } return $out; } protected function _close() { return fclose($this->getStream()); } } when($result = new SUT('foo', 0)) ->then ->object($result) ->isInstanceOf(HoaException::class); } } when($result = new \Mock\Hoa\Stream\Wrapper\IWrapper\IWrapper()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\Wrapper\IWrapper\File::class) ->isInstanceOf(LUT\Wrapper\IWrapper\Stream::class); } } when($result = new \Mock\Hoa\Stream\Wrapper\IWrapper\Stream()) ->then ->object($result) ->isInstanceOf(SUT::class); } } when($result = new \Mock\Hoa\Stream\Wrapper\IWrapper\File()) ->then ->object($result) ->isInstanceOf(SUT::class); } } given($oldIsRegistered = SUT::isRegistered('foo')) ->when($result = SUT::register('foo', 'StdClass')) ->then ->boolean($result) ->isTrue() ->boolean($oldIsRegistered) ->isFalse() ->boolean(SUT::isRegistered('foo')) ->isTrue(); } public function case_register_already_registered() { $this ->exception(function () { SUT::register('php', 'ClassName'); }) ->isInstanceOf(LUT\Exception::class) ->hasMessage('The protocol php is already registered.'); } public function case_register_implementation_does_not_exist() { $this ->exception(function () { SUT::register('foo', 'ClassName'); }) ->isInstanceOf(LUT\Exception::class) ->hasMessage( 'Cannot use the ClassName class for the implementation ' . 'of the foo protocol because it is not found.' ); } public function case_unregister() { $this ->given( SUT::register('foo', 'StdClass'), $oldIsRegistered = SUT::isRegistered('foo') ) ->when($result = SUT::unregister('foo')) ->then ->boolean($result) ->isTrue() ->boolean($oldIsRegistered) ->isTrue() ->boolean(SUT::isRegistered('foo')) ->isFalse(); } public function case_unregister_unregistered_protocol() { $this ->when($result = SUT::unregister('foo')) ->then ->boolean($result) ->isFalse(); } public function case_restore_registered_protocol() { $this ->when($result = SUT::restore('php')) ->then ->boolean($result) ->isTrue(); } public function case_restore_unregistered_protocol() { $this ->when($result = SUT::restore('foo')) ->then ->boolean($result) ->isFalse(); } public function case_is_registered() { $this ->when($result = SUT::isRegistered('php')) ->then ->boolean($result) ->isTrue(); } public function case_is_not_registered() { $this ->when($result = SUT::isRegistered('foo')) ->then ->boolean($result) ->isFalse(); } public function case_get_registered() { $this ->when($result = SUT::getRegistered()) ->then ->array($result) ->containsValues([ 'https', 'php', 'file', 'glob', 'data', 'http', 'hoa' ]); } public function case_get_registered_dynamically() { $this ->given($oldCount = count(SUT::getRegistered())) ->when( SUT::register('foo', \StdClass::class), $result = SUT::getRegistered() ) ->then ->integer(count($result)) ->isEqualTo($oldCount + 1) ->when( SUT::unregister('foo'), $result = SUT::getRegistered() ) ->then ->integer(count($result)) ->isEqualTo($oldCount); } } when($result = new SUT('foo', 0)) ->then ->object($result) ->isInstanceOf(LUT\Exception::class); } } when($result = new \Mock\Hoa\Stream\IStream\Statable()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } public function case_constants() { $this ->when($result = SUT::SIZE_UNDEFINED) ->then ->integer($result) ->isEqualTo(-1); } } when($result = new \Mock\Hoa\Stream\IStream\Touchable()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } public function case_constants() { $this ->when($result = SUT::OVERWRITE) ->then ->boolean($result) ->isEqualTo(true) ->when($result = SUT::DO_NOT_OVERWRITE) ->then ->boolean($result) ->isEqualTo(false) ->when($result = SUT::MAKE_DIRECTORY) ->then ->boolean($result) ->isEqualTo(true) ->when($result = SUT::DO_NOT_MAKE_DIRECTORY) ->then ->boolean($result) ->isEqualTo(false); } } when($result = new \Mock\Hoa\Stream\IStream\In()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } } when($result = new \Mock\Hoa\Stream\IStream\Structural()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } } when($result = new \Mock\Hoa\Stream\IStream\Out()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } } when($result = new \Mock\Hoa\Stream\IStream\Pointable()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } public function case_constants() { $this ->when($result = SUT::SEEK_SET) ->then ->integer($result) ->isEqualTo(SEEK_SET) ->when($result = SUT::SEEK_CURRENT) ->then ->integer($result) ->isEqualTo(SEEK_CUR) ->when($result = SUT::SEEK_END) ->then ->integer($result) ->isEqualTo(SEEK_END); } } when($result = new \Mock\Hoa\Stream\IStream\Stream()) ->then ->object($result) ->isInstanceOf(SUT::class); } } when($result = new \Mock\Hoa\Stream\IStream\Bufferable()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } } when($result = new \Mock\Hoa\Stream\IStream\Lockable()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } public function case_constants() { $this ->when($result = SUT::LOCK_SHARED) ->then ->integer($result) ->isEqualTo(LOCK_SH) ->when($result = SUT::LOCK_EXCLUSIVE) ->then ->integer($result) ->isEqualTo(LOCK_EX) ->when($result = SUT::LOCK_RELEASE) ->then ->integer($result) ->isEqualTo(LOCK_UN) ->when($result = SUT::LOCK_NO_BLOCK) ->then ->integer($result) ->isEqualTo(LOCK_NB); } } when($result = new \Mock\Hoa\Stream\IStream\Pathable()) ->then ->object($result) ->isInstanceOf(SUT::class) ->isInstanceOf(LUT\IStream\Stream::class); } } boolean(SUT::IS_A_BRIGADE) ->isTrue() ->boolean(SUT::IS_A_STREAM) ->isFalse(); } public function case_construct_a_brigade() { $this ->given($brigade = 'foo') ->when($result = new SUT($brigade, SUT::IS_A_BRIGADE)) ->then ->boolean($result->getType()) ->isEqualTo(SUT::IS_A_BRIGADE) ->variable($result->getBrigade()) ->isIdenticalTo($brigade) ->isIdenticalTo('foo'); } public function case_construct_a_stream() { $this ->given( $stream = fopen(__FILE__, 'r'), $buffer = 'bar' ) ->when($result = new SUT($stream, SUT::IS_A_STREAM, $buffer)) ->then ->boolean($result->getType()) ->isEqualTo(SUT::IS_A_STREAM) ->let($bucket = $this->invoke($result)->getBucket()) ->object($bucket) ->isInstanceOf(\StdClass::class) ->resource($bucket->bucket) ->string($bucket->data) ->isEqualTo($buffer) ->integer($bucket->datalen) ->isEqualTo(strlen($buffer)) ->object($result->getBrigade()) ->isIdenticalTo($bucket); } public function case_eob() { $this ->given( $stream = fopen(__FILE__, 'r'), $bucket = new SUT($stream, SUT::IS_A_STREAM) ) ->when($result = $bucket->eob()) ->then ->boolean($result) ->isTrue(); } public function case_set_data() { $this ->given( $stream = fopen(__FILE__, 'r'), $oldBuffer = 'bar', $bucket = new SUT($stream, SUT::IS_A_STREAM, $oldBuffer), $buffer = 'bazqux' ) ->when($result = $bucket->setData('bazqux')) ->then ->string($result) ->isEqualTo($oldBuffer) ->let($_bucket = $this->invoke($bucket)->getBucket()) ->object($_bucket) ->isInstanceOf(\StdClass::class) ->resource($_bucket->bucket) ->string($_bucket->data) ->isEqualTo($buffer) ->integer($_bucket->datalen) ->isEqualTo(strlen($buffer)) ->object($bucket->getBrigade()) ->isIdenticalTo($_bucket); } public function case_get_data() { $this ->given( $stream = fopen(__FILE__, 'r'), $buffer = 'bar', $bucket = new SUT($stream, SUT::IS_A_STREAM, $buffer) ) ->when($result = $bucket->getData()) ->then ->string($result) ->isEqualTo($buffer) ->isEqualTo($this->invoke($bucket)->getBucket()->data); } public function case_get_length() { $this ->given( $stream = fopen(__FILE__, 'r'), $buffer = 'bar', $bucket = new SUT($stream, SUT::IS_A_STREAM, $buffer) ) ->when($result = $bucket->getLength()) ->then ->integer($result) ->isEqualTo(strlen($buffer)) ->isEqualTo($this->invoke($bucket)->getBucket()->datalen); } } exception(function () { SUT::getInstance(null); }) ->isInstanceOf(LUT\Exception::class); } public function case_get_new_instance() { $this ->when($result = SUT::getInstance('foo')) ->then ->object($result) ->isInstanceOf(SUT::class); } public function case_get_new_instances() { $this ->when($result = SUT::getInstance('foo')) ->then ->object($result) ->isNotIdenticalTo(SUT::getInstance('bar')); } public function case_get_same_instance() { $this ->when($result = SUT::getInstance('foo')) ->then ->object($result) ->isIdenticalTo(SUT::getInstance('foo')); } public function case_get_id() { $this ->given( $id = 'foo', $context = SUT::getInstance($id) ) ->when($result = $context->getId()) ->then ->string($result) ->isEqualTo($id); } public function case_context_exists() { $this ->given( $id = 'foo', SUT::getInstance($id) ) ->when($result = SUT::contextExists($id)) ->then ->boolean($result) ->isTrue(); } public function case_context_does_not_exist() { $this ->when($result = SUT::contextExists('foo')) ->then ->boolean($result) ->isFalse(); } public function case_set_options() { $this ->given( $context = SUT::getInstance('foo'), $options = ['bar' => ['baz' => 'qux']] ) ->when($result = $context->setOptions($options)) ->then ->boolean($result) ->isTrue(); } public function case_get_options() { $this ->given( $context = SUT::getInstance('foo'), $options = ['bar' => ['baz' => 'qux']], $context->setOptions($options) ) ->when($result = $context->getOptions()) ->then ->array($result) ->isEqualTo($options); } public function case_set_parameters() { $this ->given( $context = SUT::getInstance('foo'), $parameters = [ 'notificaion' => 'callback', 'options' => ['bar' => ['baz' => 'qux']] ] ) ->when($result = $context->setParameters($parameters)) ->then ->boolean($result) ->isTrue(); } public function case_get_parameters() { $this ->given( $context = SUT::getInstance('foo'), $parameters = [ 'notification' => 'callback', 'options' => ['bar' => ['baz' => 'qux']] ], $context->setParameters($parameters) ) ->when($result = $context->getParameters()) ->then ->array($result) ->isEqualTo($parameters); } public function case_get_context() { $this ->given($context = SUT::getInstance('foo')) ->when($result = $context->getContext()) ->then ->resource($result) ->isStreamContext(); } } when($result = new SUT('foo', 0)) ->then ->object($result) ->isInstanceOf(LUT\Exception::class); } } boolean(SUT::OVERWRITE) ->isTrue() ->boolean(SUT::DO_NOT_OVERWRITE) ->isFalse() ->integer(SUT::READ) ->isEqualTo(STREAM_FILTER_READ) ->integer(SUT::WRITE) ->isEqualTo(STREAM_FILTER_WRITE) ->integer(SUT::READ_AND_WRITE) ->isEqualTo(STREAM_FILTER_ALL); } public function case_register() { $this ->when($result = SUT::register('foo', \StdClass::class)) ->then ->boolean($result) ->isTrue(); } public function case_register_already_registered_do_not_overwrite() { $this ->given( $name = 'foo', $class = \StdClass::class, SUT::register($name, $class) ) ->exception(function () use ($name, $class) { SUT::register($name, $class); }) ->isInstanceOf(LUT\Filter\Exception::class) ->hasMessage('Filter foo is already registered.'); } public function case_register_already_registered_do_overwrite() { $this ->given( $name = 'foo', SUT::register($name, \StdClass::class), new \Mock\StdClass() ) ->when($result = SUT::register($name, \Mock\StdClass::class, SUT::OVERWRITE)) ->then ->boolean($result) ->isFalse(); } public function case_register_empty_name() { $this ->exception(function () { SUT::register('', \StdClass::class); }) ->isInstanceOf(LUT\Filter\Exception::class) ->hasMessage( 'Filter name cannot be empty ' . '(implementation class is StdClass).' ); } public function case_register_unknown_class() { $this ->exception(function () { SUT::register('foo', '42Foo'); }) ->isInstanceOf(LUT\Filter\Exception::class) ->hasMessage( 'Cannot register the 42Foo class for the filter foo ' . 'because it does not exist.' ); } public function case_append() { $this ->given( $stream = fopen('hoa://Test/Vfs/Foo?type=file', 'r'), $name = 'string.toupper' ) ->when($result = SUT::append($stream, $name)) ->then ->resource($result) ->isStreamFilter(); } public function case_prepend() { $this ->given( $stream = fopen('hoa://Test/Vfs/Foo?type=file', 'r'), $name = 'string.toupper' ) ->when($result = SUT::prepend($stream, $name)) ->then ->resource($result) ->isStreamFilter(); } public function case_remove() { $this ->given( $stream = fopen('hoa://Test/Vfs/Foo?type=file', 'r'), $name = 'string.toupper', $filter = SUT::append($stream, $name) ) ->when($result = SUT::remove($filter)) ->then ->boolean($result) ->isTrue(); } public function case_remove_by_name() { $this ->given( $stream = fopen('hoa://Test/Vfs/Foo?type=file', 'r'), $name = 'string.toupper', $filter = SUT::append($stream, $name) ) ->when($result = SUT::remove($name)) ->then ->boolean($result) ->isTrue(); } public function case_remove_unknown() { $this ->exception(function () { SUT::remove('foo'); }) ->isInstanceOf(LUT\Filter\Exception::class) ->hasMessage( 'Cannot remove the stream filter foo ' . 'because no resource was found with this name.' ); } public function case_is_registered() { $this ->when($result = SUT::isRegistered('string.toupper')) ->then ->boolean($result) ->isTrue(); } public function case_is_not_registered() { $this ->when($result = SUT::isRegistered('foo')) ->then ->boolean($result) ->isFalse(); } public function case_get_registered() { $this ->when($result = SUT::getRegistered()) ->then ->array($result) ->containsValues([ 'string.rot13', 'string.toupper', 'string.tolower', 'string.strip_tags', 'consumed', 'dechunk' ]); } } integer(SUT::PASS_ON) ->isEqualTo(PSFS_PASS_ON) ->integer(SUT::FEED_ME) ->isEqualTo(PSFS_FEED_ME) ->integer(SUT::FATAL_ERROR) ->isEqualTo(PSFS_ERR_FATAL) ->integer(SUT::FLAG_NORMAL) ->isEqualTo(PSFS_FLAG_NORMAL) ->integer(SUT::FLAG_FLUSH_INC) ->isEqualTo(PSFS_FLAG_FLUSH_INC) ->integer(SUT::FLAG_FLUSH_CLOSE) ->isEqualTo(PSFS_FLAG_FLUSH_CLOSE); } public function case_is_a_php_filter() { $this ->when($result = new SUT()) ->then ->object($result) ->isInstanceOf(\php_user_filter::class); } public function case_interfaces() { $this ->when($result = new SUT()) ->then ->object($result) ->isInstanceOf(LUT\IStream\Stream::class); } public function case_set_name() { $this ->given($filter = new SUT()) ->when($result = $filter->setName('foo')) ->then ->string($result) ->isEqualTo(''); } public function case_get_name() { $this ->given( $filter = new SUT(), $name = 'foo', $filter->setName($name) ) ->when($result = $filter->getName()) ->then ->string($result) ->isEqualTo($name); } public function case_set_parameters() { $this ->given($filter = new SUT()) ->when($result = $filter->setParameters(['foo', 'bar', 'baz'])) ->then ->string($result) ->isEqualTo(''); } public function case_get_parameters() { $this ->given( $filter = new SUT(), $parameters = ['foo', 'bar', 'baz'], $filter->setParameters($parameters) ) ->when($result = $filter->getParameters()) ->then ->array($result) ->isEqualTo($parameters); } public function case_get_stream() { $this ->given($filter = new SUT()) ->when($result = $filter->getStream()) ->then ->variable($result) ->isNull(); } } when($result = new SUT(__FILE__)) ->then ->object($result) ->isInstanceOf(LUT\IStream\Stream::class) ->isInstanceOf(Event\Listenable::class); } public function case_constants() { $this ->integer(SUT::NAME) ->isEqualTo(0) ->integer(SUT::HANDLER) ->isEqualTo(1) ->integer(SUT::RESOURCE) ->isEqualTo(2) ->integer(SUT::CONTEXT) ->isEqualTo(3); } public function case_construct() { $this ->given($name = __FILE__) ->when($result = new SUT($name)) ->then ->string($result->getStreamName()) ->isEqualTo($name) ->boolean($this->invoke($result)->hasBeenDeferred()) ->isFalse() ->let($listener = $this->invoke($result)->getListener()) ->object($listener) ->isInstanceOf(Event\Listener::class) ->boolean($listener->listenerExists('authrequire')) ->isTrue() ->boolean($listener->listenerExists('authresult')) ->isTrue() ->boolean($listener->listenerExists('complete')) ->isTrue() ->boolean($listener->listenerExists('connect')) ->isTrue() ->boolean($listener->listenerExists('failure')) ->isTrue() ->boolean($listener->listenerExists('mimetype')) ->isTrue() ->boolean($listener->listenerExists('progress')) ->isTrue() ->boolean($listener->listenerExists('redirect')) ->isTrue() ->boolean($listener->listenerExists('resolve')) ->isTrue() ->boolean($listener->listenerExists('size')) ->isTrue() ->boolean(Event::eventExists('hoa://Event/Stream/' . $name)) ->isTrue() ->boolean(Event::eventExists('hoa://Event/Stream/' . $name . ':close-before')) ->isTrue(); } public function case_construct_with_a_context() { $this ->given( $name = __FILE__, $contextName = 'foo', LUT\Context::getInstance($contextName) ) ->when($result = new SUT($name, $contextName)) ->then ->string($result->getStreamName()) ->isEqualTo($name) ->boolean($this->invoke($result)->hasBeenDeferred()) ->isFalse() ->object($this->invoke($result)->getListener()) ->isInstanceOf(Event\Listener::class); } public function case_construct_with_deferred_opening() { $this ->given($name = __FILE__) ->when($result = new SUT($name, null, true)) ->then ->boolean($this->invoke($result)->hasBeenDeferred()) ->isTrue() ->boolean($result->isOpened()) ->isFalse() ->variable($result->getStreamName()) ->isNull(); } public function case_open() { $this ->given( $name = __FILE__, $stream = new SUT($name, null, true) ) ->when($result = $stream->open()) ->then ->object($result) ->isIdenticalTo($stream) ->boolean($this->invoke($result)->hasBeenDeferred()) ->isTrue() ->boolean($result->isOpened()) ->isTrue() ->string($result->getStreamName()) ->isEqualTo($name) ->integer($result->getStreamBufferSize()) ->isEqualTo(SUT::DEFAULT_BUFFER_SIZE); } public function case_close() { $this ->given( $name = __FILE__, $stream = new SUT($name), $resource = $stream->getStream(), $context = $stream->getStreamContext() ) ->when($result = $stream->close()) ->then ->variable($result) ->isNull() ->boolean($stream->isOpened()) ->isFalse() ->variable(SUT::getStreamHandler($stream)) ->isNull() ->variable($stream->getStreamName()) ->isEqualTo($name) ->variable($stream->getStream()) ->isEqualTo($resource) ->variable($stream->getStreamContext()) ->isEqualTo($context) ->boolean(Event::eventExists('hoa://Event/Stream/' . $name)) ->isFalse() ->boolean(Event::eventExists('hoa://Event/Stream/' . $name . ':close-before')) ->isFalse(); } public function case_close_more_than_once() { $this ->given( $name = __FILE__, $stream = new SUT($name), $close1 = $stream->close() ) ->when($result = $stream->close()) ->then ->variable($result) ->isIdenticalTo($close1); } public function case_open_close_open() { $this ->given( $name = __FILE__, $stream = new SUT($name, null, true), $stream->open(), $resource = $stream->getStream(), $context = $stream->getStreamContext(), $handler = SUT::getStreamHandler($stream), $this->function->stream_set_write_buffer = 0, $stream->setStreamBuffer(42), $stream->close() ) ->when($result = $stream->open()) ->then ->string($result->getStreamName()) ->isEqualTo($name) ->resource($result->getStream()) ->isNotEqualTo($resource) ->object($handler) ->isIdenticalTo($result) ->object($this->invoke($stream)->getListener()) ->isInstanceOf(Event\Listener::class) ->boolean(Event::eventExists('hoa://Event/Stream/' . $name)) ->isTrue() ->boolean(Event::eventExists('hoa://Event/Stream/' . $name . ':close-before')) ->isTrue() ->integer($stream->getStreamBufferSize()) ->isEqualTo(SUT::DEFAULT_BUFFER_SIZE); } public function case_close_event_close_before() { $self = $this; $this ->given( $name = 'hoa://Test/Vfs/Foo?type=file', $stream = new SUT($name), Event::getEvent('hoa://Event/Stream/' . $name . ':close-before')->attach( function (Event\Bucket $bucket) use ($self, &$called) { $called = true; $self ->variable($bucket->getData()) ->isNull() ->boolean($bucket->getSource()->isOpened()) ->isTrue(); } ) ) ->when($result = $stream->close()) ->then ->boolean($called) ->isTrue(); } public function case_get_stream_name() { $this ->given( $name = __FILE__, $stream = new SUT($name) ) ->when($result = $stream->getStreamName()) ->then ->string($result) ->isEqualTo($name); } public function case_get_stream() { $this ->given( $name = __FILE__, $stream = new SUT($name) ) ->when($result = $stream->getStream()) ->then ->resource($result) ->isStream($name); } public function case_get_stream_context() { $this ->given( $name = __FILE__, $contextName = 'foo', $context = LUT\Context::getInstance($contextName), $stream = new SUT($name, $contextName) ) ->when($result = $stream->getStreamContext()) ->then ->object($result) ->isIdenticalTo($context); } public function case_get_stream_context_with_no_context_given() { $this ->given( $name = __FILE__, $stream = new SUT($name) ) ->when($result = $stream->getStreamContext()) ->then ->variable($result) ->isNull(); } public function case_get_stream_handler() { $this ->given( $name = __FILE__, $stream = new SUT($name) ) ->when($result = SUT::getStreamHandler($name)) ->then ->object($result) ->isIdenticalTo($result); } public function case_get_stream_handler_of_unknown_stream() { $this ->when($result = SUT::getStreamHandler('foo')) ->then ->variable($result) ->isNull(); } public function case__set_stream() { $this ->given( $stream = new SUT(__FILE__), $oldStream = $stream->getStream(), $newStream = fopen('php://memory', 'rb') ) ->when($result = $stream->_setStream($newStream)) ->then ->resource($result) ->isIdenticalTo($oldStream) ->isStream() ->resource($stream->getStream()) ->isStream() ->isIdenticalTo($newStream); } public function case__set_stream_invalid_resource() { $this ->given($stream = new SUT(__FILE__)) ->exception(function () use ($stream) { $stream->_setStream(true); }) ->isInstanceOf(LUT\Exception::class); } public function case__set_stream_unknown_resource() { $this ->given( $stream = new SUT(__FILE__), $oldStream = $stream->getStream(), $newStream = fopen('php://memory', 'rb'), $this->function->is_resource = false, $this->function->gettype = 'resource', $this->function->get_resource_type = 'Unknown' ) ->when($result = $stream->_setStream($newStream)) ->then ->resource($result) ->isIdenticalTo($oldStream) ->isStream() ->resource($stream->getStream()) ->isStream() ->isIdenticalTo($newStream); } public function case_is_opened() { $this ->given($stream = new SUT(__FILE__)) ->when($result = $stream->isOpened()) ->then ->boolean($result) ->isTrue(); } public function case_is_not_opened() { $this ->given($stream = new SUT(__FILE__, null, true)) ->when($result = $stream->isOpened()) ->then ->boolean($result) ->isFalse() ->when( $stream->open(), $result = $stream->isOpened() ) ->then ->boolean($result) ->isTrue(); } public function case_set_stream_timeout() { $self = $this; $this ->given( $stream = new SUT(__FILE__), $this->function->stream_set_timeout = function ($_stream, $_seconds, $_microseconds) use ($self, $stream, &$called) { $called = true; $self ->resource($_stream) ->isIdenticalTo($stream->getStream()) ->integer($_seconds) ->isEqualTo(7) ->integer($_microseconds) ->isEqualTo(42); return true; } ) ->when($result = $stream->setStreamTimeout(7, 42)) ->then ->boolean($result) ->isTrue() ->boolean($called) ->isTrue(); } public function case_has_been_deferred() { $this ->given($stream = new SUT(__FILE__, null, true)) ->when($result = $this->invoke($stream)->hasBeenDeferred()) ->then ->boolean($result) ->isTrue(); } public function case_has_not_been_deferred() { $this ->given($stream = new SUT(__FILE__)) ->when($result = $this->invoke($stream)->hasBeenDeferred()) ->then ->boolean($result) ->isFalse(); } public function case_has_timed_out() { $this ->given( $stream = new SUT(__FILE__), $this->function->stream_get_meta_data = [ 'timed_out' => true ] ) ->when($result = $stream->hasTimedOut()) ->then ->boolean($result) ->isTrue(); } public function case_has_not_timed_out() { $this ->given( $stream = new SUT(__FILE__), $this->function->stream_get_meta_data = [ 'timed_out' => false ] ) ->when($result = $stream->hasTimedOut()) ->then ->boolean($result) ->isFalse(); } public function case_set_stream_blocking() { $self = $this; $this ->given( $stream = new SUT(__FILE__), $this->function->stream_set_blocking = function ($_stream, $_mode) use ($self, $stream, &$called) { $called = true; $self ->resource($_stream) ->isIdenticalTo($stream->getStream()) ->integer($_mode) ->isEqualTo(1); return true; } ) ->when($result = $stream->setStreamBlocking(true)) ->then ->boolean($result) ->isTrue() ->boolean($called) ->isTrue(); } public function case_get_default_stream_buffer_size() { $self = $this; $this ->given($stream = new SUT(__FILE__)) ->when($result = $stream->getStreamBufferSize()) ->then ->integer($result) ->isEqualTo(8192); } public function case_set_stream_buffer() { $self = $this; $this ->given( $stream = new SUT(__FILE__), $this->function->stream_set_write_buffer = function ($_stream, $_buffer) use ($self, $stream, &$called) { $called = true; $self ->resource($_stream) ->isIdenticalTo($stream->getStream()) ->integer($_buffer) ->isEqualTo(42); return 0; } ) ->when($result = $stream->setStreamBuffer(42)) ->then ->boolean($result) ->isTrue() ->boolean($called) ->isTrue() ->integer($stream->getStreamBufferSize()) ->isEqualTo(42); } public function case_set_stream_buffer_fail() { $self = $this; $this ->given( $stream = new SUT(__FILE__), $oldStreamBufferSize = $stream->getStreamBufferSize(), $this->function->stream_set_write_buffer = function ($_stream, $_buffer) use ($self, $stream, &$called) { $called = true; $self ->resource($_stream) ->isIdenticalTo($stream->getStream()) ->integer($_buffer) ->isEqualTo(42); return 1; } ) ->when($result = $stream->setStreamBuffer(42)) ->then ->boolean($result) ->isFalse() ->boolean($called) ->isTrue() ->integer($stream->getStreamBufferSize()) ->isEqualTo($oldStreamBufferSize); } public function case_disable_stream_buffer() { $self = $this; $this ->given( $stream = new SUT(__FILE__), $this->function->stream_set_write_buffer = function ($_stream, $_buffer) use ($self, $stream, &$called) { $called = true; $self ->resource($_stream) ->isIdenticalTo($stream->getStream()) ->integer($_buffer) ->isEqualTo(0); return 0; } ) ->when($result = $stream->disableStreamBuffer()) ->then ->boolean($result) ->isTrue() ->boolean($called) ->isTrue() ->integer($stream->getStreamBufferSize()) ->isEqualTo(0); } public function case_get_stream_wrapper_name_with_no_wrapper() { $this ->given($stream = new SUT(__FILE__)) ->when($result = $stream->getStreamWrapperName()) ->then ->string($result) ->isEqualTo('file'); } public function case_get_stream_wrapper_name() { $this ->given($stream = new SUT('hoa://Test/Vfs/Foo?type=file')) ->when($result = $stream->getStreamWrapperName()) ->then ->string($result) ->isEqualTo('hoa'); } public function case_get_stream_meta_data() { $this ->given($stream = new SUT(__FILE__)) ->when($result = $stream->getStreamMetaData()) ->then ->array($result) ->isEqualTo([ 'timed_out' => false, 'blocked' => true, 'eof' => false, 'wrapper_type' => 'plainfile', 'stream_type' => 'STDIO', 'mode' => 'rb', 'unread_bytes' => 0, 'seekable' => true, 'uri' => __FILE__ ]); } public function case_is_borrowing() { $this ->given( $streamA1 = new SUT(__FILE__), $streamA2 = new SUT(__FILE__) ) ->when($result = $streamA2->isBorrowing()) ->then ->boolean($result) ->isTrue() ->boolean($streamA1->isBorrowing()) ->isFalse(); } public function case_is_not_borrowing() { $this ->given($stream = new SUT(__FILE__)) ->when($result = $stream->isBorrowing()) ->then ->boolean($result) ->isFalse(); } public function case_shutdown_destructor() { $this ->given( $stream = new \Mock\Hoa\Stream\Test\Unit\SUTWithPublicClose(__FILE__), $this->calling($stream)->_close = function () use (&$called) { $called = true; } ) ->when($result = SUT::_Hoa_Stream()) ->then ->boolean($called) ->isTrue(); } public function case_destruct_an_opened_stream() { $this ->given( $stream = new \Mock\Hoa\Stream\Test\Unit\SUTWithPublicClose(__FILE__), $this->calling($stream)->_close = function () use (&$called) { $called = true; } ) ->when($result = $stream->__destruct()) ->then ->boolean($called) ->isTrue(); } public function case_destruct_a_deferred_stream() { $this ->given( $stream = new \Mock\Hoa\Stream\Test\Unit\SUTWithPublicClose(__FILE__, null, true), $this->calling($stream)->_close = function () use (&$called) { $called = true; } ) ->when($result = $stream->__destruct()) ->then ->variable($called) ->isNull(); } public function case_protocol_reach_id() { $this ->given( $name = 'hoa://Test/Vfs/Foo?type=file', $stream = new SUT($name) ) ->when($result = resolve('hoa://Library/Stream#' . $name)) ->then ->object($result) ->isIdenticalTo($stream); } public function case_protocol_reach_unknown_id() { $this ->given($name = 'hoa://Test/Vfs/Foo?type=file') ->when($result = resolve('hoa://Library/Stream#' . $name)) ->then ->variable($result) ->isNull(); } } class SUT extends LUT\Stream { protected function &_open($streamName, LUT\Context $context = null) { if (null === $context) { $out = fopen($streamName, 'rb'); } else { $out = fopen($streamName, 'rb', false, $context->getContext()); } return $out; } protected function _close() { return fclose($this->getStream()); } } class SUTWithPublicClose extends SUT { public function _close() { return parent::_close(); } } given( $stream = new \StdClass(), $composite = new SUT() ) ->when($result = $this->invoke($composite)->setStream($stream)) ->then ->variable($result) ->isNull(); } public function case_get_stream() { $this ->given( $stream = new \StdClass(), $composite = new SUT(), $this->invoke($composite)->setStream($stream) ) ->when($result = $composite->getStream()) ->then ->object($result) ->isIdenticalTo($stream); } public function case_set_inner_stream() { $this ->given( $innerStream = new \Mock\Hoa\Stream(__FILE__), $composite = new SUT() ) ->when($result = $this->invoke($composite)->setInnerStream($innerStream)) ->then ->variable($result) ->isNull(); } public function case_get_inner_stream() { $this ->given( $innerStream = new \Mock\Hoa\Stream(__FILE__), $composite = new SUT(), $this->invoke($composite)->setInnerStream($innerStream) ) ->when($result = $composite->getInnerStream()) ->then ->object($result) ->isIdenticalTo($innerStream); } } setType($is); if (self::IS_A_BRIGADE === $this->getType()) { $this->setBrigade($brigade); } else { $this->setBucket(stream_bucket_new($brigade, $buffer)); $bucket = $this->getBucket(); $this->setBrigade($bucket); } return; } public function eob() { $this->_bucket = null; return false == $this->getBucket(); } public function append(Bucket $bucket) { stream_bucket_append($this->getBrigade(), $bucket->getBucket()); return; } public function prepend(Bucket $bucket) { stream_bucket_prepend($this->getBrigade(), $bucket->getBucket()); return; } protected function setType($type) { $old = $this->_type; $this->_type = $type; return $old; } public function getType() { return $this->_type; } public function setData($data) { $old = $this->getBucket()->data; $this->getBucket()->data = $data; $this->getBucket()->datalen = strlen($this->getBucket()->data); return $old; } public function getData() { if (null === $this->getBucket()) { return null; } return $this->getBucket()->data; } public function getLength() { if (null === $this->getBucket()) { return 0; } return $this->getBucket()->datalen; } protected function setBrigade(&$brigade) { $old = $this->_brigade; $this->_brigade = $brigade; return $old; } public function getBrigade() { return $this->_brigade; } protected function setBucket($bucket) { $old = $this->_bucket; $this->_bucket = $bucket; return $old; } protected function getBucket() { if (null === $this->_bucket && self::IS_A_BRIGADE === $this->getType()) { $this->_bucket = stream_bucket_make_writeable($this->getBrigade()); } return $this->_bucket; } } _id = $id; $this->_context = stream_context_create(); return; } public static function getInstance($id) { if (empty($id)) { throw new Exception('Context ID must not be null.', 0); } if (false === static::contextExists($id)) { static::$_instances[$id] = new static($id); } return static::$_instances[$id]; } public function getId() { return $this->_id; } public static function contextExists($id) { return array_key_exists($id, static::$_instances); } public function setOptions(array $options) { return stream_context_set_option($this->getContext(), $options); } public function setParameters(array $parameters) { return stream_context_set_params($this->getContext(), $parameters); } public function getOptions() { return stream_context_get_options($this->getContext()); } public function getParameters() { return stream_context_get_params($this->getContext()); } public function getContext() { return $this->_context; } } eob()) { $this->_buffer .= $iBucket->getData(); $consumed += $iBucket->getLength(); } if (null !== $consumed) { $return = self::PASS_ON; } if (true === $closing) { $stream = $this->getStream(); $this->compute(); $bucket = new Stream\Bucket( $stream, Stream\Bucket::IS_A_STREAM, $this->_buffer ); $oBucket = new Stream\Bucket($out); $oBucket->append($bucket); $return = self::PASS_ON; $this->_buffer = null; } return $return; } abstract protected function compute(); } getStream(); } if (null === $parameters) { return self::$_resources[$name] = stream_filter_append( $stream, $name, $mode ); } return self::$_resources[$name] = stream_filter_append( $stream, $name, $mode, $parameters ); } public static function prepend( $stream, $name, $mode = self::READ, $parameters = null ) { if ($stream instanceof Stream) { $stream = $stream->getStream(); } if (null === $parameters) { return self::$_resources[$name] = stream_filter_prepend( $stream, $name, $mode ); } return self::$_resources[$name] = stream_filter_prepend( $stream, $name, $mode, $parameters ); } public static function remove($streamFilter) { if (!is_resource($streamFilter)) { if (isset(self::$_resources[$streamFilter])) { $streamFilter = self::$_resources[$streamFilter]; } else { throw new Exception( 'Cannot remove the stream filter %s because no resource was ' . 'found with this name.', 3, $streamFilter ); } } return stream_filter_remove($streamFilter); } public static function isRegistered($name) { return in_array($name, self::getRegistered()); } public static function getRegistered() { return stream_get_filters(); } } Consistency::flexEntity('Hoa\Stream\Filter\Filter'); eob()) { $consumed += $iBucket->getLength(); $oBucket->append($iBucket); } unset($iBucket); unset($oBucket); return self::PASS_ON; } public function onCreate() { return true; } public function onClose() { return; } public function setName($name) { $old = $this->filtername; $this->filtername = $name; return $old; } public function setParameters($parameters) { $old = $this->params; $this->params = $parameters; return $old; } public function getName() { return $this->filtername; } public function getParameters() { return $this->params; } public function getStream() { return isset($this->stream) ? $this->stream : null; } } _streamName = $streamName; $this->_context = $context; $this->_hasBeenDeferred = $wait; $this->setListener( new Event\Listener( $this, [ 'authrequire', 'authresult', 'complete', 'connect', 'failure', 'mimetype', 'progress', 'redirect', 'resolve', 'size' ] ) ); if (true === $wait) { return; } $this->open(); return; } final private static function &_getStream( $streamName, Stream $handler, $context = null ) { $name = md5($streamName); if (null !== $context) { if (false === Context::contextExists($context)) { throw new Exception( 'Context %s was not previously declared, cannot retrieve ' . 'this context.', 0, $context ); } $context = Context::getInstance($context); } if (!isset(self::$_register[$name])) { self::$_register[$name] = [ self::NAME => $streamName, self::HANDLER => $handler, self::RESOURCE => $handler->_open($streamName, $context), self::CONTEXT => $context ]; Event::register( 'hoa://Event/Stream/' . $streamName, $handler ); Event::register( 'hoa://Event/Stream/' . $streamName . ':close-before', $handler ); } else { $handler->_borrowing = true; } if (null === self::$_register[$name][self::RESOURCE]) { self::$_register[$name][self::RESOURCE] = $handler->_open($streamName, $context); } return self::$_register[$name]; } abstract protected function &_open($streamName, Context $context = null); abstract protected function _close(); final public function open() { $context = $this->_context; if (true === $this->hasBeenDeferred()) { if (null === $context) { $handle = Context::getInstance(uniqid()); $handle->setParameters([ 'notification' => [$this, '_notify'] ]); $context = $handle->getId(); } elseif (true === Context::contextExists($context)) { $handle = Context::getInstance($context); $parameters = $handle->getParameters(); if (!isset($parameters['notification'])) { $handle->setParameters([ 'notification' => [$this, '_notify'] ]); } } } $this->_bufferSize = self::DEFAULT_BUFFER_SIZE; $this->_bucket = self::_getStream( $this->_streamName, $this, $context ); return $this; } final public function close() { $streamName = $this->getStreamName(); $name = md5($streamName); if (!isset(self::$_register[$name])) { return; } Event::notify( 'hoa://Event/Stream/' . $streamName . ':close-before', $this, new Event\Bucket() ); if (false === $this->_close()) { return; } unset(self::$_register[$name]); $this->_bucket[self::HANDLER] = null; Event::unregister( 'hoa://Event/Stream/' . $streamName ); Event::unregister( 'hoa://Event/Stream/' . $streamName . ':close-before' ); return; } public function getStreamName() { if (empty($this->_bucket)) { return null; } return $this->_bucket[self::NAME]; } public function getStream() { if (empty($this->_bucket)) { return null; } return $this->_bucket[self::RESOURCE]; } public function getStreamContext() { if (empty($this->_bucket)) { return null; } return $this->_bucket[self::CONTEXT]; } public static function getStreamHandler($streamName) { $name = md5($streamName); if (!isset(self::$_register[$name])) { return null; } return self::$_register[$name][self::HANDLER]; } public function _setStream($stream) { if (false === is_resource($stream) && ('resource' !== gettype($stream) || 'Unknown' !== get_resource_type($stream))) { throw new Exception( 'Try to change the stream resource with an invalid one; ' . 'given %s.', 1, gettype($stream) ); } $old = $this->_bucket[self::RESOURCE]; $this->_bucket[self::RESOURCE] = $stream; return $old; } public function isOpened() { return is_resource($this->getStream()); } public function setStreamTimeout($seconds, $microseconds = 0) { return stream_set_timeout($this->getStream(), $seconds, $microseconds); } protected function hasBeenDeferred() { return $this->_hasBeenDeferred; } public function hasTimedOut() { $metaData = $this->getStreamMetaData(); return true === $metaData['timed_out']; } public function setStreamBlocking($mode) { return stream_set_blocking($this->getStream(), (int) $mode); } public function setStreamBuffer($buffer) { $out = 0 === stream_set_write_buffer($this->getStream(), $buffer); if (true === $out) { $this->_bufferSize = $buffer; } return $out; } public function disableStreamBuffer() { return $this->setStreamBuffer(0); } public function getStreamBufferSize() { return $this->_bufferSize; } public function getStreamWrapperName() { if (false === $pos = strpos($this->getStreamName(), '://')) { return 'file'; } return substr($this->getStreamName(), 0, $pos); } public function getStreamMetaData() { return stream_get_meta_data($this->getStream()); } public function isBorrowing() { return $this->_borrowing; } public function _notify( $ncode, $severity, $message, $code, $transferred, $max ) { static $_map = [ STREAM_NOTIFY_AUTH_REQUIRED => 'authrequire', STREAM_NOTIFY_AUTH_RESULT => 'authresult', STREAM_NOTIFY_COMPLETED => 'complete', STREAM_NOTIFY_CONNECT => 'connect', STREAM_NOTIFY_FAILURE => 'failure', STREAM_NOTIFY_MIME_TYPE_IS => 'mimetype', STREAM_NOTIFY_PROGRESS => 'progress', STREAM_NOTIFY_REDIRECTED => 'redirect', STREAM_NOTIFY_RESOLVE => 'resolve', STREAM_NOTIFY_FILE_SIZE_IS => 'size' ]; $this->getListener()->fire($_map[$ncode], new Event\Bucket([ 'code' => $code, 'severity' => $severity, 'message' => $message, 'transferred' => $transferred, 'max' => $max ])); return; } final public static function _Hoa_Stream() { foreach (self::$_register as $entry) { $entry[self::HANDLER]->close(); } return; } public function __toString() { return $this->getStreamName(); } public function __destruct() { if (false === $this->isOpened()) { return; } $this->close(); return; } } class _Protocol extends Protocol\Node { protected $_name = 'Stream'; public function reachId($id) { return Stream::getStreamHandler($id); } } Consistency::flexEntity('Hoa\Stream\Stream'); Consistency::registerShutdownFunction(xcallable('Hoa\Stream\Stream::_Hoa_Stream')); $protocol = Protocol::getInstance(); $protocol['Library'][] = new _Protocol(); _stream; $this->_stream = $stream; return $old; } public function getStream() { return $this->_stream; } protected function setInnerStream(Stream $innerStream) { $old = $this->_innerStream; $this->_innerStream = $innerStream; return $old; } public function getInnerStream() { return $this->_innerStream; } } _tmpArguments = $arguments; parent::__construct($message, $code, $previous); $this->_rawMessage = $message; $this->message = @vsprintf($message, $this->getArguments()); return; } public function getBacktrace() { if (null === $this->_trace) { $this->_trace = $this->getTrace(); } return $this->_trace; } public function getPreviousThrow() { if (null === $this->_previous) { $this->_previous = $this->getPrevious(); } return $this->_previous; } public function getArguments() { if (null === $this->_arguments) { $arguments = $this->_tmpArguments; if (!is_array($arguments)) { $arguments = [$arguments]; } foreach ($arguments as &$value) { if (null === $value) { $value = '(null)'; } } $this->_arguments = $arguments; unset($this->_tmpArguments); } return $this->_arguments; } public function getRawMessage() { return $this->_rawMessage; } public function getFormattedMessage() { return $this->getMessage(); } public function getFrom() { $trace = $this->getBacktrace(); $from = '{main}'; if (!empty($trace)) { $t = $trace[0]; $from = ''; if (isset($t['class'])) { $from .= $t['class'] . '::'; } if (isset($t['function'])) { $from .= $t['function'] . '()'; } } return $from; } public function raise($previous = false) { $message = $this->getFormattedMessage(); $trace = $this->getBacktrace(); $file = '/dev/null'; $line = -1; $pre = $this->getFrom(); if (!empty($trace)) { $file = isset($trace['file']) ? $trace['file'] : null; $line = isset($trace['line']) ? $trace['line'] : null; } $pre .= ': '; try { $out = $pre . '(' . $this->getCode() . ') ' . $message . "\n" . 'in ' . $this->getFile() . ' at line ' . $this->getLine() . '.'; } catch (\Exception $e) { $out = $pre . '(' . $this->getCode() . ') ' . $message . "\n" . 'in ' . $file . ' around line ' . $line . '.'; } if (true === $previous && null !== $previous = $this->getPreviousThrow()) { $out .= "\n\n" . ' ⬇' . "\n\n" . 'Nested exception (' . get_class($previous) . '):' . "\n" . ($previous instanceof self ? $previous->raise(true) : $previous->getMessage()); } return $out; } public static function uncaught($exception) { if (!($exception instanceof self)) { throw $exception; } while (0 < ob_get_level()) { ob_end_flush(); } echo 'Uncaught exception (' . get_class($exception) . '):' . "\n" . $exception->raise(true); return; } public function __toString() { return $this->raise(); } public static function enableUncaughtHandler($enable = true) { if (false === $enable) { return restore_exception_handler(); } return set_exception_handler(function ($exception) { return self::uncaught($exception); }); } } when($result = new SUT('foo')) ->then ->object($result) ->isInstanceOf('Exception'); } public function case_get_backtrace() { $this ->given($exception = new SUT('foo')) ->when($result = $exception->getBacktrace()) ->then ->array($result) ->hasKey(0) ->array($result[0]) ->hasKey('file') ->hasKey('line') ->hasKey('function') ->hasKey('class') ->hasKey('type') ->hasKey('args'); } public function case_get_previous_throw() { $this ->given( $previous = new SUT('previous'), $exception = new SUT('foo', 0, [], $previous) ) ->when($result = $exception->getPreviousThrow()) ->then ->object($result) ->isIdenticalTo($previous); } public function case_get_arguments() { $this ->given($exception = new SUT('foo', 0, ['arg', 42, null])) ->when($result = $exception->getArguments()) ->then ->array($result) ->isEqualTo(['arg', 42, '(null)']); } public function case_get_arguments_from_a_string() { $this ->given($exception = new SUT('foo', 0, 'arg')) ->when($result = $exception->getArguments()) ->then ->array($result) ->isEqualTo(['arg']); } public function case_get_raw_message() { $this ->given( $message = 'foo %s', $exception = new SUT($message) ) ->when($result = $exception->getRawMessage()) ->then ->string($result) ->isEqualTo($message); } public function case_get_formatted_message() { $this ->given( $message = 'foo %s', $exception = new SUT($message, 0, 'bar') ) ->when($result = $exception->getFormattedMessage()) ->then ->string($result) ->isEqualTo($exception->getMessage()) ->isEqualTo('foo bar'); } public function case_get_from_object() { $this ->given($exception = new SUT('foo')) ->when($result = $exception->getFrom()) ->then ->string($result) ->isEqualTo(__METHOD__ . '()'); } public function case_raise() { $this ->given($exception = new SUT('foo'), $line = __LINE__) ->when($result = $exception->raise()) ->then ->string($result) ->isEqualTo( __METHOD__ . '(): (0) foo' . "\n" . 'in ' . __FILE__ . ' at line ' . $line . '.' ); } public function case_raise_with_previous() { $this ->given( $previous = new SUT('previous'), $previousLine = __LINE__, $exception = new SUT('foo', 0, [], $previous), $line = __LINE__ ) ->when($result = $exception->raise(true)) ->then ->string($result) ->isEqualTo( __METHOD__ . '(): (0) foo' . "\n" . 'in ' . __FILE__ . ' at line ' . $line . '.' . "\n\n" . ' ⬇' . "\n\n" . 'Nested exception (' . get_class($previous) . '):' . "\n" . __METHOD__ . '(): (0) previous' . "\n" . 'in ' . __FILE__ . ' at line ' . $previousLine . '.' ); } public function case_uncaught() { $this ->given( $this->function->ob_get_level = 0, $exception = new SUT('foo'), $line = __LINE__ ) ->when($result = SUT::uncaught($exception)) ->then ->variable($result) ->isNull() ->output ->isEqualTo( 'Uncaught exception (' . get_class($exception) . '):' . "\n" . __METHOD__ . '(): (0) foo' . "\n" . 'in ' . __FILE__ . ' at line ' . $line . '.' ); } public function case_uncaught_not_Hoa() { $this ->exception(function () { SUT::uncaught(new \Exception('foo')); }) ->isInstanceOf('Exception') ->output ->isEmpty(); } public function case_to_string() { $this ->given($exception = new SUT('foo')) ->when($result = $exception->__toString()) ->then ->string($result) ->isEqualTo($exception->raise()); } public function case_disable_uncaught_handler() { $this ->given( $this->function->restore_exception_handler = function () use (&$called) { $called = true; return null; } ) ->when($result = SUT::enableUncaughtHandler(false)) ->then ->variable($result) ->isNull() ->boolean($called) ->isTrue(); } public function case_enable_uncaught_handler() { $self = $this; $this ->given( $this->function->set_exception_handler = function ($handler) use ($self, &$called) { $called = true; $self ->object($handler) ->isInstanceOf('Closure') ->let($reflection = new \ReflectionObject($handler)) ->array($invokeParameters = $reflection->getMethod('__invoke')->getParameters()) ->hasSize(1) ->string($invokeParameters[0]->getName()) ->isEqualTo('exception'); return null; } ) ->when($result = SUT::enableUncaughtHandler()) ->then ->variable($result) ->isNull() ->boolean($called) ->isTrue(); } } when($result = new SUT('foo')) ->then ->object($result) ->isInstanceOf('Hoa\Exception\Idle'); } public function case_event_is_registered() { $this ->given(new SUT('foo')) ->when($result = Event::eventExists('hoa://Event/Exception')) ->then ->boolean($result) ->isTrue(); } public function case_event_is_sent() { $self = $this; $this ->given( Event::getEvent('hoa://Event/Exception')->attach( function (Event\Bucket $bucket) use ($self, &$called) { $called = true; $self ->object($bucket->getSource()) ->isInstanceOf('Hoa\Exception\Exception') ->string($bucket->getSource()->getMessage()) ->isEqualTo('foo') ->object($bucket->getData()) ->isIdenticalTo($bucket->getSource()); } ) ) ->when(new SUT('foo')) ->then ->boolean($called) ->isTrue(); } } when($result = new SUT('foo')) ->then ->object($result) ->isInstanceOf('Hoa\Exception\Exception') ->isInstanceOf('ArrayAccess') ->isInstanceOf('IteratorAggregate') ->isInstanceOf('Countable'); } public function case_constructor() { $this ->given( $message = 'foo %s %d %s', $code = 7, $arguments = ['arg', 42, null], $previous = new SUT('previous') ) ->when($result = new SUT($message, $code, $arguments, $previous), $line = __LINE__) ->then ->string($result->getMessage()) ->isEqualTo('foo arg 42 (null)') ->integer($result->getCode()) ->isEqualTo(7) ->array($result->getArguments()) ->isEqualTo(['arg', 42, '(null)']) ->object($result->getPreviousThrow()) ->isIdenticalTo($previous) ->boolean($result->hasUncommittedExceptions()) ->isFalse(); } public function case_raise_zero_exception() { $this ->given($group = new SUT('foo'), $line = __LINE__) ->when($result = $group->raise()) ->then ->string($result) ->isEqualTo( __METHOD__ . '(): (0) foo' . "\n" . 'in ' . __FILE__ . ' at line ' . $line . '.' ); } public function case_raise_one_exception() { $this ->given( $exception1 = new SUT('bar'), $barLine = __LINE__, $group = new SUT('foo'), $fooLine = __LINE__, $group[] = $exception1 ) ->when($result = $group->raise()) ->then ->string($result) ->isEqualTo( __METHOD__ . '(): (0) foo' . "\n" . 'in ' . __FILE__ . ' at line ' . $fooLine . '.' . "\n\n" . 'Contains the following exceptions:' . "\n\n" . ' • ' . __METHOD__ . '(): (0) bar' . "\n" . ' in ' . __FILE__ . ' at line ' . $barLine . '.' ); } public function case_raise_more_exceptions() { $this ->given( $exception1 = new SUT('bar'), $barLine = __LINE__, $exception2 = new SUT('baz'), $bazLine = __LINE__, $group = new SUT('foo'), $fooLine = __LINE__, $group[] = $exception1, $group[] = $exception2 ) ->when($result = $group->raise()) ->then ->string($result) ->isEqualTo( __METHOD__ . '(): (0) foo' . "\n" . 'in ' . __FILE__ . ' at line ' . $fooLine . '.' . "\n\n" . 'Contains the following exceptions:' . "\n\n" . ' • ' . __METHOD__ . '(): (0) bar' . "\n" . ' in ' . __FILE__ . ' at line ' . $barLine . '.' . "\n\n" . ' • ' . __METHOD__ . '(): (0) baz' . "\n" . ' in ' . __FILE__ . ' at line ' . $bazLine . '.' ); } public function case_begin_transaction() { $this ->given( $group = new SUT('foo'), $oldStackSize = $group->getStackSize() ) ->when( $result = $group->beginTransaction(), $stackSize = $group->getStackSize() ) ->then ->integer($oldStackSize) ->isEqualTo(1) ->object($result) ->isIdenticalTo($group) ->integer($stackSize) ->isEqualTo($oldStackSize + 1); } public function case_rollback_transaction_with_an_empty_stack() { $this ->given( $group = new SUT('foo'), $oldStackSize = $group->getStackSize() ) ->when( $result = $group->rollbackTransaction(), $stackSize = $group->getStackSize() ) ->then ->integer($oldStackSize) ->isEqualTo(1) ->object($result) ->isIdenticalTo($group) ->integer($stackSize) ->isEqualTo($oldStackSize); } public function case_rollback_transaction() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $group->beginTransaction(), $oldStackSize = $group->getStackSize(), $group->rollbackTransaction() ) ->when( $result = $group->rollbackTransaction(), $stackSize = $group->getStackSize() ) ->then ->integer($oldStackSize) ->isEqualTo(3) ->object($result) ->isIdenticalTo($group) ->integer($stackSize) ->isEqualTo($oldStackSize - 2); } public function case_commit_transaction_with_an_empty_stack() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $oldCount = count($group), $oldStackSize = $group->getStackSize() ) ->when( $result = $group->commitTransaction(), $count = count($group), $stackSize = $group->getStackSize() ) ->then ->integer($oldCount) ->isEqualTo(0) ->integer($oldStackSize) ->isEqualTo(2) ->object($result) ->isIdenticalTo($group) ->integer($count) ->isEqualTo($oldCount) ->integer($stackSize) ->isEqualTo($oldStackSize - 1); } public function case_commit_transaction() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $exception1 = new SUT('bar'), $exception2 = new SUT('baz'), $group[] = $exception1, $group[] = $exception2, $oldCount = count($group), $oldStackSize = $group->getStackSize() ) ->when( $result = $group->commitTransaction(), $count = count($group), $stackSize = $group->getStackSize() ) ->then ->integer($oldCount) ->isEqualTo(0) ->integer($oldStackSize) ->isEqualTo(2) ->object($result) ->isIdenticalTo($group) ->integer($count) ->isEqualTo($oldCount + 2) ->integer($stackSize) ->isEqualTo($oldStackSize - 1) ->array(iterator_to_array($group->getIterator())) ->isEqualTo([ 0 => $exception1, 1 => $exception2 ]); } public function case_has_uncommitted_exceptions() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $group[] = new SUT('bar') ) ->when($result = $group->hasUncommittedExceptions()) ->then ->boolean($result) ->isTrue(); } public function case_has_no_uncommitted_exceptions() { $this ->given( $group = new SUT('foo'), $group->beginTransaction() ) ->when($result = $group->hasUncommittedExceptions()) ->then ->boolean($result) ->isFalse(); } public function case_has_no_uncommitted_exceptions_with_empty_stack() { $this ->given( $group = new SUT('foo'), $group[] = new SUT('bar') ) ->when($result = $group->hasUncommittedExceptions()) ->then ->boolean($result) ->isFalse(); } public function case_offset_exists_with_no_uncommited_exceptions() { $this ->given( $group = new SUT('foo'), $group['bar'] = new SUT('bar') ) ->when($result = $group->offsetExists('bar')) ->then ->boolean($result) ->isTrue(); } public function case_offset_does_not_exist_with_no_uncommited_exceptions() { $this ->given( $group = new SUT('foo'), $group['bar'] = new SUT('bar') ) ->when($result = $group->offsetExists('baz')) ->then ->boolean($result) ->isFalse(); } public function case_offset_exists() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $group->beginTransaction(), $group['bar'] = new SUT('bar') ) ->when($result = $group->offsetExists('bar')) ->then ->boolean($result) ->isTrue(); } public function case_offset_does_not_exist() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $group->beginTransaction(), $group['bar'] = new SUT('bar') ) ->when($result = $group->offsetExists('baz')) ->then ->boolean($result) ->isFalse(); } public function case_offset_get_with_no_uncommited_exceptions() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar'), $group['bar'] = $exception1 ) ->when($result = $group->offsetGet('bar')) ->then ->object($result) ->isIdenticalTo($exception1); } public function case_offset_get_does_not_exist_with_no_uncommited_exceptions() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar'), $group['bar'] = $exception1 ) ->when($result = $group->offsetGet('baz')) ->then ->variable($result) ->isNull(); } public function case_offset_get() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $group->beginTransaction(), $exception1 = new SUT('bar'), $group['bar'] = $exception1 ) ->when($result = $group->offsetGet('bar')) ->then ->object($result) ->isIdenticalTo($exception1); } public function case_offset_get_does_not_exist() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $group->beginTransaction(), $exception1 = new SUT('bar'), $group['bar'] = $exception1 ) ->when($result = $group->offsetGet('baz')) ->then ->variable($result) ->isNull(); } public function case_offset_set_not_an_exception() { $this ->given($group = new SUT('foo')) ->when($group->offsetSet('bar', new \StdClass())) ->then ->boolean($group->offsetExists('bar')) ->isFalse(); } public function case_offset_set() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar') ) ->when($result = $group->offsetExists('bar')) ->then ->boolean($result) ->isFalse() ->when($group->offsetSet('bar', $exception1)) ->then ->boolean($group->offsetExists('bar')) ->isTrue() ->object($group->offsetGet('bar')) ->isIdenticalTo($exception1); } public function case_offset_set_with_a_null_index() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar') ) ->when($group->offsetSet(null, $exception1)) ->then ->boolean($group->offsetExists(0)) ->isTrue() ->object($group->offsetGet(0)) ->isIdenticalTo($exception1); } public function case_offset_set_with_an_integer_index() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar') ) ->when($group->offsetSet(42, $exception1)) ->then ->boolean($group->offsetExists(42)) ->isFalse() ->boolean($group->offsetExists(0)) ->isTrue() ->object($group->offsetGet(0)) ->isIdenticalTo($exception1); } public function case_offset_unset_with_no_uncommited_exceptions() { $this ->given( $group = new SUT('foo'), $group['bar'] = new SUT('bar') ) ->when($group->offsetUnset('bar')) ->then ->boolean($group->offsetExists('bar')) ->isFalse(); } public function case_offset_unset_does_not_exist_with_no_uncommited_exceptions() { $this ->given($group = new SUT('foo')) ->when($group->offsetUnset('bar')) ->then ->boolean($group->offsetExists('bar')) ->isFalse(); } public function case_offset_unset() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $group->beginTransaction(), $group['bar'] = new SUT('bar') ) ->when($result = $group->offsetUnset('bar')) ->then ->boolean($group->offsetExists('bar')) ->isFalse(); } public function case_offset_unset_does_not_exist() { $this ->given( $group = new SUT('foo'), $group->beginTransaction(), $group->beginTransaction() ) ->when($result = $group->offsetUnset('bar')) ->then ->boolean($group->offsetExists('bar')) ->isFalse(); } public function case_get_exceptions() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar'), $exception2 = new SUT('baz'), $group['bar'] = $exception1, $group->beginTransaction(), $group['baz'] = $exception2 ) ->when($result = $group->getExceptions()) ->then ->object($result) ->isInstanceOf('ArrayObject') ->object($result['bar']) ->isIdenticalTo($exception1); } public function case_get_iterator() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar'), $group['bar'] = $exception1 ) ->when($result = $group->getIterator()) ->then ->object($result) ->isInstanceOf('ArrayIterator') ->array(iterator_to_array($result)) ->isEqualTo([ 'bar' => $exception1 ]); } public function case_count() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar'), $exception2 = new SUT('baz'), $group['bar'] = $exception1, $group->beginTransaction(), $group['baz'] = $exception2 ) ->when($result = count($group)) ->then ->integer($result) ->isEqualTo(1); } public function get_get_stack_size() { $this ->given( $group = new SUT('foo'), $exception1 = new SUT('bar'), $exception2 = new SUT('baz'), $group['bar'] = $exception1, $group->beginTransaction(), $group['baz'] = $exception2 ) ->when($result = $group->getStackSize()) ->then ->integer($result) ->isEqualTo(2); } } when($result = new SUT('foo', 42, '/hoa/flatland', 153)) ->then ->object($result) ->isInstanceOf('Hoa\Exception\Exception'); } public function case_get_message() { $this ->given($exception = new SUT('foo', 42, '/hoa/flatland', 153)) ->when($result = $exception->raise()) ->then ->string($result) ->isEqualTo( '{main}: (42) foo' . "\n" . 'in /hoa/flatland at line 153.' ); } public function case_disable_error_handler() { $this ->given( $this->function->restore_error_handler = function () use (&$called) { $called = true; return null; } ) ->when($result = SUT::enableErrorHandler(false)) ->then ->variable($result) ->isNull() ->boolean($called) ->isTrue(); } public function case_enable_error_handler() { $self = $this; $this ->given( $this->function->set_error_handler = function ($handler) use ($self, &$called) { $called = true; $self ->object($handler) ->isInstanceOf('Closure') ->let($reflection = new \ReflectionObject($handler)) ->array($invokeParameters = $reflection->getMethod('__invoke')->getParameters()) ->hasSize(5) ->string($invokeParameters[0]->getName()) ->isEqualTo('no') ->string($invokeParameters[1]->getName()) ->isEqualTo('str') ->string($invokeParameters[2]->getName()) ->isEqualTo('file') ->boolean($invokeParameters[2]->isOptional()) ->isTrue() ->string($invokeParameters[3]->getName()) ->isEqualTo('line') ->boolean($invokeParameters[3]->isOptional()) ->isTrue() ->string($invokeParameters[4]->getName()) ->isEqualTo('ctx') ->boolean($invokeParameters[4]->isOptional()) ->isTrue(); return null; } ) ->when($result = SUT::enableErrorHandler()) ->then ->variable($result) ->isNull() ->boolean($called) ->isTrue(); } public function case_error_handler() { $this ->given(SUT::enableErrorHandler()) ->exception(function () { ++$i; }) ->isInstanceOf('Hoa\Exception\Error') ->hasMessage('Undefined variable: i'); } } send(); return; } public function send() { Event::notify( 'hoa://Event/Exception', $this, new Event\Bucket($this) ); return; } } Consistency::flexEntity('Hoa\Exception\Exception'); _group = new \SplStack(); $this->beginTransaction(); return; } public function raise($previous = false) { $out = parent::raise($previous); if (0 >= count($this)) { return $out; } $out .= "\n\n" . 'Contains the following exceptions:'; foreach ($this as $exception) { $out .= "\n\n" . ' • ' . str_replace( "\n", "\n" . ' ', $exception->raise($previous) ); } return $out; } public function beginTransaction() { $this->_group->push(new \ArrayObject()); return $this; } public function rollbackTransaction() { if (1 >= count($this->_group)) { return $this; } $this->_group->pop(); return $this; } public function commitTransaction() { if (false === $this->hasUncommittedExceptions()) { $this->_group->pop(); return $this; } foreach ($this->_group->pop() as $index => $exception) { $this[$index] = $exception; } return $this; } public function hasUncommittedExceptions() { return 1 < count($this->_group) && 0 < count($this->_group->top()); } public function offsetExists($index) { foreach ($this->_group as $group) { if (isset($group[$index])) { return true; } } return false; } public function offsetGet($index) { foreach ($this->_group as $group) { if (isset($group[$index])) { return $group[$index]; } } return null; } public function offsetSet($index, $exception) { if (!($exception instanceof \Exception)) { return null; } $group = $this->_group->top(); if (null === $index || true === is_int($index)) { $group[] = $exception; } else { $group[$index] = $exception; } return; } public function offsetUnset($index) { foreach ($this->_group as $group) { if (isset($group[$index])) { unset($group[$index]); } } return; } public function getExceptions() { return $this->_group->bottom(); } public function getIterator() { return $this->getExceptions()->getIterator(); } public function count() { return count($this->getExceptions()); } public function getStackSize() { return count($this->_group); } } file = $file; $this->line = $line; $this->_trace = $trace; parent::__construct($message, $code); return; } public static function enableErrorHandler($enable = true) { if (false === $enable) { return restore_error_handler(); } return set_error_handler( function ($no, $str, $file = null, $line = null, $ctx = null) { if (0 === ($no & error_reporting())) { return; } $trace = debug_backtrace(); array_shift($trace); array_shift($trace); throw new Error($str, $no, $file, $line, $trace); } ); } } getStreamName()); } public function getDirname() { return dirname($this->getStreamName()); } public function getSize() { if (false === $this->getStatistic()) { return false; } return filesize($this->getStreamName()); } public function getStatistic() { return fstat($this->getStream()); } public function getATime() { return fileatime($this->getStreamName()); } public function getCTime() { return filectime($this->getStreamName()); } public function getMTime() { return filemtime($this->getStreamName()); } public function getGroup() { return filegroup($this->getStreamName()); } public function getOwner() { return fileowner($this->getStreamName()); } public function getPermissions() { return fileperms($this->getStreamName()); } public function getReadablePermissions() { $p = $this->getPermissions(); if (($p & 0xC000) == 0xC000) { $out = 's'; } elseif (($p & 0xA000) == 0xA000) { $out = 'l'; } elseif (($p & 0x8000) == 0x8000) { $out = '-'; } elseif (($p & 0x6000) == 0x6000) { $out = 'b'; } elseif (($p & 0x4000) == 0x4000) { $out = 'd'; } elseif (($p & 0x2000) == 0x2000) { $out = 'c'; } elseif (($p & 0x1000) == 0x1000) { $out = 'p'; } else { $out = 'u'; } $out .= (($p & 0x0100) ? 'r' : '-') . (($p & 0x0080) ? 'w' : '-') . (($p & 0x0040) ? (($p & 0x0800) ? 's' : 'x') : (($p & 0x0800) ? 'S' : '-')) . (($p & 0x0020) ? 'r' : '-') . (($p & 0x0010) ? 'w' : '-') . (($p & 0x0008) ? (($p & 0x0400) ? 's' : 'x') : (($p & 0x0400) ? 'S' : '-')) . (($p & 0x0004) ? 'r' : '-') . (($p & 0x0002) ? 'w' : '-') . (($p & 0x0001) ? (($p & 0x0200) ? 't' : 'x') : (($p & 0x0200) ? 'T' : '-')); return $out; } public function isReadable() { return is_readable($this->getStreamName()); } public function isWritable() { return is_writable($this->getStreamName()); } public function isExecutable() { return is_executable($this->getStreamName()); } public function clearStatisticCache() { clearstatcache(true, $this->getStreamName()); return; } public static function clearAllStatisticCaches() { clearstatcache(); return; } public function touch($time = -1, $atime = -1) { if ($time == -1) { $time = time(); } if ($atime == -1) { $atime = $time; } return touch($this->getStreamName(), $time, $atime); } public function copy($to, $force = Stream\IStream\Touchable::DO_NOT_OVERWRITE) { $from = $this->getStreamName(); if ($force === Stream\IStream\Touchable::DO_NOT_OVERWRITE && true === file_exists($to)) { return true; } if (null === $this->getStreamContext()) { return @copy($from, $to); } return @copy($from, $to, $this->getStreamContext()->getContext()); } public function move( $name, $force = Stream\IStream\Touchable::DO_NOT_OVERWRITE, $mkdir = Stream\IStream\Touchable::DO_NOT_MAKE_DIRECTORY ) { $from = $this->getStreamName(); if ($force === Stream\IStream\Touchable::DO_NOT_OVERWRITE && true === file_exists($name)) { return false; } if (Stream\IStream\Touchable::MAKE_DIRECTORY === $mkdir) { Directory::create( dirname($name), Directory::MODE_CREATE_RECURSIVE ); } if (null === $this->getStreamContext()) { return @rename($from, $name); } return @rename($from, $name, $this->getStreamContext()->getContext()); } public function delete() { if (null === $this->getStreamContext()) { return @unlink($this->getStreamName()); } return @unlink( $this->getStreamName(), $this->getStreamContext()->getContext() ); } public function changeGroup($group) { return chgrp($this->getStreamName(), $group); } public function changeMode($mode) { return chmod($this->getStreamName(), $mode); } public function changeOwner($user) { return chown($this->getStreamName(), $user); } public static function umask($umask = null) { if (null === $umask) { return umask(); } return umask($umask); } public function isFile() { return is_file($this->getStreamName()); } public function isLink() { return is_link($this->getStreamName()); } public function isDirectory() { return is_dir($this->getStreamName()); } public function isSocket() { return filetype($this->getStreamName()) == 'socket'; } public function isFIFOPipe() { return filetype($this->getStreamName()) == 'fifo'; } public function isCharacterSpecial() { return filetype($this->getStreamName()) == 'char'; } public function isBlockSpecial() { return filetype($this->getStreamName()) == 'block'; } public function isUnknown() { return filetype($this->getStreamName()) == 'unknown'; } protected function setMode($mode) { $old = $this->_mode; $this->_mode = $mode; return $old; } public function getMode() { return $this->_mode; } public function getINode() { return fileinode($this->getStreamName()); } public static function isCaseSensitive() { return !( file_exists(mb_strtolower(__FILE__)) && file_exists(mb_strtoupper(__FILE__)) ); } public function getRealPath() { if (false === $out = realpath($this->getStreamName())) { return $this->getStreamName(); } return $out; } public function getExtension() { return pathinfo( $this->getStreamName(), PATHINFO_EXTENSION ); } public function getFilename() { $file = basename($this->getStreamName()); if (defined('PATHINFO_FILENAME')) { return pathinfo($file, PATHINFO_FILENAME); } if (strstr($file, '.')) { return substr($file, 0, strrpos($file, '.')); } return $file; } } setMode($mode); parent::__construct($streamName, $context, $wait); return; } protected function &_open($streamName, Stream\Context $context = null) { if (false === is_dir($streamName)) { if ($this->getMode() == self::MODE_READ) { throw new Exception\FileDoesNotExist( 'Directory %s does not exist.', 0, $streamName ); } else { self::create( $streamName, $this->getMode(), null !== $context ? $context->getContext() : null ); } } $out = null; return $out; } protected function _close() { return true; } public function copy($to, $force = Stream\IStream\Touchable::DO_NOT_OVERWRITE) { if (empty($to)) { throw new Exception( 'The destination path (to copy) is empty.', 1 ); } $from = $this->getStreamName(); $fromLength = strlen($from) + 1; $finder = new Finder(); $finder->in($from); self::create($to, self::MODE_CREATE_RECURSIVE); foreach ($finder as $file) { $relative = substr($file->getPathname(), $fromLength); $_to = $to . DS . $relative; if (true === $file->isDir()) { self::create($_to, self::MODE_CREATE); continue; } $handle = null; if (true === $file->isFile()) { $handle = new Read($file->getPathname()); } elseif (true === $file->isDir()) { $handle = new Directory($file->getPathName()); } elseif (true === $file->isLink()) { $handle = new Link\Read($file->getPathName()); } if (null !== $handle) { $handle->copy($_to, $force); $handle->close(); } } return true; } public function delete() { $from = $this->getStreamName(); $finder = new Finder(); $finder->in($from) ->childFirst(); foreach ($finder as $file) { $file->open()->delete(); $file->close(); } if (null === $this->getStreamContext()) { return @rmdir($from); } return @rmdir($from, $this->getStreamContext()->getContext()); } public static function create( $name, $mode = self::MODE_CREATE_RECURSIVE, $context = null ) { if (true === is_dir($name)) { return true; } if (empty($name)) { return false; } if (null !== $context) { if (false === Stream\Context::contextExists($context)) { throw new Exception( 'Context %s was not previously declared, cannot retrieve ' . 'this context.', 2, $context ); } else { $context = Stream\Context::getInstance($context); } } if (null === $context) { return @mkdir( $name, 0755, self::MODE_CREATE_RECURSIVE === $mode ); } return @mkdir( $name, 0755, self::MODE_CREATE_RECURSIVE === $mode, $context->getContext() ); } } isFile()) { return $this->_stream = new ReadWrite($this->getPathname()); } elseif (true === $this->isDir()) { return $this->_stream = new Directory($this->getPathname()); } elseif (true === $this->isLink()) { return $this->_stream = new Link\ReadWrite($this->getPathname()); } throw new Exception('%s has an unknown type.', 0, $this->getPathname()); } public function close() { if (null === $this->_stream) { return; } return $this->_stream->close(); } public function __destruct() { $this->close(); return; } } _flags = Iterator\FileSystem::KEY_AS_PATHNAME | Iterator\FileSystem::CURRENT_AS_FILEINFO | Iterator\FileSystem::SKIP_DOTS; $this->_first = Iterator\Recursive\Iterator::SELF_FIRST; return; } public function in($paths) { if (!is_array($paths)) { $paths = [$paths]; } foreach ($paths as $path) { if (1 === preg_match('/[\*\?\[\]]/', $path)) { $iterator = new Iterator\CallbackFilter( new Iterator\Glob(rtrim($path, DS)), function ($current) { return $current->isDir(); } ); foreach ($iterator as $fileInfo) { $this->_paths[] = $fileInfo->getPathname(); } } else { $this->_paths[] = $path; } } return $this; } public function maxDepth($depth) { $this->_maxDepth = $depth; return $this; } public function files() { $this->_types[] = 'file'; return $this; } public function directories() { $this->_types[] = 'dir'; return $this; } public function links() { $this->_types[] = 'link'; return $this; } public function followSymlinks($flag = true) { if (true === $flag) { $this->_flags ^= Iterator\FileSystem::FOLLOW_SYMLINKS; } else { $this->_flags |= Iterator\FileSystem::FOLLOW_SYMLINKS; } return $this; } public function name($regex) { $this->_filters[] = function (\SplFileInfo $current) use ($regex) { return 0 !== preg_match($regex, $current->getBasename()); }; return $this; } public function notIn($regex) { $this->_filters[] = function (\SplFileInfo $current) use ($regex) { foreach (explode(DS, $current->getPathname()) as $part) { if (0 !== preg_match($regex, $part)) { return false; } } return true; }; return $this; } public function size($size) { if (0 === preg_match('#^(<|<=|>|>=|=)\s*(\d+)\s*((?:[KMGTPEZY])b)?$#', $size, $matches)) { return $this; } $number = floatval($matches[2]); $unit = isset($matches[3]) ? $matches[3] : 'b'; $operator = $matches[1]; switch ($unit) { case 'b': break; case 'Kb': $number <<= 10; break; case 'Mb': $number <<= 20; break; case 'Gb': $number <<= 30; break; case 'Tb': $number *= 1099511627776; break; case 'Pb': $number *= pow(1024, 5); break; case 'Eb': $number *= pow(1024, 6); break; case 'Zb': $number *= pow(1024, 7); break; case 'Yb': $number *= pow(1024, 8); break; } $filter = null; switch ($operator) { case '<': $filter = function (\SplFileInfo $current) use ($number) { return $current->getSize() < $number; }; break; case '<=': $filter = function (\SplFileInfo $current) use ($number) { return $current->getSize() <= $number; }; break; case '>': $filter = function (\SplFileInfo $current) use ($number) { return $current->getSize() > $number; }; break; case '>=': $filter = function (\SplFileInfo $current) use ($number) { return $current->getSize() >= $number; }; break; case '=': $filter = function (\SplFileInfo $current) use ($number) { return $current->getSize() === $number; }; break; } $this->_filters[] = $filter; return $this; } public function dots($flag = true) { if (true === $flag) { $this->_flags ^= Iterator\FileSystem::SKIP_DOTS; } else { $this->_flags |= Iterator\FileSystem::SKIP_DOTS; } return $this; } public function owner($owner) { $this->_filters[] = function (\SplFileInfo $current) use ($owner) { return $current->getOwner() === $owner; }; return $this; } protected function formatDate($date, &$operator) { $operator = -1; if (0 === preg_match('#\bago\b#', $date)) { $date .= ' ago'; } if (0 !== preg_match('#^(since|until)\b(.+)$#', $date, $matches)) { $time = strtotime($matches[2]); if ('until' === $matches[1]) { $operator = 1; } } else { $time = strtotime($date); } return $time; } public function changed($date) { $time = $this->formatDate($date, $operator); if (-1 === $operator) { $this->_filters[] = function (\SplFileInfo $current) use ($time) { return $current->getCTime() >= $time; }; } else { $this->_filters[] = function (\SplFileInfo $current) use ($time) { return $current->getCTime() < $time; }; } return $this; } public function modified($date) { $time = $this->formatDate($date, $operator); if (-1 === $operator) { $this->_filters[] = function (\SplFileInfo $current) use ($time) { return $current->getMTime() >= $time; }; } else { $this->_filters[] = function (\SplFileInfo $current) use ($time) { return $current->getMTime() < $time; }; } return $this; } public function filter($callback) { $this->_filters[] = $callback; return $this; } public function sortByName($locale = 'root') { if (true === class_exists('Collator', false)) { $collator = new \Collator($locale); $this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) use ($collator) { return $collator->compare($a->getPathname(), $b->getPathname()); }; } else { $this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) { return strcmp($a->getPathname(), $b->getPathname()); }; } return $this; } public function sortBySize() { $this->_sorts[] = function (\SplFileInfo $a, \SplFileInfo $b) { return $a->getSize() < $b->getSize(); }; return $this; } public function sort($callable) { $this->_sorts[] = $callable; return $this; } public function childFirst() { $this->_first = Iterator\Recursive\Iterator::CHILD_FIRST; return $this; } public function getIterator() { $_iterator = new Iterator\Append(); $types = $this->getTypes(); if (!empty($types)) { $this->_filters[] = function (\SplFileInfo $current) use ($types) { return in_array($current->getType(), $types); }; } $maxDepth = $this->getMaxDepth(); $splFileInfo = $this->getSplFileInfo(); foreach ($this->getPaths() as $path) { if (1 == $maxDepth) { $iterator = new Iterator\IteratorIterator( new Iterator\Recursive\Directory( $path, $this->getFlags(), $splFileInfo ), $this->getFirst() ); } else { $iterator = new Iterator\Recursive\Iterator( new Iterator\Recursive\Directory( $path, $this->getFlags(), $splFileInfo ), $this->getFirst() ); if (1 < $maxDepth) { $iterator->setMaxDepth($maxDepth - 1); } } $_iterator->append($iterator); } foreach ($this->getFilters() as $filter) { $_iterator = new Iterator\CallbackFilter( $_iterator, $filter ); } $sorts = $this->getSorts(); if (empty($sorts)) { return $_iterator; } $array = iterator_to_array($_iterator); foreach ($sorts as $sort) { uasort($array, $sort); } return new Iterator\Map($array); } public function setSplFileInfo($splFileInfo) { $old = $this->_splFileInfo; $this->_splFileInfo = $splFileInfo; return $old; } public function getSplFileInfo() { return $this->_splFileInfo; } protected function getPaths() { return $this->_paths; } public function getMaxDepth() { return $this->_maxDepth; } public function getTypes() { return $this->_types; } protected function getFilters() { return $this->_filters; } protected function getSorts() { return $this->_sorts; } public function getFlags() { return $this->_flags; } public function getFirst() { return $this->_first; } } getMode(), $createModes)) { throw new Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName)) { throw new Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function eof() { return feof($this->getStream()); } public function read($length) { if (0 > $length) { throw new Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fread($this->getStream(), $length); } public function readString($length) { return $this->read($length); } public function readCharacter() { return fgetc($this->getStream()); } public function readBoolean() { return (bool) $this->read(1); } public function readInteger($length = 1) { return (int) $this->read($length); } public function readFloat($length = 1) { return (float) $this->read($length); } public function readArray($format = null) { return $this->scanf($format); } public function readLine() { return fgets($this->getStream()); } public function readAll($offset = 0) { return stream_get_contents($this->getStream(), -1, $offset); } public function scanf($format) { return fscanf($this->getStream(), $format); } } setListener( new Event\Listener( $this, [ 'new', 'modify', 'move' ] ) ); if (null !== $latency) { $this->setLatency($latency); } return; } public function run() { $iterator = $this->getIterator(); $previous = iterator_to_array($iterator); $current = $previous; while (true) { foreach ($current as $name => $c) { if (!isset($previous[$name])) { $this->getListener()->fire( 'new', new Event\Bucket([ 'file' => $c ]) ); continue; } if (null === $c->getHash()) { unset($current[$name]); continue; } if ($previous[$name]->getHash() != $c->getHash()) { $this->getListener()->fire( 'modify', new Event\Bucket([ 'file' => $c ]) ); } unset($previous[$name]); } foreach ($previous as $p) { $this->getListener()->fire( 'move', new Event\Bucket([ 'file' => $p ]) ); } usleep($this->getLatency() * 1000000); $previous = $current; $current = iterator_to_array($iterator); } return; } public function setLatency($latency) { $old = $this->_latency; $this->_latency = $latency; return $old; } public function getLatency() { return $this->_latency; } } getMode(), $createModes)) { throw new Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName) && parent::MODE_READ_WRITE == $this->getMode()) { throw new Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function eof() { return feof($this->getStream()); } public function read($length) { if (0 > $length) { throw new Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fread($this->getStream(), $length); } public function readString($length) { return $this->read($length); } public function readCharacter() { return fgetc($this->getStream()); } public function readBoolean() { return (bool) $this->read(1); } public function readInteger($length = 1) { return (int) $this->read($length); } public function readFloat($length = 1) { return (float) $this->read($length); } public function readArray($format = null) { return $this->scanf($format); } public function readLine() { return fgets($this->getStream()); } public function readAll($offset = 0) { return stream_get_contents($this->getStream(), -1, $offset); } public function scanf($format) { return fscanf($this->getStream(), $format); } public function write($string, $length) { if (0 > $length) { throw new Exception( 'Length must be greater than 0, given %d.', 3, $length ); } return fwrite($this->getStream(), $string, $length); } public function writeString($string) { $string = (string) $string; return $this->write($string, strlen($string)); } public function writeCharacter($char) { return $this->write((string) $char[0], 1); } public function writeBoolean($boolean) { return $this->write((string) (bool) $boolean, 1); } public function writeInteger($integer) { $integer = (string) (int) $integer; return $this->write($integer, strlen($integer)); } public function writeFloat($float) { $float = (string) (float) $float; return $this->write($float, strlen($float)); } public function writeArray(array $array) { $array = var_export($array, true); return $this->write($array, strlen($array)); } public function writeLine($line) { if (false === $n = strpos($line, "\n")) { return $this->write($line . "\n", strlen($line) + 1); } ++$n; return $this->write(substr($line, 0, $n), $n); } public function writeAll($string) { return $this->write($string, strlen($string)); } public function truncate($size) { return ftruncate($this->getStream(), $size); } } getMode(), $createModes)) { throw new Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName) && parent::MODE_TRUNCATE_WRITE == $this->getMode()) { throw new Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function write($string, $length) { if (0 > $length) { throw new Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fwrite($this->getStream(), $string, $length); } public function writeString($string) { $string = (string) $string; return $this->write($string, strlen($string)); } public function writeCharacter($char) { return $this->write((string) $char[0], 1); } public function writeBoolean($boolean) { return $this->write((string) (bool) $boolean, 1); } public function writeInteger($integer) { $integer = (string) (int) $integer; return $this->write($integer, strlen($integer)); } public function writeFloat($float) { $float = (string) (float) $float; return $this->write($float, strlen($float)); } public function writeArray(array $array) { $array = var_export($array, true); return $this->write($array, strlen($array)); } public function writeLine($line) { if (false === $n = strpos($line, "\n")) { return $this->write($line . "\n", strlen($line) + 1); } ++$n; return $this->write(substr($line, 0, $n), $n); } public function writeAll($string) { return $this->write($string, strlen($string)); } public function truncate($size) { return ftruncate($this->getStream(), $size); } } getMode(), $createModes)) { throw new File\Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName)) { throw new File\Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function eof() { return feof($this->getStream()); } public function read($length) { if (0 > $length) { throw new File\Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fread($this->getStream(), $length); } public function readString($length) { return $this->read($length); } public function readCharacter() { return fgetc($this->getStream()); } public function readBoolean() { return (bool) $this->read(1); } public function readInteger($length = 1) { return (int) $this->read($length); } public function readFloat($length = 1) { return (float) $this->read($length); } public function readArray($format = null) { return $this->scanf($format); } public function readLine() { return fgets($this->getStream()); } public function readAll($offset = 0) { return stream_get_contents($this->getStream(), -1, $offset); } public function scanf($format) { return fscanf($this->getStream(), $format); } } getMode(), $createModes)) { throw new File\Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName) && parent::MODE_READ_WRITE == $this->getMode()) { throw new File\Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function eof() { return feof($this->getStream()); } public function read($length) { if (0 > $length) { throw new File\Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fread($this->getStream(), $length); } public function readString($length) { return $this->read($length); } public function readCharacter() { return fgetc($this->getStream()); } public function readBoolean() { return (bool) $this->read(1); } public function readInteger($length = 1) { return (int) $this->read($length); } public function readFloat($length = 1) { return (float) $this->read($length); } public function readArray($format = null) { return $this->scanf($format); } public function readLine() { return fgets($this->getStream()); } public function readAll($offset = 0) { return stream_get_contents($this->getStream(), -1, $offset); } public function scanf($format) { return fscanf($this->getStream(), $format); } public function write($string, $length) { if (0 > $length) { throw new File\Exception( 'Length must be greater than 0, given %d.', 3, $length ); } return fwrite($this->getStream(), $string, $length); } public function writeString($string) { $string = (string) $string; return $this->write($string, strlen($string)); } public function writeCharacter($char) { return $this->write((string) $char[0], 1); } public function writeBoolean($boolean) { return $this->write((string) (bool) $boolean, 1); } public function writeInteger($integer) { $integer = (string) (int) $integer; return $this->write($integer, strlen($integer)); } public function writeFloat($float) { $float = (string) (float) $float; return $this->write($float, strlen($float)); } public function writeArray(array $array) { $array = var_export($array, true); return $this->write($array, strlen($array)); } public function writeLine($line) { if (false === $n = strpos($line, "\n")) { return $this->write($line . "\n", strlen($line) + 1); } ++$n; return $this->write(substr($line, 0, $n), $n); } public function writeAll($string) { return $this->write($string, strlen($string)); } public function truncate($size) { return ftruncate($this->getStream(), $size); } } getMode(), $createModes)) { throw new File\Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName)) { throw new File\Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function write($string, $length) { if (0 > $length) { throw new File\Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fwrite($this->getStream(), $string, $length); } public function writeString($string) { $string = (string) $string; return $this->write($string, strlen($string)); } public function writeCharacter($char) { return $this->write((string) $char[0], 1); } public function writeBoolean($boolean) { return $this->write((string) (bool) $boolean, 1); } public function writeInteger($integer) { $integer = (string) (int) $integer; return $this->write($integer, strlen($integer)); } public function writeFloat($float) { $float = (string) (float) $float; return $this->write($float, strlen($float)); } public function writeArray(array $array) { $array = var_export($array, true); return $this->write($array, strlen($array)); } public function writeLine($line) { if (false === $n = strpos($line, "\n")) { return $this->write($line . "\n", strlen($line) + 1); } ++$n; return $this->write(substr($line, 0, $n), $n); } public function writeAll($string) { return $this->write($string, strlen($string)); } public function truncate($size) { return ftruncate($this->getStream(), $size); } } getMode(), $createModes)) { throw new File\Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName)) { throw new File\Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function eof() { return feof($this->getStream()); } public function read($length) { if (0 > $length) { throw new File\Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fread($this->getStream(), $length); } public function readString($length) { return $this->read($length); } public function readCharacter() { return fgetc($this->getStream()); } public function readBoolean() { return (bool) $this->read(1); } public function readInteger($length = 1) { return (int) $this->read($length); } public function readFloat($length = 1) { return (float) $this->read($length); } public function readArray($format = null) { return $this->scanf($format); } public function readLine() { return fgets($this->getStream()); } public function readAll($offset = 0) { return stream_get_contents($this->getStream(), -1, $offset); } public function scanf($format) { return fscanf($this->getStream(), $format); } } getMode(), $createModes)) { throw new File\Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName) && parent::MODE_READ_WRITE == $this->getMode()) { throw new File\Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function eof() { return feof($this->getStream()); } public function read($length) { if (0 > $length) { throw new File\Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fread($this->getStream(), $length); } public function readString($length) { return $this->read($length); } public function readCharacter() { return fgetc($this->getStream()); } public function readBoolean() { return (bool) $this->read(1); } public function readInteger($length = 1) { return (int) $this->read($length); } public function readFloat($length = 1) { return (float) $this->read($length); } public function readArray($format = null) { return $this->scanf($format); } public function readLine() { return fgets($this->getStream()); } public function readAll($offset = 0) { return stream_get_contents($this->getStream(), -1, $offset); } public function scanf($format) { return fscanf($this->getStream(), $format); } public function write($string, $length) { if (0 > $length) { throw new File\Exception( 'Length must be greater than 0, given %d.', 3, $length ); } return fwrite($this->getStream(), $string, $length); } public function writeString($string) { $string = (string) $string; return $this->write($string, strlen($string)); } public function writeCharacter($char) { return $this->write((string) $char[0], 1); } public function writeBoolean($boolean) { return $this->write((string) (bool) $boolean, 1); } public function writeInteger($integer) { $integer = (string) (int) $integer; return $this->write($integer, strlen($integer)); } public function writeFloat($float) { $float = (string) (float) $float; return $this->write($float, strlen($float)); } public function writeArray(array $array) { $array = var_export($array, true); return $this->write($array, strlen($array)); } public function writeLine($line) { if (false === $n = strpos($line, "\n")) { return $this->write($line . "\n", strlen($line) + 1); } ++$n; return $this->write(substr($line, 0, $n), $n); } public function writeAll($string) { return $this->write($string, strlen($string)); } public function truncate($size) { return ftruncate($this->getStream(), $size); } } getMode(), $createModes)) { throw new File\Exception( 'Open mode are not supported; given %d. Only %s are supported.', 0, [$this->getMode(), implode(', ', $createModes)] ); } preg_match('#^(\w+)://#', $streamName, $match); if (((isset($match[1]) && $match[1] == 'file') || !isset($match[1])) && !file_exists($streamName)) { throw new File\Exception\FileDoesNotExist( 'File %s does not exist.', 1, $streamName ); } $out = parent::_open($streamName, $context); return $out; } public function write($string, $length) { if (0 > $length) { throw new File\Exception( 'Length must be greater than 0, given %d.', 2, $length ); } return fwrite($this->getStream(), $string, $length); } public function writeString($string) { $string = (string) $string; return $this->write($string, strlen($string)); } public function writeCharacter($char) { return $this->write((string) $char[0], 1); } public function writeBoolean($boolean) { return $this->write((string) (bool) $boolean, 1); } public function writeInteger($integer) { $integer = (string) (int) $integer; return $this->write($integer, strlen($integer)); } public function writeFloat($float) { $float = (string) (float) $float; return $this->write($float, strlen($float)); } public function writeArray(array $array) { $array = var_export($array, true); return $this->write($array, strlen($array)); } public function writeLine($line) { if (false === $n = strpos($line, "\n")) { return $this->write($line . "\n", strlen($line) + 1); } ++$n; return $this->write(substr($line, 0, $n), $n); } public function writeAll($string) { return $this->write($string, strlen($string)); } public function truncate($size) { return ftruncate($this->getStream(), $size); } } getStreamName()); } public function changeGroup($group) { return lchgrp($this->getStreamName(), $group); } public function changeOwner($user) { return lchown($this->getStreamName(), $user); } public function getPermissions() { return 41453; } public function getTarget() { $target = dirname($this->getStreamName()) . DS . $this->getTargetName(); $context = null !== $this->getStreamContext() ? $this->getStreamContext()->getCurrentId() : null; if (true === is_link($target)) { return new ReadWrite( $target, File::MODE_APPEND_READ_WRITE, $context ); } elseif (true === is_file($target)) { return new File\ReadWrite( $target, File::MODE_APPEND_READ_WRITE, $context ); } elseif (true === is_dir($target)) { return new File\Directory( $target, File::MODE_READ, $context ); } throw new File\Exception( 'Cannot find an appropriated object that matches with ' . 'path %s when defining it.', 1, $target ); } public function getTargetName() { return readlink($this->getStreamName()); } public static function create($name, $target) { if (false != linkinfo($name)) { return true; } return symlink($target, $name); } } Consistency::flexEntity('Hoa\File\Link\Link'); setMode($mode); switch ($streamName) { case '0': $streamName = 'php://stdin'; break; case '1': $streamName = 'php://stdout'; break; case '2': $streamName = 'php://stderr'; break; default: if (true === ctype_digit($streamName)) { if (PHP_VERSION_ID >= 50306) { $streamName = 'php://fd/' . $streamName; } else { throw new Exception( 'You need PHP5.3.6 to use a file descriptor ' . 'other than 0, 1 or 2 (tried %d with PHP%s).', 0, [$streamName, PHP_VERSION] ); } } } parent::__construct($streamName, $context, $wait); return; } protected function &_open($streamName, Stream\Context $context = null) { if (substr($streamName, 0, 4) == 'file' && false === is_dir(dirname($streamName))) { throw new Exception( 'Directory %s does not exist. Could not open file %s.', 1, [dirname($streamName), basename($streamName)] ); } if (null === $context) { if (false === $out = @fopen($streamName, $this->getMode(), true)) { throw new Exception( 'Failed to open stream %s.', 2, $streamName ); } return $out; } $out = @fopen( $streamName, $this->getMode(), true, $context->getContext() ); if (false === $out) { throw new Exception( 'Failed to open stream %s.', 3, $streamName ); } return $out; } protected function _close() { return @fclose($this->getStream()); } public function newBuffer($callable = null, $size = null) { $this->setStreamBuffer($size); return 1; } public function flush() { return fflush($this->getStream()); } public function deleteBuffer() { return $this->disableStreamBuffer(); } public function getBufferLevel() { return 1; } public function getBufferSize() { return $this->getStreamBufferSize(); } public function lock($operation) { return flock($this->getStream(), $operation); } public function rewind() { return rewind($this->getStream()); } public function seek($offset, $whence = Stream\IStream\Pointable::SEEK_SET) { return fseek($this->getStream(), $offset, $whence); } public function tell() { $stream = $this->getStream(); if (null === $stream) { return 0; } return ftell($stream); } public static function create($name, $dummy) { if (file_exists($name)) { return true; } return touch($name); } } Consistency::flexEntity('Hoa\File\File'); _case_get_mode_xxx(0010000, SUT::IS_FIFO); } public function case_get_mode_character() { return $this->_case_get_mode_xxx(0020000, SUT::IS_CHARACTER); } public function case_get_mode_directory() { return $this->_case_get_mode_xxx(0040000, SUT::IS_DIRECTORY); } public function case_get_mode_block() { return $this->_case_get_mode_xxx(0060000, SUT::IS_BLOCK); } public function case_get_mode_regular() { return $this->_case_get_mode_xxx(0100000, SUT::IS_REGULAR); } public function case_get_mode_link() { return $this->_case_get_mode_xxx(0120000, SUT::IS_LINK); } public function case_get_mode_socket() { return $this->_case_get_mode_xxx(0140000, SUT::IS_SOCKET); } public function case_get_mode_whiteout() { return $this->_case_get_mode_xxx(0160000, SUT::IS_WHITEOUT); } public function case_get_mode_unknown() { return $this->_case_get_mode_xxx(0170000, -1); } protected function _case_get_mode_xxx($mask, $expect) { $this ->given($this->function->fstat = ['mode' => $mask & 0170000]) ->when($result = SUT::getMode(null)) ->then ->integer($result) ->isEqualTo($expect); } public function case_set_input() { $this ->given($input = new LUT\Input()) ->when($result = SUT::setInput($input)) ->then ->variable($result) ->isNull() ->object(SUT::getInput()) ->isIdenticalTo($input); } public function case_get_input() { $this ->when($result = SUT::getInput()) ->then ->object($result) ->isInstanceOf('Hoa\Console\Input') ->isIdenticalTo(SUT::getInput()); } public function case_set_output() { $this ->given($output = new LUT\Output()) ->when($result = SUT::setOutput($output)) ->then ->variable($result) ->isNull() ->object(SUT::getOutput()) ->isIdenticalTo($output); } public function case_get_output() { $this ->when($result = SUT::getOutput()) ->then ->object($result) ->isInstanceOf('Hoa\Console\Output') ->isIdenticalTo(SUT::getOutput()); } public function case_set_tput() { $this ->given($tput = new LUT\Tput('hoa://Library/Console/Terminfo/78/xterm')) ->when($result = SUT::setTput($tput)) ->then ->variable($result) ->isNull() ->object(SUT::getTput()) ->isIdenticalTo($tput); } public function case_get_tput() { $this ->when($result = SUT::getTput()) ->then ->object($result) ->isInstanceOf('Hoa\Console\Tput') ->isIdenticalTo(SUT::getTput()); } public function case_is_tmux_running() { $this ->given($_SERVER['TMUX'] = 'foo') ->when($result = SUT::isTmuxRunning()) ->then ->boolean($result) ->isTrue(); } public function case_is_not_tmux_running() { unset($_SERVER['TMUX']); $this ->when($result = SUT::isTmuxRunning()) ->then ->boolean($result) ->isFalse(); } } when($result = SUT::getInstance()) ->then ->object($result) ->isIdenticalTo(SUT::getInstance()); } public function case_track_button_left() { return $this->_case_track( 7, 42, SUT::BUTTON_LEFT, 'mousedown', [ 'x' => 7, 'y' => 42, 'button' => 'left', 'shift' => false, 'meta' => false, 'ctrl' => false ] ); } public function case_track_button_middle() { return $this->_case_track( 7, 42, SUT::BUTTON_MIDDLE, 'mousedown', [ 'x' => 7, 'y' => 42, 'button' => 'middle', 'shift' => false, 'meta' => false, 'ctrl' => false ] ); } public function case_track_button_right() { return $this->_case_track( 7, 42, SUT::BUTTON_RIGHT, 'mousedown', [ 'x' => 7, 'y' => 42, 'button' => 'right', 'shift' => false, 'meta' => false, 'ctrl' => false ] ); } public function case_track_button_release() { return $this->_case_track( 7, 42, SUT::BUTTON_RELEASE, 'mouseup', [ 'x' => 7, 'y' => 42, 'button' => null, 'shift' => false, 'meta' => false, 'ctrl' => false ] ); } public function case_track_wheelup() { return $this->_case_track( 7, 42, SUT::WHEEL_UP, 'wheelup', [ 'x' => 7, 'y' => 42, 'button' => null, 'shift' => false, 'meta' => false, 'ctrl' => false ] ); } public function case_track_wheeldown() { return $this->_case_track( 7, 42, SUT::WHEEL_DOWN, 'wheeldown', [ 'x' => 7, 'y' => 42, 'button' => null, 'shift' => false, 'meta' => false, 'ctrl' => false ] ); } public function _case_track($x, $y, $pointerActionCode, $listenerName, array $listenerData) { $this ->given( $self = $this, $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll( "\033[M" . chr(($pointerActionCode + 32) & ~28) . chr($x + 32) . chr($y + 32) ), $file->rewind(), $input = LUT::setInput(new LUT\Input($file)), $this->function->stream_select = function () { static $i = 1; if (1 === $i) { return $i--; } return false; }, SUT::getInstance()->on( $listenerName, function (Event\Bucket $bucket) use (&$_listenerData) { $_listenerData = $bucket->getData(); return; } ) ) ->when(SUT::track()) ->then ->output ->isEqualTo( "\033[1;2'z" . "\033[?1000h" . "\033[?1003h" . "\033[?1003l" . "\033[?1000l" ) ->array($_listenerData) ->isEqualTo($listenerData); } public function case_untrack_when_not_tracked() { $this ->when($result = SUT::untrack()) ->then ->variable($result) ->isNull() ->output ->isEmpty(); } } when($result = new SUT()) ->then ->object($result) ->isInstanceOf('Hoa\Stream\IStream\In'); } public function case_eof() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $input = new SUT($file) ) ->when($result = $input->eof()) ->then ->boolean($result) ->isEqualTo($file->eof()); } public function case_read() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('foobar'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->read(3)) ->then ->string($result) ->isEqualTo('foo'); } public function case_read_string() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('foobar'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readString(3)) ->then ->string($result) ->isEqualTo('foo'); } public function case_read_character() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('foobar'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readCharacter(1)) ->then ->string($result) ->isEqualTo('f'); } public function case_read_boolean_true() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('1'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readBoolean()) ->then ->boolean($result) ->isTrue(); } public function case_read_boolean_false() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('0'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readBoolean()) ->then ->boolean($result) ->isFalse(); } public function case_read_integer() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('42'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readInteger(2)) ->then ->integer($result) ->isEqualTo(42); } public function case_read_float() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('4.2'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readFloat(3)) ->then ->float($result) ->isEqualTo(4.2); } public function case_read_array() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('foo bar'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readArray('%s %s')) ->then ->array($result) ->isEqualTo([ 0 => 'foo', 1 => 'bar' ]); } public function case_read_line() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('foo' . "\n" . 'bar'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readLine()) ->then ->string($result) ->isEqualTo('foo' . "\n"); } public function case_read_all() { $this ->given( $content = '4.2foo' . "\n" . 'bar', $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll($content), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->readAll()) ->then ->string($result) ->isEqualTo($content); } public function case_scanf() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll('foo 42' . "\n" . 'bar 153'), $file->rewind(), $input = new SUT($file) ) ->when($result = $input->scanf('%s %d')) ->then ->array($result) ->isEqualTo([ 0 => 'foo', 1 => 42 ]) ->when($result = $input->scanf('%s %d')) ->then ->array($result) ->isEqualTo([ 0 => 'bar', 1 => 153 ]); } } given($_SERVER['TERM'] = 'foo') ->when($result = SUT::getTerm()) ->then ->string($result) ->isEqualTo('foo'); } public function case_get_unknown_term_on_windows() { unset($_SERVER['TERM']); $this ->given($this->constant->OS_WIN = true) ->when($result = SUT::getTerm()) ->then ->string($result) ->isEqualTo('windows-ansi'); } public function case_get_unknown_term() { unset($_SERVER['TERM']); $this ->given($this->constant->OS_WIN = false) ->when($result = SUT::getTerm()) ->then ->string($result) ->isEqualTo('xterm'); } public function case_unknown_file_when_parsing() { $this ->exception(function () { new SUT('/hoa/flatland'); }) ->isInstanceOf('Hoa\Console\Exception'); } public function case_all_informations() { $this ->given($tput = new SUT('hoa://Library/Console/Terminfo/78/xterm')) ->when($result = $tput->getInformations()) ->then ->array($result) ->isIdenticalTo([ 'file' => 'hoa://Library/Console/Terminfo/78/xterm', 'headers' => [ 'data_size' => 3258, 'header_size' => 12, 'magic_number' => 282, 'names_size' => 48, 'bool_count' => 38, 'number_count' => 15, 'string_count' => 413, 'string_table_size' => 1388 ], 'name' => 'xterm', 'description' => 'xterm terminal emulator (X Window System)', 'booleans' => [ 'auto_left_margin' => false, 'auto_right_margin' => true, 'no_esc_ctlc' => false, 'ceol_standout_glitch' => false, 'eat_newline_glitch' => true, 'erase_overstrike' => false, 'generic_type' => false, 'hard_copy' => false, 'meta_key' => true, 'status_line' => false, 'insert_null_glitch' => false, 'memory_above' => false, 'memory_below' => false, 'move_insert_mode' => true, 'move_standout_mode' => true, 'over_strike' => false, 'status_line_esc_ok' => false, 'dest_tabs_magic_smso' => false, 'tilde_glitch' => false, 'transparent_underline' => false, 'xon_xoff' => false, 'needs_xon_xoff' => false, 'prtr_silent' => true, 'hard_cursor' => false, 'non_rev_rmcup' => false, 'no_pad_char' => true, 'non_dest_scroll_region' => false, 'can_change' => false, 'back_color_erase' => true, 'hue_lightness_saturation' => false, 'col_addr_glitch' => false, 'cr_cancels_micro_mode' => false, 'print_wheel' => false, 'row_addr_glitch' => false, 'semi_auto_right_margin' => false, 'cpi_changes_res' => false, 'lpi_changes_res' => false, 'backspaces_with_bs' => true ], 'numbers' => [ 'columns' => 80, 'init_tabs' => 8, 'lines' => 24, 'lines_of_memory' => -1, 'magic_cookie_glitch' => -1, 'padding_baud_rate' => -1, 'virtual_terminal' => -1, 'width_status_line' => -1, 'num_labels' => -1, 'label_height' => -1, 'label_width' => -1, 'max_attributes' => -1, 'maximum_windows' => -1, 'max_colors' => 8, 'max_pairs' => 64 ], 'strings' => [ 'back_tab' => '', 'bell' => '', 'carriage_return' => ' ', 'change_scroll_region' => '[%i%p1%d;%p2%dr', 'clear_all_tabs' => '', 'clear_screen' => '', 'clr_eol' => '', 'clr_eos' => '', 'column_address' => '[%i%p1%dG', 'cursor_address' => '[%i%p1%d;%p2%dH', 'cursor_down' => "\n", 'cursor_home' => '', 'cursor_invisible' => '[?25l', 'cursor_left' => '', 'cursor_normal' => '[?12l[?25h', 'cursor_right' => '', 'cursor_up' => '', 'cursor_visible' => '[?12;25h', 'delete_character' => '', 'delete_line' => '', 'enter_alt_charset_mode' => '(0', 'enter_blink_mode' => '', 'enter_bold_mode' => '', 'enter_ca_mode' => '[?1049h', 'enter_insert_mode' => '', 'enter_secure_mode' => '', 'enter_reverse_mode' => '', 'enter_standout_mode' => '', 'enter_underline_mode' => '', 'erase_chars' => '[%p1%dX', 'exit_alt_charset_mode' => '(B', 'exit_attribute_mode' => '(B', 'exit_ca_mode' => '[?1049l', 'exit_insert_mode' => '', 'exit_standout_mode' => '', 'exit_underline_mode' => '', 'flash_screen' => '[?5h$<100/>[?5l', 'init_2string' => '[!p[?3;4l>', 'insert_line' => '', 'key_backspace' => '', 'key_dc' => '[3~', 'key_down' => 'OB', 'key_f1' => 'OP', 'key_f10' => '[21~', 'key_f2' => 'OQ', 'key_f3' => 'OR', 'key_f4' => 'OS', 'key_f5' => '[15~', 'key_f6' => '[17~', 'key_f7' => '[18~', 'key_f8' => '[19~', 'key_f9' => '[20~', 'key_home' => 'OH', 'key_ic' => '[2~', 'key_left' => 'OD', 'key_npage' => '[6~', 'key_ppage' => '[5~', 'key_right' => 'OC', 'key_sf' => '', 'key_sr' => '', 'key_up' => 'OA', 'keypad_local' => '[?1l>', 'keypad_xmit' => '[?1h=', 'meta_off' => '[?1034l', 'meta_on' => '[?1034h', 'parm_dch' => '[%p1%dP', 'parm_delete_line' => '[%p1%dM', 'parm_down_cursor' => '[%p1%dB', 'parm_ich' => '[%p1%d@', 'parm_index' => '[%p1%dS', 'parm_insert_line' => '[%p1%dL', 'parm_left_cursor' => '[%p1%dD', 'parm_right_cursor' => '[%p1%dC', 'parm_rindex' => '[%p1%dT', 'parm_up_cursor' => '[%p1%dA', 'print_screen' => '', 'prtr_off' => '', 'prtr_on' => '', 'reset_1string' => 'c', 'reset_2string' => '[!p[?3;4l>', 'restore_cursor' => '8', 'row_address' => '[%i%p1%dd', 'save_cursor' => '7', 'scroll_forward' => "\n", 'scroll_reverse' => 'M', 'set_attributes' => '%?%p9%t(0%e(B%;[0%?%p6%t;1%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m', 'set_tab' => 'H', 'tab' => ' ', 'key_b2' => 'OE', 'acs_chars' => '``aaffggiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~', 'key_btab' => '', 'enter_am_mode' => '[?7h', 'exit_am_mode' => '[?7l', 'key_end' => 'OF', 'key_enter' => 'OM', 'key_sdc' => '[3;2~', 'key_send' => '', 'key_shome' => '', 'key_sic' => '[2;2~', 'key_sleft' => '', 'key_snext' => '[6;2~', 'key_sprevious' => '[5;2~', 'key_sright' => '', 'key_f11' => '[23~', 'key_f12' => '[24~', 'key_f13' => '', 'key_f14' => '', 'key_f15' => '', 'key_f16' => '', 'key_f17' => '[15;2~', 'key_f18' => '[17;2~', 'key_f19' => '[18;2~', 'key_f20' => '[19;2~', 'key_f21' => '[20;2~', 'key_f22' => '[21;2~', 'key_f23' => '[23;2~', 'key_f24' => '[24;2~', 'key_f25' => '', 'key_f26' => '', 'key_f27' => '', 'key_f28' => '', 'key_f29' => '[15;5~', 'key_f30' => '[17;5~', 'key_f31' => '[18;5~', 'key_f32' => '[19;5~', 'key_f33' => '[20;5~', 'key_f34' => '[21;5~', 'key_f35' => '[23;5~', 'key_f36' => '[24;5~', 'key_f37' => '', 'key_f38' => '', 'key_f39' => '', 'key_f40' => '', 'key_f41' => '[15;6~', 'key_f42' => '[17;6~', 'key_f43' => '[18;6~', 'key_f44' => '[19;6~', 'key_f45' => '[20;6~', 'key_f46' => '[21;6~', 'key_f47' => '[23;6~', 'key_f48' => '[24;6~', 'key_f49' => '', 'key_f50' => '', 'key_f51' => '', 'key_f52' => '', 'key_f53' => '[15;3~', 'key_f54' => '[17;3~', 'key_f55' => '[18;3~', 'key_f56' => '[19;3~', 'key_f57' => '[20;3~', 'key_f58' => '[21;3~', 'key_f59' => '[23;3~', 'key_f60' => '[24;3~', 'key_f61' => '', 'key_f62' => '', 'key_f63' => '', 'clr_bol' => '', 'user6' => '[%i%d;%dR', 'user7' => '', 'user8' => '[?1;2c', 'user9' => '', 'orig_pair' => '', 'set_foreground' => '[3%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m', 'set_background' => '[4%?%p1%{1}%=%t4%e%p1%{3}%=%t6%e%p1%{4}%=%t1%e%p1%{6}%=%t3%e%p1%d%;m', 'key_mouse' => '', 'set_a_foreground' => '[3%p1%dm', 'set_a_background' => '[4%p1%dm', 'memory_lock' => 'l', 'memory_unlock' => 'm' ] ]); } public function case_has() { $this ->given($tput = new SUT('hoa://Library/Console/Terminfo/78/xterm')) ->when($result = $tput->has('auto_left_margin')) ->then ->boolean($result) ->isFalse() ->when($result = $tput->has('auto_right_margin')) ->then ->boolean($result) ->isTrue(); } public function case_has_unknown_boolean() { $this ->given($tput = new SUT('hoa://Library/Console/Terminfo/78/xterm')) ->when($result = $tput->has('💩')) ->then ->boolean($result) ->isFalse(); } public function case_count() { $this ->given($tput = new SUT('hoa://Library/Console/Terminfo/78/xterm')) ->when($result = $tput->count('columns')) ->then ->integer($result) ->isEqualTo(80); } public function case_count_unknown_integer() { $this ->given($tput = new SUT('hoa://Library/Console/Terminfo/78/xterm')) ->when($result = $tput->count('💩')) ->then ->integer($result) ->isEqualTo(0); } public function case_get() { $this ->given($tput = new SUT('hoa://Library/Console/Terminfo/78/xterm')) ->when($result = $tput->get('cursor_down')) ->then ->string($result) ->isEqualTo("\n"); } public function case_get_unknown_string() { $this ->given($tput = new SUT('hoa://Library/Console/Terminfo/78/xterm')) ->when($result = $tput->get('💩')) ->then ->variable($result) ->isNull(); } } when($result = new SUT()) ->then ->object($result) ->isInstanceOf('Hoa\Stream\IStream\Out'); } public function case_write() { $this ->given($output = new SUT()) ->when($output->write('foobar', 3)) ->then ->output ->isIdenticalTo('foo'); } public function case_write_string() { $this ->given($output = new SUT()) ->when($output->writeString(123)) ->then ->output ->isIdenticalTo('123'); } public function case_write_character() { $this ->given($output = new SUT()) ->when($output->writeCharacter('foo')) ->then ->output ->isIdenticalTo('f'); } public function case_write_boolean_true() { $this ->given($output = new SUT()) ->when($output->writeBoolean(true)) ->then ->output ->isIdenticalTo('1'); } public function case_write_boolean_false() { $this ->given($output = new SUT()) ->when($output->writeBoolean(false)) ->then ->output ->isIdenticalTo('0'); } public function case_write_integer() { $this ->given($output = new SUT()) ->when($output->writeInteger(-42)) ->then ->output ->isIdenticalTo('-42'); } public function case_write_float() { $this ->given($output = new SUT()) ->when($output->writeFloat(-4.2)) ->then ->output ->isIdenticalTo('-4.2'); } public function case_write_array() { $this ->given($output = new SUT()) ->when($output->writeArray(['foo' => 'bar'])) ->then ->output ->isIdenticalTo( 'array (' . "\n" . ' \'foo\' => \'bar\',' . "\n" . ')' ); } public function case_write_line_no_newline() { $this ->given($output = new SUT()) ->when($output->writeLine('foo')) ->then ->output ->isIdenticalTo('foo' . "\n"); } public function case_write_line_with_newline() { $this ->given($output = new SUT()) ->when($output->writeLine('foo' . "\n")) ->then ->output ->isIdenticalTo('foo' . "\n"); } public function case_write_line_with_newlines() { $this ->given($output = new SUT()) ->when($output->writeLine('foo' . "\n" . 'bar' . "\n")) ->then ->output ->isIdenticalTo('foo' . "\n"); } public function case_write_all() { $this ->given($output = new SUT()) ->when($output->writeAll('foobar')) ->then ->output ->isIdenticalTo('foobar'); } public function case_truncate() { $this ->given($output = new SUT()) ->when($result = $output->truncate(42)) ->then ->boolean($result) ->isFalse(); } public function case_default_multiplexer_consideration() { $this ->given($output = new SUT()) ->when($result = $output->isMultiplexerConsidered()) ->then ->boolean($result) ->isFalse(); } public function case_consider_multiplexer() { $this ->given($output = new SUT()) ->when( $output->considerMultiplexer(true), $result = $output->isMultiplexerConsidered() ) ->then ->boolean($result) ->isTrue(); } } when($result = SUT::getInstance()) ->then ->object($result) ->isIdenticalTo(SUT::getInstance()); } public function case_set_size() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setSize(7, 42)) ->then ->output ->isEqualTo("\033[8;42;7t"); } public function case_set_size_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::setSize(7, 42)) ->then ->output ->isEmpty(); } public function case_move_to() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::moveTo(7, 42)) ->then ->output ->isEqualTo("\033[3;7;42t"); } public function case_move_to_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::moveTo(7, 42)) ->then ->output ->isEmpty(); } public function case_get_position() { $this ->given( $this->constant->OS_WIN = false, $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll("\033[3;7;42t"), $file->rewind(), $input = LUT::setInput(new LUT\Input($file)) ) ->when($result = SUT::getPosition()) ->then ->output ->isEqualTo("\033[13t") ->array($result) ->isEqualTo([ 'x' => 7, 'y' => 42 ]); } public function case_get_position_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when($result = SUT::getPosition()) ->then ->variable($result) ->isNull() ->output ->isEmpty(); } public function case_scroll_u() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::scroll('u')) ->then ->output ->isEqualTo("\033[1S"); } public function case_scroll_up() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::scroll('up')) ->then ->output ->isEqualTo("\033[1S"); } public function case_scroll_d() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::scroll('d')) ->then ->output ->isEqualTo("\033[1T"); } public function case_scroll_down() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::scroll('d')) ->then ->output ->isEqualTo("\033[1T"); } public function case_scroll_u_d_up_down() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::scroll('u d up down')) ->then ->output ->isEqualTo("\033[2S\033[2T"); } public function case_scroll_up_repeated() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::scroll('up', 3)) ->then ->output ->isEqualTo("\033[3S"); } public function case_scroll_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::scroll('u')) ->then ->output ->isEmpty(); } public function case_minimize() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::minimize()) ->then ->output ->isEqualTo("\033[2t"); } public function case_minimize_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::minimize()) ->then ->output ->isEmpty(); } public function case_restore() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::restore()) ->then ->output ->isEqualTo("\033[1t"); } public function case_restore_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::restore()) ->then ->output ->isEmpty(); } public function case_raise() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::raise()) ->then ->output ->isEqualTo("\033[5t"); } public function case_raise_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::raise()) ->then ->output ->isEmpty(); } public function case_lower() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::lower()) ->then ->output ->isEqualTo("\033[6t"); } public function case_lower_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::lower()) ->then ->output ->isEmpty(); } public function case_set_title() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setTitle('foobar 😄')) ->then ->output ->isEqualTo("\033]0;foobar 😄\033\\"); } public function case_set_title_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::setTitle('foobar 😄')) ->then ->output ->isEmpty(); } public function case_get_title() { $this ->given( $this->constant->OS_WIN = false, $title = 'hello 🌍', $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll("\033]l" . $title . "\033\\"), $file->rewind(), $input = LUT::setInput(new LUT\Input($file)), $this->function->stream_select = function () { return 1; } ) ->when($result = SUT::getTitle()) ->then ->output ->isEqualTo("\033[21t") ->string($result) ->isEqualTo($title); } public function case_get_title_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when($result = SUT::getTitle()) ->then ->variable($result) ->isNull() ->output ->isEmpty(); } public function case_get_title_timed_out() { $this ->given( $this->function->stream_select = function () { return 0; } ) ->when($result = SUT::getTitle()) ->then ->output ->isEqualTo("\033[21t") ->variable($result) ->isNull(); } public function case_get_label() { $this ->given( $this->constant->OS_WIN = false, $label = 'hello 🌍', $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll("\033]L" . $label . "\033\\"), $file->rewind(), $input = LUT::setInput(new LUT\Input($file)), $this->function->stream_select = function () { return 1; } ) ->when($result = SUT::getLabel()) ->then ->output ->isEqualTo("\033[20t") ->string($result) ->isEqualTo($label); } public function case_get_label_timed_out() { $this ->given( $this->function->stream_select = function () { return 0; } ) ->when($result = SUT::getLabel()) ->then ->output ->isEqualTo("\033[20t") ->variable($result) ->isNull(); } public function case_get_label_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when($result = SUT::getLabel()) ->then ->variable($result) ->isNull() ->output ->isEmpty(); } public function case_refresh() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::refresh()) ->then ->output ->isEqualTo("\033[7t"); } public function case_refresh_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::refresh()) ->then ->output ->isEmpty(); } public function case_copy() { unset($_SERVER['TMUX']); $this ->given($this->constant->OS_WIN = false) ->when(SUT::copy('bla')) ->then ->output ->isEqualTo("\033]52;;" . base64_encode('bla') . "\033\\"); } public function case_copy_on_tmux() { $this ->given( $_SERVER['TMUX'] = 'foo', $this->constant->OS_WIN = false ) ->when(SUT::copy('bla')) ->then ->output ->isEqualTo( "\033Ptmux;" . "\033\033]52;;" . base64_encode('bla') . "\033\033\\" . "\033\\" ); } public function case_copy_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::copy('bla')) ->then ->output ->isEmpty(); } } given( $parser = new LUT\Parser(), $parser->parse(''), $options = new SUT([], $parser) ) ->when($result = $options->getOption($value)) ->then ->boolean($options->isPipetteEmpty()) ->isTrue() ->boolean($result) ->isFalse() ->variable($value) ->isNull(); } public function case_one_entry() { $this ->given( $parser = new LUT\Parser(), $parser->parse('--foo'), $options = new SUT( [ ['foo', SUT::NO_ARGUMENT, 'f'] ], $parser ) ) ->when($result = $options->getOption($value)) ->then ->boolean($options->isPipetteEmpty()) ->isFalse() ->string($result) ->isEqualTo('f') ->boolean($value) ->isTrue() ->when($result = $options->getOption($value)) ->boolean($options->isPipetteEmpty()) ->isFalse() ->boolean($result) ->isFalse() ->variable($value) ->isNull(); } public function case_more_entries() { $this ->given( $parser = new LUT\Parser(), $parser->parse('--foo --bar baz'), $options = new SUT( [ ['foo', SUT::NO_ARGUMENT, 'f'], ['bar', SUT::REQUIRED_ARGUMENT, 'b'] ], $parser ) ) ->when($result = $options->getOption($value)) ->then ->boolean($options->isPipetteEmpty()) ->isFalse() ->string($result) ->isEqualTo('f') ->boolean($value) ->isTrue() ->when($result = $options->getOption($value)) ->then ->boolean($options->isPipetteEmpty()) ->isFalse() ->string($result) ->isEqualTo('b') ->string($value) ->isEqualTo('baz') ->when($result = $options->getOption($value)) ->then ->boolean($options->isPipetteEmpty()) ->isFalse() ->boolean($result) ->isFalse() ->variable($value) ->isNull(); } public function case_ambiguous() { $this ->given( $parser = new LUT\Parser(), $parser->parse('--baz'), $options = new SUT( [ ['foo', SUT::NO_ARGUMENT, 'f'], ['bar', SUT::REQUIRED_ARGUMENT, 'b'] ], $parser ) ) ->when($result = $options->getOption($value)) ->then ->boolean($options->isPipetteEmpty()) ->isFalse() ->string($result) ->isEqualTo('__ambiguous') ->array($value) ->isEqualTo([ 'solutions' => ['bar'], 'value' => true, 'option' => 'baz' ]); } public function case_resolve_option_ambiguity_no_solution() { $this ->given( $parser = new LUT\Parser(), $parser->parse(''), $options = new SUT([], $parser), $solutions = [ 'solutions' => [], 'value' => true, 'option' => 'baz' ] ) ->exception(function () use ($options, $solutions) { $options->resolveOptionAmbiguity($solutions); }) ->isInstanceOf('Hoa\Console\Exception'); } public function case_resolve_option_ambiguity() { $this ->given( $parser = new LUT\Parser(), $parser->parse('--baz'), $options = new SUT( [ ['bar', SUT::NO_ARGUMENT, 'b'] ], $parser ) ) ->when($result = $options->getOption($value)) ->then ->string($result) ->isEqualTo('__ambiguous') ->array($value) ->isEqualTo([ 'solutions' => ['bar'], 'value' => true, 'option' => 'baz' ]) ->when($result = $options->resolveOptionAmbiguity($value)) ->then ->variable($result) ->isNull() ->when($result = $options->getOption($value)) ->then ->string($result) ->isEqualTo('b') ->boolean($value) ->isEqualTo(true); } } _case( '-a -b -c', ['a' => true, 'b' => true, 'c' => true] ); } public function case_single_dashed_short_options() { return $this->_case( '-abc', ['a' => true, 'b' => true, 'c' => true] ); } public function case_long_options() { return $this->_case( '--foo --bar --b-a-z', ['foo' => true, 'bar' => true, 'b-a-z' => true] ); } public function case_boolean_switches() { return $this->_case( '-a -a --foo --foo --bar --bar --bar', ['a' => false, 'foo' => false, 'bar' => true] ); } public function case_valued_switches_equal_simple() { return $this->_case( '-a=foo --long=bar', ['a' => 'foo', 'long' => 'bar'] ); } public function case_valued_switches_equal_with_escaped_space() { return $this->_case( '-a=fo\ o --long=b\ a\ r', ['a' => 'fo o', 'long' => 'b a r'] ); } public function case_valued_switches_equal_double_quoted() { return $this->_case( '-a="fo\"o" --long="b\"a\'r"', ['a' => 'fo"o', 'long' => 'b"a\'r'] ); } public function case_valued_switches_equal_single_quoted() { return $this->_case( '-a=\'fo\\\'"o\' --long=\'b\\\'a"r\'', ['a' => 'fo\'"o', 'long' => 'b\'a"r'] ); } public function case_valued_switches_space_simple() { return $this->_case( '-a foo --long bar', ['a' => 'foo', 'long' => 'bar'] ); } public function case_valued_switches_space_with_escaped_space() { return $this->_case( '-a fo\ o --long b\ a\ r', ['a' => 'fo o', 'long' => 'b a r'] ); } public function case_valued_switches_space_double_quoted() { return $this->_case( '-a "fo\"o" --long "b\"a\'r"', ['a' => 'fo"o', 'long' => 'b"a\'r'] ); } public function case_valued_switches_space_single_quoted() { return $this->_case( '-a \'fo\\\'"o\' --long \'b\\\'a"r\'', ['a' => 'fo\'"o', 'long' => 'b\'a"r'] ); } public function case_valued_switch_equal_negative_value() { return $this->_case( '-a=-foo --long=-bar', ['a' => '-foo', 'long' => '-bar'] ); } public function case_special_valued_switch() { return $this->_case( '-a f,o,o --long b,a,r', ['a' => 'f,o,o', 'long' => 'b,a,r'] ); } public function case_input_associated_to_a_short_option() { return $this->_case( '-a input', ['a' => 'input'] ); } public function case_double_dashes_input() { return $this->_case( '-a --long bar -- inputA inputB', ['a' => true, 'long' => 'bar'], ['inputA', 'inputB'] ); } public function case_simple_input() { return $this->_case( 'inputA inputB', [], ['inputA', 'inputB'] ); } public function case_valued_switch_followed_by_an_input() { return $this->_case( '-a foo --long bar inputA inputB', ['a' => 'foo', 'long' => 'bar'], ['inputA', 'inputB'] ); } public function case_unordered() { return $this->_case( 'inputA -a foo inputB --long bar inputC', ['a' => 'foo', 'long' => 'bar'], ['inputA', 'inputB', 'inputC'] ); } protected function _case($command, array $switches, array $inputs = []) { $this ->given($parser = new SUT()) ->when($result = $parser->parse($command)) ->then ->variable($result) ->isNull() ->array($parser->getSwitches()) ->isIdenticalTo($switches) ->array($parser->getInputs()) ->isIdenticalTo($inputs); } public function case_state_is_reset() { $this ->given($parser = new SUT()) ->when($result = $parser->parse('--foo=bar baz')) ->then ->variable($result) ->isNull() ->array($parser->getSwitches()) ->isIdenticalTo(['foo' => 'bar']) ->array($parser->getInputs()) ->isIdenticalTo(['baz']) ->when($result = $parser->parse('--bar=baz qux')) ->variable($result) ->isNull() ->array($parser->getSwitches()) ->isIdenticalTo(['bar' => 'baz']) ->array($parser->getInputs()) ->isIdenticalTo(['qux']); } public function case_parse_special_value_list() { $this ->given($parser = new SUT()) ->when($result = $parser->parseSpecialValue('foo,bar,baz')) ->then ->array($result) ->isIdenticalTo([ 'foo', 'bar', 'baz' ]); } public function case_parse_special_value_list_with_keywords() { $this ->given($parser = new SUT()) ->when($result = $parser->parseSpecialValue('foo,bar,QUX', ['QUX' => 'baz'])) ->then ->array($result) ->isIdenticalTo([ 'foo', 'bar', 'baz' ]); } public function case_parse_special_value_list_with_range() { $this ->given($parser = new SUT()) ->when($result = $parser->parseSpecialValue('foo,bar,1:3')) ->then ->array($result) ->isIdenticalTo([ 1, 2, 3, 'foo', 'bar' ]); } public function case_set_long_only() { $this ->given($parser = new SUT()) ->when( $parser->setLongOnly(true), $result = $parser->parse('-abc') ) ->then ->boolean($parser->getLongOnly()) ->isTrue() ->variable($result) ->isNull() ->array($parser->getSwitches()) ->isIdenticalTo([ 'abc' => true, ]) ->array($parser->getInputs()) ->isEmpty(); } } when($result = SUT::STATE_CONTINUE) ->then ->integer($result) ->isEqualTo( LUT\Readline::STATE_CONTINUE | LUT\Readline::STATE_NO_ECHO ); } } given($autocompleter = new SUT([])) ->when($result = $autocompleter->getWordDefinition()) ->then ->string($result) ->isEqualTo('.*'); } public function case_constructor() { $this ->given( $autocompleterA = new \Mock\Hoa\Console\Readline\Autocompleter\Autocompleter(), $autocompleterB = new \Mock\Hoa\Console\Readline\Autocompleter\Autocompleter() ) ->when($result = new SUT([$autocompleterA, $autocompleterB])) ->then ->object($result) ->isInstanceOf('Hoa\Console\Readline\Autocompleter\Autocompleter') ->let($autocompleters = $result->getAutocompleters()) ->object($autocompleters) ->isInstanceOf('ArrayObject') ->integer(count($autocompleters)) ->isEqualTo(2) ->object($autocompleters[0]) ->isIdenticalTo($autocompleterA) ->object($autocompleters[1]) ->isIdenticalTo($autocompleterB); } public function case_complete_no_solution() { $this ->given( $autocompleterA = new \Mock\Hoa\Console\Readline\Autocompleter\Autocompleter(), $autocompleterA->getWordDefinition = function () { return 'aaa'; }, $autocompleterB = new \Mock\Hoa\Console\Readline\Autocompleter\Autocompleter(), $autocompleterB->getWordDefinition = function () { return 'bbb'; }, $autocompleter = new SUT([$autocompleterA, $autocompleterB]), $prefix = 'ccc' ) ->when($result = $autocompleter->complete($prefix)) ->then ->variable($result) ->isNull() ->string($prefix) ->isEqualTo('ccc'); } public function case_complete_one_solution_first_autocompleter() { $self = $this; $this ->given( $autocompleterA = new \Mock\Hoa\Console\Readline\Autocompleter\Autocompleter(), $this->calling($autocompleterA)->getWordDefinition = function () { return 'aaa'; }, $this->calling($autocompleterA)->complete = function ($prefix) use ($self) { $self ->string($prefix) ->isEqualTo('aaa'); return 'AAA'; }, $autocompleterB = new \Mock\Hoa\Console\Readline\Autocompleter\Autocompleter(), $this->calling($autocompleterB)->getWordDefinition = function () { return 'bbb'; }, $this->calling($autocompleterB)->complete = function ($prefix) use ($self) { $self->fail('Bad autocompleter called.'); }, $autocompleter = new SUT([$autocompleterA, $autocompleterB]), $prefix = 'aaa' ) ->when($result = $autocompleter->complete($prefix)) ->then ->string($result) ->isEqualTo('AAA') ->string($prefix) ->isEqualTo('aaa'); } public function case_complete_one_solution_second_autocompleter() { $self = $this; $this ->given( $autocompleterA = new \Mock\Hoa\Console\Readline\Autocompleter\Autocompleter(), $this->calling($autocompleterA)->getWordDefinition = function () { return 'aaa'; }, $this->calling($autocompleterA)->complete = function ($prefix) use ($self) { $self->fail('Bad autocompleter called.'); }, $autocompleterB = new \Mock\Hoa\Console\Readline\Autocompleter\Autocompleter(), $this->calling($autocompleterB)->getWordDefinition = function () { return 'bbb'; }, $this->calling($autocompleterB)->complete = function ($prefix) use ($self) { $self ->string($prefix) ->isEqualTo('bbb'); return 'BBB'; }, $autocompleter = new SUT([$autocompleterA, $autocompleterB]), $prefix = 'bbb' ) ->when($result = $autocompleter->complete($prefix)) ->then ->string($result) ->isEqualTo('BBB') ->string($prefix) ->isEqualTo('bbb'); } } given($words = ['foo', 'bar', 'baz', 'qux']) ->when($result = new SUT($words)) ->then ->object($result) ->isInstanceOf('Hoa\Console\Readline\Autocompleter\Autocompleter') ->array($result->getWords()) ->isEqualTo($words); } public function case_complete_no_solution() { $this ->given( $autocompleter = new SUT(['foo', 'bar']), $prefix = 'q' ) ->when($result = $autocompleter->complete($prefix)) ->then ->variable($result) ->isNull() ->string($prefix) ->isEqualTo('q'); } public function case_complete_one_solution() { $this ->given( $autocompleter = new SUT(['foo', 'bar']), $prefix = 'f' ) ->when($result = $autocompleter->complete($prefix)) ->then ->string($result) ->isEqualTo('foo') ->string($prefix) ->isEqualTo('f'); } public function case_complete_with_smallest_prefix() { $this ->given( $autocompleter = new SUT(['foo', 'bar', 'baz', 'qux']), $prefix = 'b' ) ->when($result = $autocompleter->complete($prefix)) ->then ->array($result) ->isEqualTo(['bar', 'baz']) ->string($prefix) ->isEqualTo('b'); } public function case_complete_with_longer_prefix() { $this ->given( $autocompleter = new SUT(['bara', 'barb', 'baza']), $prefix = 'bar' ) ->when($result = $autocompleter->complete($prefix)) ->then ->array($result) ->isEqualTo(['bara', 'barb']) ->string($prefix) ->isEqualTo('bar'); } public function case_get_word_definition() { $this ->given($autocompleter = new SUT([])) ->when($result = $autocompleter->getWordDefinition()) ->then ->string($result) ->isEqualTo('\b\w+'); } public function case_set_words() { $this ->given( $words = ['foo', 'bar', 'baz', 'qux'], $autocompleter = new SUT([]) ) ->when($result = $autocompleter->setWords($words)) ->then ->array($result) ->isEmpty(); } public function case_get_words() { $this ->given( $words = ['foo', 'bar', 'baz', 'qux'], $autocompleter = new SUT($words) ) ->when($result = $autocompleter->getWords()) ->then ->array($result) ->isEqualTo($words); } } given($autocompleter = new SUT()) ->when($result = $autocompleter->getWordDefinition()) ->then ->string($result) ->isEqualTo('/?[\w\d\\_\-\.]+(/[\w\d\\_\-\.]*)*'); } public function case_constructor() { $this ->given( $root = 'foo', $iteratorFactory = function () { return 42; } ) ->when($result = new SUT($root, $iteratorFactory)) ->then ->object($result) ->isInstanceOf('Hoa\Console\Readline\Autocompleter\Autocompleter') ->string($result->getRoot()) ->isEqualTo($root) ->object($result->getIteratorFactory()) ->isIdenticalTo($iteratorFactory); } public function case_complete_no_solution() { $this ->given( resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/Foo?type=file'), resolve('hoa://Test/Vfs/Root/Bar?type=file'), $autocompleter = new SUT('hoa://Test/Vfs/Root'), $prefix = 'Q' ) ->when($result = $autocompleter->complete($prefix)) ->then ->variable($result) ->isNull() ->string($prefix) ->isEqualTo('Q'); } public function case_complete_one_solution() { $this ->given( resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/Foo?type=file'), resolve('hoa://Test/Vfs/Root/Bar?type=file'), $autocompleter = new SUT('hoa://Test/Vfs/Root'), $prefix = 'F' ) ->when($result = $autocompleter->complete($prefix)) ->then ->string($result) ->isEqualTo('Foo') ->string($prefix) ->isEqualTo('F'); } public function case_complete_with_smallest_prefix() { $this ->given( resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/Foo?type=file'), resolve('hoa://Test/Vfs/Root/Bar?type=file'), resolve('hoa://Test/Vfs/Root/Baz?type=file'), resolve('hoa://Test/Vfs/Root/Qux?type=file'), $autocompleter = new SUT('hoa://Test/Vfs/Root'), $prefix = 'B' ) ->when($result = $autocompleter->complete($prefix)) ->then ->array($result) ->isEqualTo(['Bar', 'Baz']) ->string($prefix) ->isEqualTo('B'); } public function case_complete_with_longer_prefix() { $this ->given( resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/Bara?type=file'), resolve('hoa://Test/Vfs/Root/Barb?type=file'), resolve('hoa://Test/Vfs/Root/Baza?type=file'), $autocompleter = new SUT('hoa://Test/Vfs/Root'), $prefix = 'Bar' ) ->when($result = $autocompleter->complete($prefix)) ->then ->array($result) ->isEqualTo(['Bara', 'Barb']) ->string($prefix) ->isEqualTo('Bar'); } public function case_set_root() { $this ->given($autocompleter = new SUT()) ->when($result = $autocompleter->setRoot('foo')) ->then ->variable($result) ->isNull() ->when($result = $autocompleter->setRoot('bar')) ->then ->string($result) ->isEqualTo('foo'); } public function case_get_root() { $this ->given( $autocompleter = new SUT(), $autocompleter->setRoot('foo') ) ->when($result = $autocompleter->getRoot()) ->then ->string($result) ->isEqualTo('foo'); } public function case_set_iterator_factory() { $this ->given($autocompleter = new SUT()) ->when( $result = $autocompleter->setIteratorFactory(function () { return 42; }) ) ->then ->variable($result) ->isNull() ->when( $result = $autocompleter->setIteratorFactory(function () { return 43; }) ) ->then ->integer($result()) ->isEqualTo(42); } public function case_get_iterator_factory() { $this ->given( $autocompleter = new SUT(), $autocompleter->setIteratorFactory(function () { return 42; }) ) ->when(function () use (&$result, $autocompleter) { $result = $autocompleter->getIteratorFactory(); }) ->then ->integer($result()) ->isEqualTo(42); } public function case_get_default_iterator_factory() { $this ->when(function () use (&$result) { $result = SUT::getDefaultIteratorFactory(); }) ->then ->object($result) ->isInstanceOf('Closure') ->object($result(__DIR__)) ->isInstanceOf('DirectoryIterator'); } } when(SUT::move('u')) ->then ->output ->isEqualTo("\033[1A"); } public function case_move_up() { $this ->when(SUT::move('up')) ->then ->output ->isEqualTo("\033[1A"); } public function case_move_↑() { $this ->when(SUT::move('↑')) ->then ->output ->isEqualTo("\033[1A"); } public function case_move_↑_repeated() { $this ->when(SUT::move('↑', 42)) ->then ->output ->isEqualTo("\033[42A"); } public function case_move_r() { $this ->when(SUT::move('r')) ->then ->output ->isEqualTo("\033[1C"); } public function case_move_right() { $this ->when(SUT::move('right')) ->then ->output ->isEqualTo("\033[1C"); } public function case_move_→() { $this ->when(SUT::move('→')) ->then ->output ->isEqualTo("\033[1C"); } public function case_move_→_repeated() { $this ->when(SUT::move('→', 42)) ->then ->output ->isEqualTo("\033[42C"); } public function case_move_d() { $this ->when(SUT::move('d')) ->then ->output ->isEqualTo("\033[1B"); } public function case_move_down() { $this ->when(SUT::move('down')) ->then ->output ->isEqualTo("\033[1B"); } public function case_move_↓() { $this ->when(SUT::move('↓')) ->then ->output ->isEqualTo("\033[1B"); } public function case_move_↓_repeated() { $this ->when(SUT::move('↓', 42)) ->then ->output ->isEqualTo("\033[42B"); } public function case_move_l() { $this ->when(SUT::move('l')) ->then ->output ->isEqualTo("\033[1D"); } public function case_move_left() { $this ->when(SUT::move('left')) ->then ->output ->isEqualTo("\033[1D"); } public function case_move_←() { $this ->when(SUT::move('←')) ->then ->output ->isEqualTo("\033[1D"); } public function case_move_←_repeated() { $this ->when(SUT::move('←', 42)) ->then ->output ->isEqualTo("\033[42D"); } public function case_move_sequence() { $this ->when(SUT::move('↑ → ↓ ←')) ->then ->output ->isEqualTo("\033[1A\033[1C\033[1B\033[1D"); } public function case_move_to_x_y() { $this ->when(SUT::moveTo(7, 42)) ->then ->output ->isEqualTo("\033[42;7H"); } public function case_move_to_x() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll("\033[42;7R"), $file->rewind(), $input = LUT::setInput(new LUT\Input($file)) ) ->when(SUT::moveTo(153)) ->then ->output ->isEqualTo("\033[6n\033[42;153H"); } public function case_move_to_y() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll("\033[42;7R"), $file->rewind(), $input = LUT::setInput(new LUT\Input($file)) ) ->when(SUT::moveTo(null, 153)) ->then ->output ->isEqualTo("\033[6n\033[153;7H"); } public function case_get_position() { $this ->given( $file = new File\ReadWrite('hoa://Test/Vfs/Input?type=file'), $file->writeAll("\033[42;7R"), $file->rewind(), $input = LUT::setInput(new LUT\Input($file)) ) ->when($result = SUT::getPosition()) ->then ->output ->isEqualTo("\033[6n") ->array($result) ->isEqualTo([ 'x' => 7, 'y' => 42 ]); } public function case_save() { $this ->when(SUT::save()) ->then ->output ->isEqualTo("\0337"); } public function case_restore() { $this ->when(SUT::restore()) ->then ->output ->isEqualTo("\0338"); } public function case_clear_a() { $this ->when(SUT::clear('a')) ->then ->output ->isEqualTo("\033[H\033[2J\033[1;1H"); } public function case_clear_all() { $this ->when(SUT::clear('all')) ->then ->output ->isEqualTo("\033[H\033[2J\033[1;1H"); } public function case_clear_↕() { $this ->when(SUT::clear('↕')) ->then ->output ->isEqualTo("\033[H\033[2J\033[1;1H"); } public function case_clear_u() { $this ->when(SUT::clear('u')) ->then ->output ->isEqualTo("\033[1J"); } public function case_clear_up() { $this ->when(SUT::clear('up')) ->then ->output ->isEqualTo("\033[1J"); } public function case_clear_↑() { $this ->when(SUT::clear('↑')) ->then ->output ->isEqualTo("\033[1J"); } public function case_clear_r() { $this ->when(SUT::clear('r')) ->then ->output ->isEqualTo("\033[K"); } public function case_clear_right() { $this ->when(SUT::clear('right')) ->then ->output ->isEqualTo("\033[K"); } public function case_clear_→() { $this ->when(SUT::clear('→')) ->then ->output ->isEqualTo("\033[K"); } public function case_clear_d() { $this ->when(SUT::clear('d')) ->then ->output ->isEqualTo("\033[J"); } public function case_clear_down() { $this ->when(SUT::clear('down')) ->then ->output ->isEqualTo("\033[J"); } public function case_clear_↓() { $this ->when(SUT::clear('↓')) ->then ->output ->isEqualTo("\033[J"); } public function case_clear_l() { $this ->when(SUT::clear('l')) ->then ->output ->isEqualTo("\033[1K"); } public function case_clear_left() { $this ->when(SUT::clear('left')) ->then ->output ->isEqualTo("\033[1K"); } public function case_clear_←() { $this ->when(SUT::clear('←')) ->then ->output ->isEqualTo("\033[1K"); } public function case_clear_line() { $this ->when(SUT::clear('line')) ->then ->output ->isEqualTo("\r\033[K"); } public function case_clear_↔() { $this ->when(SUT::clear('↔')) ->then ->output ->isEqualTo("\r\033[K"); } public function case_hide() { $this ->when(SUT::hide()) ->then ->output ->isEqualTo("\033[?25l"); } public function case_show() { $this ->when(SUT::show()) ->then ->output ->isEqualTo("\033[?12;25h"); } public function case_colorize_n() { $this ->when(SUT::colorize('n')) ->then ->output ->isEqualTo("\033[0m"); } public function case_colorize_normal() { $this ->when(SUT::colorize('normal')) ->then ->output ->isEqualTo("\033[0m"); } public function case_colorize_normal_repeated() { $this ->when(SUT::colorize('n normal')) ->then ->output ->isEqualTo("\033[0;0m"); } public function case_colorize_b() { $this ->when(SUT::colorize('b')) ->then ->output ->isEqualTo("\033[1m"); } public function case_colorize_bold() { $this ->when(SUT::colorize('bold')) ->then ->output ->isEqualTo("\033[1m"); } public function case_colorize_u() { $this ->when(SUT::colorize('u')) ->then ->output ->isEqualTo("\033[4m"); } public function case_colorize_underlined() { $this ->when(SUT::colorize('underlined')) ->then ->output ->isEqualTo("\033[4m"); } public function case_colorize_bl() { $this ->when(SUT::colorize('bl')) ->then ->output ->isEqualTo("\033[5m"); } public function case_colorize_blink() { $this ->when(SUT::colorize('blink')) ->then ->output ->isEqualTo("\033[5m"); } public function case_colorize_i() { $this ->when(SUT::colorize('i')) ->then ->output ->isEqualTo("\033[7m"); } public function case_colorize_inverse() { $this ->when(SUT::colorize('inverse')) ->then ->output ->isEqualTo("\033[7m"); } public function case_colorize_not_b() { $this ->when(SUT::colorize('!b')) ->then ->output ->isEqualTo("\033[22m"); } public function case_colorize_not_bold() { $this ->when(SUT::colorize('!bold')) ->then ->output ->isEqualTo("\033[22m"); } public function case_colorize_not_u() { $this ->when(SUT::colorize('!u')) ->then ->output ->isEqualTo("\033[24m"); } public function case_colorize_not_underlined() { $this ->when(SUT::colorize('!underlined')) ->then ->output ->isEqualTo("\033[24m"); } public function case_colorize_not_bl() { $this ->when(SUT::colorize('!bl')) ->then ->output ->isEqualTo("\033[25m"); } public function case_colorize_not_blink() { $this ->when(SUT::colorize('!blink')) ->then ->output ->isEqualTo("\033[25m"); } public function case_colorize_not_i() { $this ->when(SUT::colorize('!i')) ->then ->output ->isEqualTo("\033[27m"); } public function case_colorize_not_inverse() { $this ->when(SUT::colorize('!inverse')) ->then ->output ->isEqualTo("\033[27m"); } public function case_colorize_fg_black() { $this ->when(SUT::colorize('fg(black)')) ->then ->output ->isEqualTo("\033[30m"); } public function case_colorize_foreground_black() { $this ->when(SUT::colorize('foreground(black)')) ->then ->output ->isEqualTo("\033[30m"); } public function case_colorize_fg_red() { $this ->when(SUT::colorize('fg(red)')) ->then ->output ->isEqualTo("\033[31m"); } public function case_colorize_fg_green() { $this ->when(SUT::colorize('fg(green)')) ->then ->output ->isEqualTo("\033[32m"); } public function case_colorize_fg_yellow() { $this ->when(SUT::colorize('fg(yellow)')) ->then ->output ->isEqualTo("\033[33m"); } public function case_colorize_fg_blue() { $this ->when(SUT::colorize('fg(blue)')) ->then ->output ->isEqualTo("\033[34m"); } public function case_colorize_fg_magenta() { $this ->when(SUT::colorize('fg(magenta)')) ->then ->output ->isEqualTo("\033[35m"); } public function case_colorize_fg_cyan() { $this ->when(SUT::colorize('fg(cyan)')) ->then ->output ->isEqualTo("\033[36m"); } public function case_colorize_fg_white() { $this ->when(SUT::colorize('fg(white)')) ->then ->output ->isEqualTo("\033[37m"); } public function case_colorize_fg_default() { $this ->when(SUT::colorize('fg(default)')) ->then ->output ->isEqualTo("\033[39m"); } public function case_colorize_bg_black() { $this ->when(SUT::colorize('bg(black)')) ->then ->output ->isEqualTo("\033[40m"); } public function case_colorize_background_black() { $this ->when(SUT::colorize('background(black)')) ->then ->output ->isEqualTo("\033[40m"); } public function case_colorize_bg_red() { $this ->when(SUT::colorize('bg(red)')) ->then ->output ->isEqualTo("\033[41m"); } public function case_colorize_bg_green() { $this ->when(SUT::colorize('bg(green)')) ->then ->output ->isEqualTo("\033[42m"); } public function case_colorize_bg_yellow() { $this ->when(SUT::colorize('bg(yellow)')) ->then ->output ->isEqualTo("\033[43m"); } public function case_colorize_bg_blue() { $this ->when(SUT::colorize('bg(blue)')) ->then ->output ->isEqualTo("\033[44m"); } public function case_colorize_bg_magenta() { $this ->when(SUT::colorize('bg(magenta)')) ->then ->output ->isEqualTo("\033[45m"); } public function case_colorize_bg_cyan() { $this ->when(SUT::colorize('bg(cyan)')) ->then ->output ->isEqualTo("\033[46m"); } public function case_colorize_bg_white() { $this ->when(SUT::colorize('bg(white)')) ->then ->output ->isEqualTo("\033[47m"); } public function case_colorize_bg_default() { $this ->when(SUT::colorize('bg(default)')) ->then ->output ->isEqualTo("\033[49m"); } public function case_colorize_foreground_ff0066() { $this ->when(SUT::colorize('foreground(#ff0066)')) ->then ->output ->isEqualTo("\033[38;5;197m"); } public function case_colorize_background_ff0066() { $this ->when(SUT::colorize('background(#ff0066)')) ->then ->output ->isEqualTo("\033[48;5;197m"); } public function case_colorize_foreground_color_index() { $this ->when(SUT::colorize('foreground(42)')) ->then ->output ->isEqualTo("\033[38;5;42m"); } public function case_change_color() { $this ->when(SUT::changeColor(35, 0xff0066)) ->then ->output ->isEqualTo("\033]4;35;ff0066\033\\"); } public function case_set_style_b() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('b')) ->then ->output ->isEqualTo("\033[1 q"); } public function case_set_style_block() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('block')) ->then ->output ->isEqualTo("\033[1 q"); } public function case_set_style_▋() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('▋')) ->then ->output ->isEqualTo("\033[1 q"); } public function case_set_style_block_no_blink() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('block', false)) ->then ->output ->isEqualTo("\033[2 q"); } public function case_set_style_u() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('u')) ->then ->output ->isEqualTo("\033[2 q"); } public function case_set_style_underline() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('underline')) ->then ->output ->isEqualTo("\033[2 q"); } public function case_set_style__() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('_')) ->then ->output ->isEqualTo("\033[2 q"); } public function case_set_style_underline_no_blink() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('underline', false)) ->then ->output ->isEqualTo("\033[3 q"); } public function case_set_style_v() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('v')) ->then ->output ->isEqualTo("\033[5 q"); } public function case_set_style_vertical() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('vertical')) ->then ->output ->isEqualTo("\033[5 q"); } public function case_set_style_pipe() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('|')) ->then ->output ->isEqualTo("\033[5 q"); } public function case_set_style_vertical_no_blink() { $this ->given($this->constant->OS_WIN = false) ->when(SUT::setStyle('vertical', false)) ->then ->output ->isEqualTo("\033[6 q"); } public function case_set_style_on_windows() { $this ->given($this->constant->OS_WIN = true) ->when(SUT::setStyle('b')) ->then ->output ->isEmpty(); } public function case_bip() { $this ->when(SUT::bip()) ->then ->output ->isEqualTo("\007"); } } setListener( new Event\Listener( $this, [ 'mouseup', 'mousedown', 'wheelup', 'wheeldown', ] ) ); return; } public static function getInstance() { if (null === static::$_instance) { static::$_instance = new static(); } return static::$_instance; } public static function track() { if (true === static::$_enabled) { return; } static::$_enabled = true; Console::getOutput()->writeAll( "\033[1;2'z" . "\033[?1000h" . "\033[?1003h" ); $instance = static::getInstance(); $bucket = [ 'x' => 0, 'y' => 0, 'button' => null, 'shift' => false, 'meta' => false, 'ctrl' => false ]; $input = Console::getInput(); $read = [$input->getStream()->getStream()]; while (true) { if (false === @stream_select($read, $write, $except, 30)) { static::untrack(); break; } $string = $input->readCharacter(); if ("\033" !== $string) { continue; } $char = $input->readCharacter(); if ('[' !== $char) { continue; } $char = $input->readCharacter(); if ('M' !== $char) { continue; } $data = $input->read(3); $cb = ord($data[0]); $cx = ord($data[1]) - 32; $cy = ord($data[2]) - 32; $bucket['x'] = $cx; $bucket['y'] = $cy; $bucket['shift'] = 0 !== ($cb & 4); $bucket['meta'] = 0 !== ($cb & 8); $bucket['ctrl'] = 0 !== ($cb & 16); $cb = ($cb | 28) ^ 28; $cb -= 32; switch ($cb) { case static::WHEEL_UP: $instance->getListener()->fire( 'wheelup', new Event\Bucket($bucket) ); break; case static::WHEEL_DOWN: $instance->getListener()->fire( 'wheeldown', new Event\Bucket($bucket) ); break; case static::BUTTON_RELEASE: $instance->getListener()->fire( 'mouseup', new Event\Bucket($bucket) ); $bucket['button'] = null; break; default: if (static::BUTTON_LEFT === $cb) { $bucket['button'] = 'left'; } elseif (static::BUTTON_MIDDLE === $cb) { $bucket['button'] = 'middle'; } elseif (static::BUTTON_RIGHT === $cb) { $bucket['button'] = 'right'; } else { continue 2; } $instance->getListener()->fire( 'mousedown', new Event\Bucket($bucket) ); } } return; } public static function untrack() { if (false === static::$_enabled) { return; } Console::getOutput()->writeAll( "\033[?1003l" . "\033[?1000l" ); static::$_enabled = false; return; } } Console::advancedInteraction(); Consistency::registerShutdownFunction(xcallable('Hoa\Console\Mouse::untrack')); getOption($v)) { switch ($c) { case 't': echo $tput->getTerm(); return; case 'f': echo $tput->getTerminfo(); return; case 'H': echo $tput->has($v) ? 1 : 0; return; case 'c': echo $tput->count($v); return; case 'g': echo $tput->get($v); return; case 'b': $informations = $tput->getInformations(); static::format($informations['booleans']); return; case 'n': $informations = $tput->getInformations(); static::format($informations['numbers']); return; case 's': $informations = $tput->getInformations(); static::format($informations['strings']); return; case '__ambiguous': $this->resolveOptionAmbiguity($v); break; case 'h': case '?': default: return $this->usage(); } } return $this->usage(); } public function usage() { echo 'Usage : console:termcap', "\n", 'Options :', "\n", $this->makeUsageOptionsList([ 't' => 'Get terminal name.', 'f' => 'Get path to the terminfo file.', 'H' => 'Get value of a boolean capability.', 'c' => 'Get value of a number capability.', 'g' => 'Get value of a string capability.', 'b' => 'Get all boolean capabilites.', 'n' => 'Get all number capabilites.', 's' => 'Get all string capabilites.', 'help' => 'This help.' ]), "\n", 'Examples:', "\n", ' $ hoa console:termcap --count max_colors', "\n", ' $ TERM=vt200 hoa console:termcap --has back_color_erase', "\n"; return; } public static function format(array $data) { $max = 0; foreach ($data as $key => $_) { if ($max < ($handle = strlen($key))) { $max = $handle; } } $format = '%-' . ($max + 1) . 's: %s' . "\n"; foreach ($data as $key => $value) { printf( $format, $key, is_bool($value) ? ($value ? 'true' : 'false') : (is_string($value) ? str_replace( [ "\033", "\n", ord(0xa), "\r", "\b", "\f", "\0" ], [ '\e', '\n', '\l', '\r', '\b', '\f', '\0' ], $value ) : $value) ); } return; } } __halt_compiler(); Terminal capabilities. _input = $input; return; } public function getStream() { return $this->_input; } public function eof() { return $this->_input->eof(); } public function read($length) { return $this->_input->read($length); } public function readString($length) { return $this->_input->readString($length); } public function readCharacter() { return $this->_input->readCharacter(); } public function readBoolean() { return $this->_input->readBoolean(); } public function readInteger($length = 1) { return $this->_input->readInteger($length); } public function readFloat($length = 1) { return $this->_input->readFloat($length); } public function readArray($argument = null) { return $this->_input->readArray($argument); } public function readLine() { return $this->_input->readLine(); } public function readAll($offset = 0) { return $this->_input->readAll($offset); } public function scanf($format) { return $this->_input->scanf($format); } } parse($terminfo); return; } protected function parse($terminfo) { if (!file_exists($terminfo)) { throw new Exception( 'Terminfo file %s does not exist.', 0, $terminfo ); } $data = file_get_contents($terminfo); $length = strlen($data); $out = ['file' => $terminfo]; $headers = [ 'data_size' => $length, 'header_size' => 12, 'magic_number' => (ord($data[ 1]) << 8) | ord($data[ 0]), 'names_size' => (ord($data[ 3]) << 8) | ord($data[ 2]), 'bool_count' => (ord($data[ 5]) << 8) | ord($data[ 4]), 'number_count' => (ord($data[ 7]) << 8) | ord($data[ 6]), 'string_count' => (ord($data[ 9]) << 8) | ord($data[ 8]), 'string_table_size' => (ord($data[11]) << 8) | ord($data[10]), ]; $out['headers'] = $headers; $i = $headers['header_size']; $nameAndDescription = explode('|', substr($data, $i, $headers['names_size'] - 1)); $out['name'] = $nameAndDescription[0]; $out['description'] = $nameAndDescription[1]; $i += $headers['names_size']; $booleans = []; $booleanNames = &static::$_booleans; for ( $e = 0, $max = $i + $headers['bool_count']; $i < $max; ++$e, ++$i ) { $booleans[$booleanNames[$e]] = 1 === ord($data[$i]); } $out['booleans'] = $booleans; if (1 === ($i % 2)) { ++$i; } $numbers = []; $numberNames = &static::$_numbers; for ( $e = 0, $max = $i + $headers['number_count'] * 2; $i < $max; ++$e, $i += 2 ) { $name = $numberNames[$e]; $data_i0 = ord($data[$i ]); $data_i1 = ord($data[$i + 1]); if ($data_i1 === 255 && $data_i0 === 255) { $numbers[$name] = -1; } else { $numbers[$name] = ($data_i1 << 8) | $data_i0; } } $out['numbers'] = $numbers; $strings = []; $stringNames = &static::$_strings; $ii = $i + $headers['string_count'] * 2; for ( $e = 0, $max = $ii; $i < $max; ++$e, $i += 2 ) { $name = $stringNames[$e]; $data_i0 = ord($data[$i ]); $data_i1 = ord($data[$i + 1]); if ($data_i1 === 255 && $data_i0 === 255) { continue; } $a = ($data_i1 << 8) | $data_i0; $strings[$name] = $a; if (65534 === $a) { continue; } $b = $ii + $a; $c = $b; while ($c < $length && ord($data[$c])) { $c++; } $value = substr($data, $b, $c - $b); $strings[$name] = false !== $value ? $value : null; } $out['strings'] = $strings; return $this->_informations = $out; } public function getInformations() { return $this->_informations; } public function has($boolean) { if (!isset($this->_informations['booleans'][$boolean])) { return false; } return $this->_informations['booleans'][$boolean]; } public function count($number) { if (!isset($this->_informations['numbers'][$number])) { return 0; } return $this->_informations['numbers'][$number]; } public function get($string) { if (!isset($this->_informations['strings'][$string])) { return null; } return $this->_informations['strings'][$string]; } public static function getTerm() { return isset($_SERVER['TERM']) && !empty($_SERVER['TERM']) ? $_SERVER['TERM'] : (OS_WIN ? 'windows-ansi' : 'xterm'); } public static function getTerminfo($term = null) { $paths = []; if (isset($_SERVER['TERMINFO'])) { $paths[] = $_SERVER['TERMINFO']; } if (isset($_SERVER['HOME'])) { $paths[] = $_SERVER['HOME'] . DS . '.terminfo'; } if (isset($_SERVER['TERMINFO_DIRS'])) { foreach (explode(':', $_SERVER['TERMINFO_DIRS']) as $path) { $paths[] = $path; } } $paths[] = '/usr/share/terminfo'; $paths[] = '/usr/share/lib/terminfo'; $paths[] = '/lib/terminfo'; $paths[] = '/usr/lib/terminfo'; $paths[] = '/usr/local/share/terminfo'; $paths[] = '/usr/local/share/lib/terminfo'; $paths[] = '/usr/local/lib/terminfo'; $paths[] = '/usr/local/ncurses/lib/terminfo'; $paths[] = 'hoa://Library/Console/Terminfo'; $term = $term ?: static::getTerm(); $fileHexa = dechex(ord($term[0])) . DS . $term; $fileAlpha = $term[0] . DS . $term; $pathname = null; foreach ($paths as $path) { if (file_exists($_ = $path . DS . $fileHexa) || file_exists($_ = $path . DS . $fileAlpha)) { $pathname = $_; break; } } if (null === $pathname && 'xterm' !== $term) { return static::getTerminfo('xterm'); } return $pathname; } } `tty` < `tty`', false ); } } writeAll($text); Console\Cursor::colorize($attributesAfter); $out = ob_get_contents(); ob_end_clean(); return $out; } public static function columnize( array $line, $alignement = self::ALIGN_LEFT, $horizontalPadding = 2, $verticalPadding = 0, $separator = null ) { if (empty($line)) { return ''; } $separator = explode('|', $separator); $nbColumn = 0; $nbLine = count($line); $xtraWidth = 2 * ($verticalPadding + 2); foreach ($line as $key => &$column) { if (!is_array($column)) { $column = [$column]; } $handle = count($column); $handle > $nbColumn and $nbColumn = $handle; } $xtraWidth += $horizontalPadding * $nbColumn; $columnWidth = array_fill(0, $nbColumn, 0); for ($e = 0; $e < $nbColumn; $e++) { for ($i = 0; $i < $nbLine; $i++) { if (!isset($line[$i][$e])) { continue; } $handle = self::getMaxLineWidth($line[$i][$e]); $handle > $columnWidth[$e] and $columnWidth[$e] = $handle; } } $window = Console\Window::getSize(); $envWindow = $window['x']; while ($envWindow <= ($cWidthSum = $xtraWidth + array_sum($columnWidth))) { $diff = $cWidthSum - $envWindow; $max = max($columnWidth) - $xtraWidth; $newWidth = $max - $diff; $i = array_search(max($columnWidth), $columnWidth); $columnWidth[$i] = $newWidth; foreach ($line as $key => &$c) { if (isset($c[$i])) { $c[$i] = self::wordwrap($c[$i], $newWidth); } } } $columnWidth = array_map( function ($x) use ($horizontalPadding) { return $x + 2 * $horizontalPadding; }, $columnWidth ); $newLine = []; foreach ($line as $key => $plpl) { $i = self::getMaxLineNumber($plpl); while ($i-- >= 0) { $newLine[] = array_fill(0, $nbColumn, null); } } $yek = 0; foreach ($line as $key => $col) { foreach ($col as $kkey => $value) { if (false === strpos($value, "\n")) { $newLine[$yek][$kkey] = $value; continue; } foreach (explode("\n", $value) as $foo => $oof) { $newLine[$yek + $foo][$kkey] = $oof; } } $i = self::getMaxLineNumber($col); $i > 0 and $yek += $i; $yek++; } foreach ($newLine as $key => $col) { foreach ($col as $kkey => $value) { if (isset($separator[$kkey])) { $newLine[$key][$kkey] = $separator[$kkey] . str_replace( "\n", "\n" . $separator[$kkey], $value ); } } } $line = $newLine; unset($newLine); foreach ($line as $key => &$column) { $handle = count($column); if ($nbColumn - $handle > 0) { $column += array_fill($handle, $nbColumn - $handle, null); } } $out = null; $dash = $alignement === self::ALIGN_LEFT ? '-' : ''; foreach ($line as $key => $handle) { $format = null; foreach ($handle as $i => $hand) { if (preg_match_all('#(\\e\[[0-9]+m)#', $hand, $match)) { $a = $columnWidth[$i]; foreach ($match as $m) { $a += strlen($m[1]); } $format .= '%' . $dash . ($a + floor(count($match) / 2)) . 's'; } else { $format .= '%' . $dash . $columnWidth[$i] . 's'; } } $format .= str_repeat("\n", $verticalPadding + 1); array_unshift($handle, $format); $out .= call_user_func_array('sprintf', $handle); } return $out; } public static function align( $text, $alignement = self::ALIGN_LEFT, $width = null ) { if (null === $width) { $window = Console\Window::getSize(); $width = $window['x']; } $out = null; switch ($alignement) { case self::ALIGN_LEFT: $out .= sprintf('%-' . $width . 's', self::wordwrap($text, $width)); break; case self::ALIGN_CENTER: foreach (explode("\n", self::wordwrap($text, $width)) as $key => $value) { $out .= str_repeat(' ', ceil(($width - strlen($value)) / 2)) . $value . "\n"; } break; case self::ALIGN_RIGHT: default: foreach (explode("\n", self::wordwrap($text, $width)) as $key => $value) { $out .= sprintf('%' . $width . 's' . "\n", $value); } break; } return $out; } protected static function getMaxLineWidth($lines) { if (!is_array($lines)) { $lines = [$lines]; } $width = 0; foreach ($lines as $foo => $line) { foreach (explode("\n", $line) as $fooo => $lin) { $lin = preg_replace('#\\e\[[0-9]+m#', '', $lin); strlen($lin) > $width and $width = strlen($lin); } } return $width; } protected static function getMaxLineNumber($lines) { if (!is_array($lines)) { $lines = [$lines]; } $number = 0; foreach ($lines as $foo => $line) { substr_count($line, "\n") > $number and $number = substr_count($line, "\n"); } return $number; } public static function wordwrap($text, $width = null, $break = "\n") { if (null === $width) { $window = Console\Window::getSize(); $width = $window['x']; } return wordwrap($text, $width, $break, true); } public static function underline($text, $pattern = '*') { $text = explode("\n", $text); $card = strlen($pattern); foreach ($text as $key => &$value) { $i = -1; $max = strlen($value); while ($value{++$i} == ' ' && $i < $max); $underline = str_repeat(' ', $i) . str_repeat($pattern, strlen(trim($value)) / $card) . str_repeat(' ', strlen($value) - $i - strlen(trim($value))); $value .= "\n" . $underline; } return implode("\n", $text); } } open(); } $process->writeAll($output); if ($mode & PHP_OUTPUT_HANDLER_FINAL) { $process->close(); } return null; } } _output = $output; return; } public function getStream() { return $this->_output; } public function write($string, $length) { if (0 > $length) { throw new Exception( 'Length must be greater than 0, given %d.', 0, $length ); } $out = substr($string, 0, $length); if (true === $this->isMultiplexerConsidered()) { if (true === Console::isTmuxRunning()) { $out = "\033Ptmux;" . str_replace("\033", "\033\033", $out) . "\033\\"; } $length = strlen($out); } if (null === $this->_output) { echo $out; } else { $this->_output->write($out, $length); } } public function writeString($string) { $string = (string) $string; return $this->write($string, strlen($string)); } public function writeCharacter($character) { return $this->write((string) $character[0], 1); } public function writeBoolean($boolean) { return $this->write(((bool) $boolean) ? '1' : '0', 1); } public function writeInteger($integer) { $integer = (string) (int) $integer; return $this->write($integer, strlen($integer)); } public function writeFloat($float) { $float = (string) (float) $float; return $this->write($float, strlen($float)); } public function writeArray(array $array) { $array = var_export($array, true); return $this->write($array, strlen($array)); } public function writeLine($line) { if (false === $n = strpos($line, "\n")) { return $this->write($line . "\n", strlen($line) + 1); } ++$n; return $this->write(substr($line, 0, $n), $n); } public function writeAll($string) { return $this->write($string, strlen($string)); } public function truncate($size) { return false; } public function considerMultiplexer($consider) { $old = $this->_considerMultiplexer; $this->_considerMultiplexer = $consider; return $old; } public function isMultiplexerConsidered() { return $this->_considerMultiplexer; } } writeAll("\033[8;" . $y . ";" . $x . "t"); return; } public static function getSize() { if (OS_WIN) { $modecon = explode("\n", ltrim(Processus::execute('mode con'))); $_y = trim($modecon[2]); preg_match('#[^:]+:\s*([0-9]+)#', $_y, $matches); $y = (int) $matches[1]; $_x = trim($modecon[3]); preg_match('#[^:]+:\s*([0-9]+)#', $_x, $matches); $x = (int) $matches[1]; return [ 'x' => $x, 'y' => $y ]; } $term = ''; if (isset($_SERVER['TERM'])) { $term = 'TERM="' . $_SERVER['TERM'] . '" '; } $command = $term . 'tput cols && ' . $term . 'tput lines'; $tput = Processus::execute($command, false); if (!empty($tput)) { list($x, $y) = explode("\n", $tput); return [ 'x' => intval($x), 'y' => intval($y) ]; } Console::getOutput()->writeAll("\033[18t"); $input = Console::getInput(); $input->read(4); $x = null; $y = null; $handle = &$y; do { $char = $input->readCharacter(); switch ($char) { case ';': $handle = &$x; break; case 't': break 2; default: if (false === ctype_digit($char)) { break 2; } $handle .= $char; } } while (true); if (null === $x || null === $y) { return [ 'x' => 0, 'y' => 0 ]; } return [ 'x' => (int) $x, 'y' => (int) $y ]; } public static function moveTo($x, $y) { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[3;" . $x . ";" . $y . "t"); return; } public static function getPosition() { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[13t"); $input = Console::getInput(); $input->read(4); $x = null; $y = null; $handle = &$x; do { $char = $input->readCharacter(); switch ($char) { case ';': $handle = &$y; break; case 't': break 2; default: $handle .= $char; } } while (true); return [ 'x' => (int) $x, 'y' => (int) $y ]; } public static function scroll($directions, $repeat = 1) { if (OS_WIN) { return; } if (1 > $repeat) { return; } elseif (1 === $repeat) { $handle = explode(' ', $directions); } else { $handle = explode(' ', $directions, 1); } $tput = Console::getTput(); $count = ['up' => 0, 'down' => 0]; foreach ($handle as $direction) { switch ($direction) { case 'u': case 'up': case '↑': ++$count['up']; break; case 'd': case 'down': case '↓': ++$count['down']; break; } } $output = Console::getOutput(); if (0 < $count['up']) { $output->writeAll( str_replace( '%p1%d', $count['up'] * $repeat, $tput->get('parm_index') ) ); } if (0 < $count['down']) { $output->writeAll( str_replace( '%p1%d', $count['down'] * $repeat, $tput->get('parm_rindex') ) ); } return; } public static function minimize() { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[2t"); return; } public static function restore() { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[1t"); return; } public static function raise() { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[5t"); return; } public static function lower() { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[6t"); return; } public static function setTitle($title) { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033]0;" . $title . "\033\\"); return; } public static function getTitle() { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[21t"); $input = Console::getInput(); $read = [$input->getStream()->getStream()]; $write = []; $except = []; $out = null; if (0 === stream_select($read, $write, $except, 0, 50000)) { return $out; } $input->read(3); do { $char = $input->readCharacter(); if ("\033" === $char) { $chaar = $input->readCharacter(); if ('\\' === $chaar) { break; } $char .= $chaar; } $out .= $char; } while (true); return $out; } public static function getLabel() { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[20t"); $input = Console::getInput(); $read = [$input->getStream()->getStream()]; $write = []; $except = []; $out = null; if (0 === stream_select($read, $write, $except, 0, 50000)) { return $out; } $input->read(3); do { $char = $input->readCharacter(); if ("\033" === $char) { $chaar = $input->readCharacter(); if ('\\' === $chaar) { break; } $char .= $chaar; } $out .= $char; } while (true); return $out; } public static function refresh() { if (OS_WIN) { return; } Console::getOutput()->writeAll("\033[7t"); return; } public static function copy($data) { if (OS_WIN) { return; } $out = "\033]52;;" . base64_encode($data) . "\033\\"; $output = Console::getOutput(); $considerMultiplexer = $output->considerMultiplexer(true); $output->writeAll($out); $output->considerMultiplexer($considerMultiplexer); return; } } Console::advancedInteraction(); if (function_exists('pcntl_signal')) { Window::getInstance(); pcntl_signal( SIGWINCH, function () { static $_window = null; if (null === $_window) { $_window = Window::getInstance(); } Event::notify( 'hoa://Event/Console/Window:resize', $_window, new Event\Bucket([ 'size' => Window::getSize() ]) ); } ); } parser = new Console\Parser(); return; } public function getOption(&$optionValue, $short = null) { if (null === $this->_options && !empty($this->options)) { $this->setOptions($this->options); } if (null === $this->_options) { return false; } return $this->_options->getOption($optionValue, $short); } public function setOptions(array $options) { $old = $this->options; $this->options = $options; $rule = $this->router->getTheRule(); $variables = $rule[Router::RULE_VARIABLES]; if (isset($variables['_tail'])) { $this->parser->parse($variables['_tail']); $this->_options = new Console\GetOption( $this->options, $this->parser ); } return $old; } public function makeUsageOptionsList(array $definitions = []) { $out = []; foreach ($this->options as $i => $options) { $out[] = [ ' -' . $options[Console\GetOption::OPTION_VAL] . ', --' . $options[Console\GetOption::OPTION_NAME] . ($options[Console\GetOption::OPTION_HAS_ARG] === Console\GetOption::REQUIRED_ARGUMENT ? '=' : ($options[Console\GetOption::OPTION_HAS_ARG] === Console\GetOption::OPTIONAL_ARGUMENT ? '[=]' : '')), (isset($definitions[$options[Console\GetOption::OPTION_VAL]]) ? $definitions[$options[Console\GetOption::OPTION_VAL]] : (isset($definitions[$options[0]]) ? $definitions[$options[Console\GetOption::OPTION_NAME]] : null ) ) ]; } return Console\Chrome\Text::columnize( $out, Console\Chrome\Text::ALIGN_LEFT, .5, 0, '|: ' ); } public function resolveOptionAmbiguity(array $solutions) { echo 'You have made a typo in the option ', $solutions['option'], '; it can match the following options: ', "\n", ' • ', implode(";\n • ", $solutions['solutions']), '.', "\n", 'Please, type the right option (empty to choose the first one):', "\n"; $new = $this->readLine('> '); if (empty($new)) { $new = $solutions['solutions'][0]; } $solutions['solutions'] = [$new]; $this->_options->resolveOptionAmbiguity($solutions); return; } public function status($text, $status) { $window = Console\Window::getSize(); $out = ' ' . Console\Chrome\Text::colorize('*', 'foreground(yellow)') . ' ' . $text . str_pad( ' ', $window['x'] - strlen(preg_replace('#' . "\033" . '\[[0-9]+m#', '', $text)) - 8 ) . ($status === true ? '[' . Console\Chrome\Text::colorize('ok', 'foreground(green)') . ']' : '[' . Console\Chrome\Text::colorize('!!', 'foreground(white) background(red)') . ']'); Console::getOutput()->writeAll($out . "\n"); return; } public function readLine($prefix = null) { static $_rl = null; if (null === $_rl) { $_rl = new Console\Readline(); } return $_rl->readLine($prefix); } public function readPassword($prefix = null) { static $_rl = null; if (null === $_rl) { $_rl = new Console\Readline\Password(); } return $_rl->readLine($prefix); } } ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w'] ]; protected $_pipes = null; protected $_seekable = []; public function __construct( $command, array $options = null, array $descriptors = null, $cwd = null, array $environment = null, $timeout = 30 ) { $this->setCommand($command); if (null !== $options) { $this->setOptions($options); } if (null !== $descriptors) { $this->_descriptors = []; foreach ($descriptors as $descriptor => $nature) { if (isset($this->_descriptors[$descriptor])) { throw new Exception( 'Pipe descriptor %d already exists, cannot ' . 'redefine it.', 0, $descriptor ); } $this->_descriptors[$descriptor] = $nature; } } $this->setCwd($cwd ?: getcwd()); if (null !== $environment) { $this->setEnvironment($environment); } $this->setTimeout($timeout); parent::__construct($this->getCommandLine(), null, true); $this->getListener()->addIds(['input', 'output', 'timeout', 'start', 'stop']); return; } protected function &_open($streamName, Stream\Context $context = null) { $out = @proc_open( $streamName, $this->_descriptors, $this->_pipes, $this->getCwd(), $this->getEnvironment() ); if (false === $out) { throw new Exception( 'Something wrong happen when running %s.', 1, $streamName ); } return $out; } protected function _close() { foreach ($this->_pipes as $pipe) { @fclose($pipe); } return @proc_close($this->getStream()); } public function run() { if (false === $this->isOpened()) { $this->open(); } else { $this->_close(); $this->_setStream($this->_open( $this->getStreamName(), $this->getStreamContext() )); } $this->getListener()->fire('start', new Event\Bucket()); $_read = []; $_write = []; $_except = []; foreach ($this->_pipes as $p => $pipe) { switch ($this->_descriptors[$p][1]) { case 'r': stream_set_blocking($pipe, false); $_write[] = $pipe; break; case 'w': case 'a': stream_set_blocking($pipe, true); $_read[] = $pipe; break; } } while (true) { foreach ($_read as $i => $r) { if (false === is_resource($r)) { unset($_read[$i]); } } foreach ($_write as $i => $w) { if (false === is_resource($w)) { unset($_write[$i]); } } foreach ($_except as $i => $e) { if (false === is_resource($e)) { unset($_except[$i]); } } if (empty($_read) && empty($_write) && empty($_except)) { break; } $read = $_read; $write = $_write; $except = $_except; $select = stream_select($read, $write, $except, $this->getTimeout()); if (0 === $select) { $this->getListener()->fire('timeout', new Event\Bucket()); break; } foreach ($read as $i => $_r) { $pipe = array_search($_r, $this->_pipes); $line = $this->readLine($pipe); if (false === $line) { $result = [false]; } else { $result = $this->getListener()->fire( 'output', new Event\Bucket([ 'pipe' => $pipe, 'line' => $line ]) ); } if (true === feof($_r) || in_array(false, $result, true)) { fclose($_r); unset($_read[$i]); break; } } foreach ($write as $j => $_w) { $result = $this->getListener()->fire( 'input', new Event\Bucket([ 'pipe' => array_search($_w, $this->_pipes) ]) ); if (true === feof($_w) || in_array(false, $result, true)) { fclose($_w); unset($_write[$j]); } } if (empty($_read)) { break; } } $this->getListener()->fire('stop', new Event\Bucket()); return; } protected function getPipe($pipe) { if (!isset($this->_pipes[$pipe])) { throw new Exception( 'Pipe descriptor %d does not exist, cannot read from it.', 2, $pipe ); } return $this->_pipes[$pipe]; } protected function isPipeSeekable($pipe) { if (!isset($this->_seekable[$pipe])) { $_pipe = $this->getPipe($pipe); $data = stream_get_meta_data($_pipe); $this->_seekable[$pipe] = $data['seekable']; } return $this->_seekable[$pipe]; } public function eof($pipe = 1) { return feof($this->getPipe($pipe)); } public function read($length, $pipe = 1) { if (0 > $length) { throw new Exception( 'Length must be greater than 0, given %d.', 3, $length ); } return fread($this->getPipe($pipe), $length); } public function readString($length, $pipe = 1) { return $this->read($length, $pipe); } public function readCharacter($pipe = 1) { return fgetc($this->getPipe($pipe)); } public function readBoolean($pipe = 1) { return (bool) $this->read(1, $pipe); } public function readInteger($length = 1, $pipe = 1) { return (int) $this->read($length, $pipe); } public function readFloat($length = 1, $pipe = 1) { return (float) $this->read($length, $pipe); } public function readArray($format = null, $pipe = 1) { return $this->scanf($format, $pipe); } public function readLine($pipe = 1) { return stream_get_line($this->getPipe($pipe), 1 << 15, "\n"); } public function readAll($offset = -1, $pipe = 1) { $_pipe = $this->getPipe($pipe); if (true === $this->isPipeSeekable($pipe)) { $offset += ftell($_pipe); } else { $offset = -1; } return stream_get_contents($_pipe, -1, $offset); } public function scanf($format, $pipe = 1) { return fscanf($this->getPipe($pipe), $format); } public function write($string, $length, $pipe = 0) { if (0 > $length) { throw new Exception( 'Length must be greater than 0, given %d.', 4, $length ); } return fwrite($this->getPipe($pipe), $string, $length); } public function writeString($string, $pipe = 0) { $string = (string) $string; return $this->write($string, strlen($string), $pipe); } public function writeCharacter($char, $pipe = 0) { return $this->write((string) $char[0], 1, $pipe); } public function writeBoolean($boolean, $pipe = 0) { return $this->write((string) (bool) $boolean, 1, $pipe); } public function writeInteger($integer, $pipe = 0) { $integer = (string) (int) $integer; return $this->write($integer, strlen($integer), $pipe); } public function writeFloat($float, $pipe = 0) { $float = (string) (float) $float; return $this->write($float, strlen($float), $pipe); } public function writeArray(array $array, $pipe = 0) { $array = var_export($array, true); return $this->write($array, strlen($array), $pipe); } public function writeLine($line, $pipe = 0) { if (false === $n = strpos($line, "\n")) { return $this->write($line . "\n", strlen($line) + 1, $pipe); } ++$n; return $this->write(substr($line, 0, $n), $n, $pipe); } public function writeAll($string, $pipe = 0) { return $this->write($string, strlen($string), $pipe); } public function truncate($size, $pipe = 0) { return ftruncate($this->getPipe($pipe), $size); } public function getBasename() { return basename($this->getCommand()); } public function getDirname() { return dirname($this->getCommand()); } public function getStatus() { return proc_get_status($this->getStream()); } public function getExitCode() { $handle = $this->getStatus(); return $handle['exitcode']; } public function isSuccessful() { return 0 === $this->getExitCode(); } public function terminate($signal = self::SIGTERM) { return proc_terminate($this->getStream(), $signal); } protected function setCommand($command) { $old = $this->_command; $this->_command = escapeshellcmd($command); return $old; } public function getCommand() { return $this->_command; } protected function setOptions(array $options) { foreach ($options as &$option) { $option = escapeshellarg($option); } $old = $this->_options; $this->_options = $options; return $old; } public function getOptions() { return $this->_options; } public function getCommandLine() { $out = $this->getCommand(); foreach ($this->getOptions() as $key => $value) { if (!is_int($key)) { $out .= ' ' . $key . '=' . $value; } else { $out .= ' ' . $value; } } return $out; } protected function setCwd($cwd) { $old = $this->_cwd; $this->_cwd = $cwd; return $old; } public function getCwd() { return $this->_cwd; } protected function setEnvironment(array $environment) { $old = $this->_environment; $this->_environment = $environment; return $old; } public function getEnvironment() { return $this->_environment; } public function setTimeout($timeout) { $old = $this->_timeout; $this->_timeout = $timeout; return $old; } public function getTimeout() { return $this->_timeout; } public static function setTitle($title) { if (PHP_VERSION_ID < 50500) { return; } cli_set_process_title($title); return; } public static function getTitle() { if (PHP_VERSION_ID < 50500) { return null; } return cli_get_process_title(); } public static function locate($binary) { if (isset($_ENV['PATH'])) { $separator = ':'; $path = &$_ENV['PATH']; } elseif (isset($_SERVER['PATH'])) { $separator = ':'; $path = &$_SERVER['PATH']; } elseif (isset($_SERVER['Path'])) { $separator = ';'; $path = &$_SERVER['Path']; } else { return null; } foreach (explode($separator, $path) as $directory) { if (true === file_exists($out = $directory . DS . $binary)) { return $out; } } return null; } public static function execute($commandLine, $escape = true) { if (true === $escape) { $commandLine = escapeshellcmd($commandLine); } return rtrim(shell_exec($commandLine)); } } _options = $options; $this->_parser = $parser; if (empty($options)) { $this->_pipette[null] = null; return; } $names = []; foreach ($options as $i => $option) { if (isset($option[self::OPTION_NAME])) { $names[$option[self::OPTION_NAME]] = $i; } if (isset($option[self::OPTION_VAL])) { $names[$option[self::OPTION_VAL]] = $i; } } $_names = array_keys($names); $switches = $parser->getSwitches(); foreach ($switches as $name => $value) { if (false === in_array($name, $_names)) { if (1 === strlen($name)) { $this->_pipette[] = ['__ambiguous', [ 'solutions' => [], 'value' => $value, 'option' => $name ]]; continue; } $haystack = implode(';', $_names); $differences = (int) ceil(strlen($name) / 3); $searched = Ustring\Search::approximated( $haystack, $name, $differences ); $solutions = []; foreach ($searched as $s) { $h = substr($haystack, $s['i'], $s['l']); if (false !== strpos($h, ';') || false !== in_array($h, array_keys($switches)) || false === in_array($h, $_names)) { continue; } $solutions[] = $h; } if (empty($solutions)) { continue; } $this->_pipette[] = ['__ambiguous', [ 'solutions' => $solutions, 'value' => $value, 'option' => $name ]]; continue; } $option = $options[$names[$name]]; $argument = $option[self::OPTION_HAS_ARG]; if (self::NO_ARGUMENT === $argument) { if (!is_bool($value)) { $parser->transferSwitchToInput($name, $value); } } elseif (self::REQUIRED_ARGUMENT === $argument && !is_string($value)) { throw new Exception( 'The argument %s requires a value (it is not a switch).', 0, $name ); } $this->_pipette[] = [$option[self::OPTION_VAL], $value]; } $this->_pipette[null] = null; reset($this->_pipette); return; } public function getOption(&$optionValue, $short = null) { static $first = true; $optionValue = null; if (true === $this->isPipetteEmpty() && true === $first) { $first = false; return false; } $k = key($this->_pipette); $c = current($this->_pipette); $key = $c[0]; $value = $c[1]; if (null == $k && null === $c) { reset($this->_pipette); $first = true; return false; } $allow = []; if (null === $short) { foreach ($this->_options as $option) { $allow[] = $option[self::OPTION_VAL]; } } else { $allow = str_split($short); } if (!in_array($key, $allow) && '__ambiguous' != $key) { return false; } $optionValue = $value; $return = $key; next($this->_pipette); return $return; } public function isPipetteEmpty() { return count($this->_pipette) == 1; } public function resolveOptionAmbiguity(array $solutions) { if (!isset($solutions['solutions']) || !isset($solutions['value']) || !isset($solutions['option'])) { throw new Exception( 'Cannot resolve option ambiguity because the given solution ' . 'seems to be corruped.', 1 ); } $choices = $solutions['solutions']; if (1 > count($choices)) { throw new Exception( 'Cannot resolve ambiguity, fix your typo in the option %s :-).', 2, $solutions['option'] ); } $theSolution = $choices[0]; foreach ($this->_options as $option) { if ($theSolution == $option[self::OPTION_NAME] || $theSolution == $option[self::OPTION_VAL]) { $argument = $option[self::OPTION_HAS_ARG]; $value = $solutions['value']; if (self::NO_ARGUMENT === $argument) { if (!is_bool($value)) { $this->_parser->transferSwitchToInput($theSolution, $value); } } elseif (self::REQUIRED_ARGUMENT === $argument && !is_string($value)) { throw new Exception( 'The argument %s requires a value (it is not a switch).', 3, $theSolution ); } unset($this->_pipette[null]); $this->_pipette[] = [$option[self::OPTION_VAL], $value]; $this->_pipette[null] = null; return; } } return; } } _parsed); $this->_parsed = [ 'input' => [], 'switch' => [] ]; $regex = '#(?:(?--?[^=\s]+)(?:(?:(=)|(\s))(?(?(3)[^-]|).*?)(?(4)(?.*?)(?(6)(?addInput($match); } elseif (!isset($match['i']) && !isset($match['s'])) { if (isset($matches[$i + 1])) { $nextMatch = $matches[$i + 1]; if (!empty($nextMatch['i']) && '=' === $nextMatch['i'][0]) { ++$i; $match[2] = '='; $match[3] = $match[4] = null; $match['s'] = $match[5] = substr($nextMatch[7], 1); $this->addValuedSwitch($match); continue; } } $this->addBoolSwitch($match); } elseif (!isset($match['i']) && isset($match['s'])) { $this->addValuedSwitch($match); } } return; } protected function addInput(array $input) { $handle = $input['i']; if (!empty($input[6])) { $handle = str_replace('\\' . $input[6], $input[6], $handle); } else { $handle = str_replace('\\ ', ' ', $handle); } $this->_parsed['input'][] = $handle; return; } protected function addBoolSwitch(array $switch) { $this->addSwitch($switch['b'], true); return; } protected function addValuedSwitch(array $switch) { $this->addSwitch($switch['b'], $switch['s'], $switch[4]); return; } protected function addSwitch($name, $value, $escape = null) { if (substr($name, 0, 2) == '--') { return $this->addSwitch(substr($name, 2), $value, $escape); } if (substr($name, 0, 1) == '-') { if (true === $this->getLongOnly()) { return $this->addSwitch('-' . $name, $value, $escape); } foreach (str_split(substr($name, 1)) as $foo => $switch) { $this->addSwitch($switch, $value, $escape); } return; } if (null !== $escape) { $escape = '' == $escape ? ' ' : $escape; if (is_string($value)) { $value = str_replace('\\' . $escape, $escape, $value); } } elseif (is_string($value)) { $value = str_replace('\\ ', ' ', $value); } if (isset($this->_parsed['switch'][$name])) { if (is_bool($this->_parsed['switch'][$name])) { $value = !$this->_parsed['switch'][$name]; } else { $value = [$this->_parsed['switch'][$name], $value]; } } if (empty($name)) { return $this->addInput([6 => null, 'i' => $value]); } $this->_parsed['switch'][$name] = $value; return; } public function transferSwitchToInput($name, &$value) { if (!isset($this->_parsed['switch'][$name])) { return; } $this->_parsed['input'][] = $this->_parsed['switch'][$name]; $value = true; unset($this->_parsed['switch'][$name]); return; } public function getInputs() { return $this->_parsed['input']; } public function listInputs( &$a, &$b = null, &$c = null, &$d = null, &$e = null, &$f = null, &$g = null, &$h = null, &$i = null, &$j = null, &$k = null, &$l = null, &$m = null, &$n = null, &$o = null, &$p = null, &$q = null, &$r = null, &$s = null, &$t = null, &$u = null, &$v = null, &$w = null, &$x = null, &$y = null, &$z = null ) { $inputs = $this->getInputs(); $i = 'a'; $ii = -1; while (isset($inputs[++$ii]) && $i <= 'z') { ${$i++} = $inputs[$ii]; } return; } public function getSwitches() { return $this->_parsed['switch']; } public function parseSpecialValue($value, array $keywords = []) { $out = []; foreach (explode(',', $value) as $key => $subvalue) { $subvalue = str_replace( array_keys($keywords), array_values($keywords), $subvalue ); if (0 !== preg_match('#^(-?[0-9]+):(-?[0-9]+)$#', $subvalue, $matches)) { if (0 > $matches[1] && 0 > $matches[2]) { throw new Exception( 'Cannot give two negative numbers, given %s.', 0, $subvalue ); } array_shift($matches); $max = max($matches); $min = min($matches); if (0 > $max || 0 > $min) { if (0 > $max - $min) { throw new Exception( 'The difference between operands must be ' . 'positive.', 1 ); } $min = $max + $min; } $out = array_merge(range($min, $max), $out); } else { $out[] = $subvalue; } } return $out; } public function setLongOnly($longonly = false) { $old = $this->_longonly; $this->_longonly = $longonly; return $old; } public function getLongOnly() { return $this->_longonly; } } _mapping["\033[A"] = xcallable($this, '_bindArrowUp'); $this->_mapping["\033[B"] = xcallable($this, '_bindArrowDown'); $this->_mapping["\033[C"] = xcallable($this, '_bindArrowRight'); $this->_mapping["\033[D"] = xcallable($this, '_bindArrowLeft'); $this->_mapping["\001"] = xcallable($this, '_bindControlA'); $this->_mapping["\002"] = xcallable($this, '_bindControlB'); $this->_mapping["\005"] = xcallable($this, '_bindControlE'); $this->_mapping["\006"] = xcallable($this, '_bindControlF'); $this->_mapping["\010"] = $this->_mapping["\177"] = xcallable($this, '_bindBackspace'); $this->_mapping["\027"] = xcallable($this, '_bindControlW'); $this->_mapping["\n"] = xcallable($this, '_bindNewline'); $this->_mapping["\t"] = xcallable($this, '_bindTab'); return; } public function readLine($prefix = null) { $input = Console::getInput(); if (true === $input->eof()) { return false; } $direct = Console::isDirect($input->getStream()->getStream()); $output = Console::getOutput(); if (false === $direct || OS_WIN) { $out = $input->readLine(); if (false === $out) { return false; } $out = substr($out, 0, -1); if (true === $direct) { $output->writeAll($prefix); } else { $output->writeAll($prefix . $out . "\n"); } return $out; } $this->resetLine(); $this->setPrefix($prefix); $read = [$input->getStream()->getStream()]; $output->writeAll($prefix); while (true) { @stream_select($read, $write, $except, 30, 0); if (empty($read)) { $read = [$input->getStream()->getStream()]; continue; } $char = $this->_read(); $this->_buffer = $char; $return = $this->_readLine($char); if (0 === ($return & self::STATE_NO_ECHO)) { $output->writeAll($this->_buffer); } if (0 !== ($return & self::STATE_BREAK)) { break; } } return $this->getLine(); } public function _readLine($char) { if (isset($this->_mapping[$char]) && is_callable($this->_mapping[$char])) { $mapping = $this->_mapping[$char]; return $mapping($this); } if (isset($this->_mapping[$char])) { $this->_buffer = $this->_mapping[$char]; } elseif (false === Ustring::isCharPrintable($char)) { Console\Cursor::bip(); return static::STATE_CONTINUE | static::STATE_NO_ECHO; } if ($this->getLineLength() == $this->getLineCurrent()) { $this->appendLine($this->_buffer); return static::STATE_CONTINUE; } $this->insertLine($this->_buffer); $tail = mb_substr( $this->getLine(), $this->getLineCurrent() - 1 ); $this->_buffer = "\033[K" . $tail . str_repeat( "\033[D", mb_strlen($tail) - 1 ); return static::STATE_CONTINUE; } public function addMappings(array $mappings) { foreach ($mappings as $key => $mapping) { $this->addMapping($key, $mapping); } return; } public function addMapping($key, $mapping) { if ('\e[' === substr($key, 0, 3)) { $this->_mapping["\033[" . substr($key, 3)] = $mapping; } elseif ('\C-' === substr($key, 0, 3)) { $_key = ord(strtolower(substr($key, 3))) - 96; $this->_mapping[chr($_key)] = $mapping; } else { $this->_mapping[$key] = $mapping; } return; } public function addHistory($line = null) { if (empty($line)) { return; } $this->_history[] = $line; $this->_historyCurrent = $this->_historySize++; return; } public function clearHistory() { unset($this->_history); $this->_history = []; $this->_historyCurrent = 0; $this->_historySize = 1; return; } public function getHistory($i = null) { if (null === $i) { $i = $this->_historyCurrent; } if (!isset($this->_history[$i])) { return null; } return $this->_history[$i]; } public function previousHistory() { if (0 >= $this->_historyCurrent) { return $this->getHistory(0); } return $this->getHistory($this->_historyCurrent--); } public function nextHistory() { if ($this->_historyCurrent + 1 >= $this->_historySize) { return $this->getLine(); } return $this->getHistory(++$this->_historyCurrent); } public function getLine() { return $this->_line; } public function appendLine($append) { $this->_line .= $append; $this->_lineLength = mb_strlen($this->_line); $this->_lineCurrent = $this->_lineLength; return; } public function insertLine($insert) { if ($this->_lineLength == $this->_lineCurrent) { return $this->appendLine($insert); } $this->_line = mb_substr($this->_line, 0, $this->_lineCurrent) . $insert . mb_substr($this->_line, $this->_lineCurrent); $this->_lineLength = mb_strlen($this->_line); $this->_lineCurrent += mb_strlen($insert); return; } protected function resetLine() { $this->_line = null; $this->_lineCurrent = 0; $this->_lineLength = 0; return; } public function getLineCurrent() { return $this->_lineCurrent; } public function getLineLength() { return $this->_lineLength; } public function setPrefix($prefix) { $this->_prefix = $prefix; return; } public function getPrefix() { return $this->_prefix; } public function getBuffer() { return $this->_buffer; } public function setAutocompleter(Autocompleter $autocompleter) { $old = $this->_autocompleter; $this->_autocompleter = $autocompleter; return $old; } public function getAutocompleter() { return $this->_autocompleter; } public function _read($length = 512) { return Console::getInput()->read($length); } public function setLine($line) { $this->_line = $line; $this->_lineLength = mb_strlen($this->_line); $this->_lineCurrent = $this->_lineLength; return; } public function setLineCurrent($current) { $this->_lineCurrent = $current; return; } public function setLineLength($length) { $this->_lineLength = $length; return; } public function setBuffer($buffer) { $this->_buffer = $buffer; return; } public function _bindArrowUp(Readline $self) { if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) { Console\Cursor::clear('↔'); Console::getOutput()->writeAll($self->getPrefix()); } $self->setBuffer($buffer = $self->previousHistory()); $self->setLine($buffer); return static::STATE_CONTINUE; } public function _bindArrowDown(Readline $self) { if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) { Console\Cursor::clear('↔'); Console::getOutput()->writeAll($self->getPrefix()); } $self->setBuffer($buffer = $self->nextHistory()); $self->setLine($buffer); return static::STATE_CONTINUE; } public function _bindArrowRight(Readline $self) { if ($self->getLineLength() > $self->getLineCurrent()) { if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) { Console\Cursor::move('→'); } $self->setLineCurrent($self->getLineCurrent() + 1); } $self->setBuffer(null); return static::STATE_CONTINUE; } public function _bindArrowLeft(Readline $self) { if (0 < $self->getLineCurrent()) { if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) { Console\Cursor::move('←'); } $self->setLineCurrent($self->getLineCurrent() - 1); } $self->setBuffer(null); return static::STATE_CONTINUE; } public function _bindBackspace(Readline $self) { $buffer = null; if (0 < $self->getLineCurrent()) { if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) { Console\Cursor::move('←'); Console\Cursor::clear('→'); } if ($self->getLineLength() == $current = $self->getLineCurrent()) { $self->setLine(mb_substr($self->getLine(), 0, -1)); } else { $line = $self->getLine(); $current = $self->getLineCurrent(); $tail = mb_substr($line, $current); $buffer = $tail . str_repeat("\033[D", mb_strlen($tail)); $self->setLine(mb_substr($line, 0, $current - 1) . $tail); $self->setLineCurrent($current - 1); } } $self->setBuffer($buffer); return static::STATE_CONTINUE; } public function _bindControlA(Readline $self) { for ($i = $self->getLineCurrent() - 1; 0 <= $i; --$i) { $self->_bindArrowLeft($self); } return static::STATE_CONTINUE; } public function _bindControlB(Readline $self) { $current = $self->getLineCurrent(); if (0 === $current) { return static::STATE_CONTINUE; } $words = preg_split( '#\b#u', $self->getLine(), -1, PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_NO_EMPTY ); for ( $i = 0, $max = count($words) - 1; $i < $max && $words[$i + 1][1] < $current; ++$i ); for ($j = $words[$i][1] + 1; $current >= $j; ++$j) { $self->_bindArrowLeft($self); } return static::STATE_CONTINUE; } public function _bindControlE(Readline $self) { for ( $i = $self->getLineCurrent(), $max = $self->getLineLength(); $i < $max; ++$i ) { $self->_bindArrowRight($self); } return static::STATE_CONTINUE; } public function _bindControlF(Readline $self) { $current = $self->getLineCurrent(); if ($self->getLineLength() === $current) { return static::STATE_CONTINUE; } $words = preg_split( '#\b#u', $self->getLine(), -1, PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_NO_EMPTY ); for ( $i = 0, $max = count($words) - 1; $i < $max && $words[$i][1] < $current; ++$i ); if (!isset($words[$i + 1])) { $words[$i + 1] = [1 => $self->getLineLength()]; } for ($j = $words[$i + 1][1]; $j > $current; --$j) { $self->_bindArrowRight($self); } return static::STATE_CONTINUE; } public function _bindControlW(Readline $self) { $current = $self->getLineCurrent(); if (0 === $current) { return static::STATE_CONTINUE; } $words = preg_split( '#\b#u', $self->getLine(), -1, PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_NO_EMPTY ); for ( $i = 0, $max = count($words) - 1; $i < $max && $words[$i + 1][1] < $current; ++$i ); for ($j = $words[$i][1] + 1; $current >= $j; ++$j) { $self->_bindBackspace($self); } return static::STATE_CONTINUE; } public function _bindNewline(Readline $self) { $self->addHistory($self->getLine()); return static::STATE_BREAK; } public function _bindTab(Readline $self) { $output = Console::getOutput(); $autocompleter = $self->getAutocompleter(); $state = static::STATE_CONTINUE | static::STATE_NO_ECHO; if (null === $autocompleter) { return $state; } $current = $self->getLineCurrent(); $line = $self->getLine(); if (0 === $current) { return $state; } $matches = preg_match_all( '#' . $autocompleter->getWordDefinition() . '$#u', mb_substr($line, 0, $current), $words ); if (0 === $matches) { return $state; } $word = $words[0][0]; if ('' === trim($word)) { return $state; } $solution = $autocompleter->complete($word); $length = mb_strlen($word); if (null === $solution) { return $state; } if (is_array($solution)) { $_solution = $solution; $count = count($_solution) - 1; $cWidth = 0; $window = Console\Window::getSize(); $wWidth = $window['x']; $cursor = Console\Cursor::getPosition(); array_walk($_solution, function (&$value) use (&$cWidth) { $handle = mb_strlen($value); if ($handle > $cWidth) { $cWidth = $handle; } return; }); array_walk($_solution, function (&$value) use (&$cWidth) { $handle = mb_strlen($value); if ($handle >= $cWidth) { return; } $value .= str_repeat(' ', $cWidth - $handle); return; }); $mColumns = (int) floor($wWidth / ($cWidth + 2)); $mLines = (int) ceil(($count + 1) / $mColumns); --$mColumns; $i = 0; if (0 > $window['y'] - $cursor['y'] - $mLines) { Console\Window::scroll('↑', $mLines); Console\Cursor::move('↑', $mLines); } Console\Cursor::save(); Console\Cursor::hide(); Console\Cursor::move('↓ LEFT'); Console\Cursor::clear('↓'); foreach ($_solution as $j => $s) { $output->writeAll("\033[0m" . $s . "\033[0m"); if ($i++ < $mColumns) { $output->writeAll(' '); } else { $i = 0; if (isset($_solution[$j + 1])) { $output->writeAll("\n"); } } } Console\Cursor::restore(); Console\Cursor::show(); ++$mColumns; $input = Console::getInput(); $read = [$input->getStream()->getStream()]; $mColumn = -1; $mLine = -1; $coord = -1; $unselect = function () use ( &$mColumn, &$mLine, &$coord, &$_solution, &$cWidth, $output ) { Console\Cursor::save(); Console\Cursor::hide(); Console\Cursor::move('↓ LEFT'); Console\Cursor::move('→', $mColumn * ($cWidth + 2)); Console\Cursor::move('↓', $mLine); $output->writeAll("\033[0m" . $_solution[$coord] . "\033[0m"); Console\Cursor::restore(); Console\Cursor::show(); return; }; $select = function () use ( &$mColumn, &$mLine, &$coord, &$_solution, &$cWidth, $output ) { Console\Cursor::save(); Console\Cursor::hide(); Console\Cursor::move('↓ LEFT'); Console\Cursor::move('→', $mColumn * ($cWidth + 2)); Console\Cursor::move('↓', $mLine); $output->writeAll("\033[7m" . $_solution[$coord] . "\033[0m"); Console\Cursor::restore(); Console\Cursor::show(); return; }; $init = function () use ( &$mColumn, &$mLine, &$coord, &$select ) { $mColumn = 0; $mLine = 0; $coord = 0; $select(); return; }; while (true) { @stream_select($read, $write, $except, 30, 0); if (empty($read)) { $read = [$input->getStream()->getStream()]; continue; } switch ($char = $self->_read()) { case "\033[A": if (-1 === $mColumn && -1 === $mLine) { $init(); break; } $unselect(); $coord = max(0, $coord - $mColumns); $mLine = (int) floor($coord / $mColumns); $mColumn = $coord % $mColumns; $select(); break; case "\033[B": if (-1 === $mColumn && -1 === $mLine) { $init(); break; } $unselect(); $coord = min($count, $coord + $mColumns); $mLine = (int) floor($coord / $mColumns); $mColumn = $coord % $mColumns; $select(); break; case "\t": case "\033[C": if (-1 === $mColumn && -1 === $mLine) { $init(); break; } $unselect(); $coord = min($count, $coord + 1); $mLine = (int) floor($coord / $mColumns); $mColumn = $coord % $mColumns; $select(); break; case "\033[D": if (-1 === $mColumn && -1 === $mLine) { $init(); break; } $unselect(); $coord = max(0, $coord - 1); $mLine = (int) floor($coord / $mColumns); $mColumn = $coord % $mColumns; $select(); break; case "\n": if (-1 !== $mColumn && -1 !== $mLine) { $tail = mb_substr($line, $current); $current -= $length; $self->setLine( mb_substr($line, 0, $current) . $solution[$coord] . $tail ); $self->setLineCurrent( $current + mb_strlen($solution[$coord]) ); Console\Cursor::move('←', $length); $output->writeAll($solution[$coord]); Console\Cursor::clear('→'); $output->writeAll($tail); Console\Cursor::move('←', mb_strlen($tail)); } default: $mColumn = -1; $mLine = -1; $coord = -1; Console\Cursor::save(); Console\Cursor::move('↓ LEFT'); Console\Cursor::clear('↓'); Console\Cursor::restore(); if ("\033" !== $char && "\n" !== $char) { $self->setBuffer($char); return $self->_readLine($char); } break 2; } } return $state; } $tail = mb_substr($line, $current); $current -= $length; $self->setLine( mb_substr($line, 0, $current) . $solution . $tail ); $self->setLineCurrent( $current + mb_strlen($solution) ); Console\Cursor::move('←', $length); $output->writeAll($solution); Console\Cursor::clear('→'); $output->writeAll($tail); Console\Cursor::move('←', mb_strlen($tail)); return $state; } } Console::advancedInteraction(); Consistency::flexEntity('Hoa\Console\Readline\Readline'); setAutocompleters($autocompleters); return; } public function complete(&$prefix) { foreach ($this->getAutocompleters() as $autocompleter) { $preg = preg_match( '#(' . $autocompleter->getWordDefinition() . ')$#u', $prefix, $match ); if (0 === $preg) { continue; } $_prefix = $match[0]; if (null === $out = $autocompleter->complete($_prefix)) { continue; } $prefix = $_prefix; return $out; } return null; } protected function setAutocompleters(array $autocompleters) { $old = $this->_autocompleters; $this->_autocompleters = new \ArrayObject($autocompleters); return $old; } public function getAutocompleters() { return $this->_autocompleters; } public function getWordDefinition() { return '.*'; } } setWords($words); return; } public function complete(&$prefix) { $out = []; $length = mb_strlen($prefix); foreach ($this->getWords() as $word) { if (mb_substr($word, 0, $length) === $prefix) { $out[] = $word; } } if (empty($out)) { return null; } if (1 === count($out)) { return $out[0]; } return $out; } public function getWordDefinition() { return '\b\w+'; } public function setWords(array $words) { $old = $this->_words; $this->_words = $words; return $old; } public function getWords() { return $this->_words; } } setRoot($root); if (null !== $iteratorFactory) { $this->setIteratorFactory($iteratorFactory); } return; } public function complete(&$prefix) { $root = $this->getRoot(); if (static::PWD === $root) { $root = getcwd(); } $path = $root . DS . $prefix; if (!is_dir($path)) { $path = dirname($path) . DS; $prefix = basename($prefix); } else { $prefix = null; } $iteratorFactory = $this->getIteratorFactory() ?: static::getDefaultIteratorFactory(); try { $iterator = $iteratorFactory($path); $out = []; $length = mb_strlen($prefix); foreach ($iterator as $fileinfo) { $filename = $fileinfo->getFilename(); if (null === $prefix || (mb_substr($filename, 0, $length) === $prefix)) { if ($fileinfo->isDir()) { $out[] = $filename . '/'; } else { $out[] = $filename; } } } } catch (\Exception $e) { return null; } $count = count($out); if (1 === $count) { return $out[0]; } if (0 === $count) { return null; } return $out; } public function getWordDefinition() { return '/?[\w\d\\_\-\.]+(/[\w\d\\_\-\.]*)*'; } public function setRoot($root) { $old = $this->_root; $this->_root = $root; return $old; } public function getRoot() { return $this->_root; } public function setIteratorFactory(\Closure $iteratorFactory) { $old = $this->_iteratorFactory; $this->_iteratorFactory = $iteratorFactory; return $old; } public function getIteratorFactory() { return $this->_iteratorFactory; } public static function getDefaultIteratorFactory() { return function ($path) { return new \DirectoryIterator($path); }; } } $repeat) { return; } elseif (1 === $repeat) { $handle = explode(' ', $steps); } else { $handle = explode(' ', $steps, 1); } $tput = Console::getTput(); $output = Console::getOutput(); foreach ($handle as $step) { switch ($step) { case 'u': case 'up': case '↑': $output->writeAll( str_replace( '%p1%d', $repeat, $tput->get('parm_up_cursor') ) ); break; case 'U': case 'UP': static::moveTo(null, 1); break; case 'r': case 'right': case '→': $output->writeAll( str_replace( '%p1%d', $repeat, $tput->get('parm_right_cursor') ) ); break; case 'R': case 'RIGHT': static::moveTo(9999); break; case 'd': case 'down': case '↓': $output->writeAll( str_replace( '%p1%d', $repeat, $tput->get('parm_down_cursor') ) ); break; case 'D': case 'DOWN': static::moveTo(null, 9999); break; case 'l': case 'left': case '←': $output->writeAll( str_replace( '%p1%d', $repeat, $tput->get('parm_left_cursor') ) ); break; case 'L': case 'LEFT': static::moveTo(1); break; } } return; } public static function moveTo($x = null, $y = null) { if (null === $x || null === $y) { $position = static::getPosition(); if (null === $x) { $x = $position['x']; } if (null === $y) { $y = $position['y']; } } Console::getOutput()->writeAll( str_replace( ['%i%p1%d', '%p2%d'], [$y, $x], Console::getTput()->get('cursor_address') ) ); return; } public static function getPosition() { $tput = Console::getTput(); $user7 = $tput->get('user7'); if (null === $user7) { return [ 'x' => 0, 'y' => 0 ]; } Console::getOutput()->writeAll($user7); $input = Console::getInput(); $input->read(2); $x = null; $y = null; $handle = &$y; do { $char = $input->readCharacter(); switch ($char) { case ';': $handle = &$x; break; case 'R': break 2; default: $handle .= $char; } } while (true); return [ 'x' => (int) $x, 'y' => (int) $y ]; } public static function save() { Console::getOutput()->writeAll( Console::getTput()->get('save_cursor') ); return; } public static function restore() { Console::getOutput()->writeAll( Console::getTput()->get('restore_cursor') ); return; } public static function clear($parts = 'all') { $tput = Console::getTput(); $output = Console::getOutput(); foreach (explode(' ', $parts) as $part) { switch ($part) { case 'a': case 'all': case '↕': $output->writeAll($tput->get('clear_screen')); static::moveTo(1, 1); break; case 'u': case 'up': case '↑': $output->writeAll("\033[1J"); break; case 'r': case 'right': case '→': $output->writeAll($tput->get('clr_eol')); break; case 'd': case 'down': case '↓': $output->writeAll($tput->get('clr_eos')); break; case 'l': case 'left': case '←': $output->writeAll($tput->get('clr_bol')); break; case 'line': case '↔': $output->writeAll("\r" . $tput->get('clr_eol')); break; } } return; } public static function hide() { Console::getOutput()->writeAll( Console::getTput()->get('cursor_invisible') ); return; } public static function show() { Console::getOutput()->writeAll( Console::getTput()->get('cursor_visible') ); return; } public static function colorize($attributes) { static $_rgbTo256 = null; if (null === $_rgbTo256) { $_rgbTo256 = [ '000000', '800000', '008000', '808000', '000080', '800080', '008080', 'c0c0c0', '808080', 'ff0000', '00ff00', 'ffff00', '0000ff', 'ff00ff', '00ffff', 'ffffff', '000000', '00005f', '000087', '0000af', '0000d7', '0000ff', '005f00', '005f5f', '005f87', '005faf', '005fd7', '005fff', '008700', '00875f', '008787', '0087af', '0087d7', '0087ff', '00af00', '00af5f', '00af87', '00afaf', '00afd7', '00afff', '00d700', '00d75f', '00d787', '00d7af', '00d7d7', '00d7ff', '00ff00', '00ff5f', '00ff87', '00ffaf', '00ffd7', '00ffff', '5f0000', '5f005f', '5f0087', '5f00af', '5f00d7', '5f00ff', '5f5f00', '5f5f5f', '5f5f87', '5f5faf', '5f5fd7', '5f5fff', '5f8700', '5f875f', '5f8787', '5f87af', '5f87d7', '5f87ff', '5faf00', '5faf5f', '5faf87', '5fafaf', '5fafd7', '5fafff', '5fd700', '5fd75f', '5fd787', '5fd7af', '5fd7d7', '5fd7ff', '5fff00', '5fff5f', '5fff87', '5fffaf', '5fffd7', '5fffff', '870000', '87005f', '870087', '8700af', '8700d7', '8700ff', '875f00', '875f5f', '875f87', '875faf', '875fd7', '875fff', '878700', '87875f', '878787', '8787af', '8787d7', '8787ff', '87af00', '87af5f', '87af87', '87afaf', '87afd7', '87afff', '87d700', '87d75f', '87d787', '87d7af', '87d7d7', '87d7ff', '87ff00', '87ff5f', '87ff87', '87ffaf', '87ffd7', '87ffff', 'af0000', 'af005f', 'af0087', 'af00af', 'af00d7', 'af00ff', 'af5f00', 'af5f5f', 'af5f87', 'af5faf', 'af5fd7', 'af5fff', 'af8700', 'af875f', 'af8787', 'af87af', 'af87d7', 'af87ff', 'afaf00', 'afaf5f', 'afaf87', 'afafaf', 'afafd7', 'afafff', 'afd700', 'afd75f', 'afd787', 'afd7af', 'afd7d7', 'afd7ff', 'afff00', 'afff5f', 'afff87', 'afffaf', 'afffd7', 'afffff', 'd70000', 'd7005f', 'd70087', 'd700af', 'd700d7', 'd700ff', 'd75f00', 'd75f5f', 'd75f87', 'd75faf', 'd75fd7', 'd75fff', 'd78700', 'd7875f', 'd78787', 'd787af', 'd787d7', 'd787ff', 'd7af00', 'd7af5f', 'd7af87', 'd7afaf', 'd7afd7', 'd7afff', 'd7d700', 'd7d75f', 'd7d787', 'd7d7af', 'd7d7d7', 'd7d7ff', 'd7ff00', 'd7ff5f', 'd7ff87', 'd7ffaf', 'd7ffd7', 'd7ffff', 'ff0000', 'ff005f', 'ff0087', 'ff00af', 'ff00d7', 'ff00ff', 'ff5f00', 'ff5f5f', 'ff5f87', 'ff5faf', 'ff5fd7', 'ff5fff', 'ff8700', 'ff875f', 'ff8787', 'ff87af', 'ff87d7', 'ff87ff', 'ffaf00', 'ffaf5f', 'ffaf87', 'ffafaf', 'ffafd7', 'ffafff', 'ffd700', 'ffd75f', 'ffd787', 'ffd7af', 'ffd7d7', 'ffd7ff', 'ffff00', 'ffff5f', 'ffff87', 'ffffaf', 'ffffd7', 'ffffff', '080808', '121212', '1c1c1c', '262626', '303030', '3a3a3a', '444444', '4e4e4e', '585858', '606060', '666666', '767676', '808080', '8a8a8a', '949494', '9e9e9e', 'a8a8a8', 'b2b2b2', 'bcbcbc', 'c6c6c6', 'd0d0d0', 'dadada', 'e4e4e4', 'eeeeee' ]; } $tput = Console::getTput(); if (1 >= $tput->count('max_colors')) { return; } $handle = []; foreach (explode(' ', $attributes) as $attribute) { switch ($attribute) { case 'n': case 'normal': $handle[] = 0; break; case 'b': case 'bold': $handle[] = 1; break; case 'u': case 'underlined': $handle[] = 4; break; case 'bl': case 'blink': $handle[] = 5; break; case 'i': case 'inverse': $handle[] = 7; break; case '!b': case '!bold': $handle[] = 22; break; case '!u': case '!underlined': $handle[] = 24; break; case '!bl': case '!blink': $handle[] = 25; break; case '!i': case '!inverse': $handle[] = 27; break; default: if (0 === preg_match('#^([^\(]+)\(([^\)]+)\)$#', $attribute, $m)) { break; } $shift = 0; switch ($m[1]) { case 'fg': case 'foreground': $shift = 0; break; case 'bg': case 'background': $shift = 10; break; default: break 2; } $_handle = 0; $_keyword = true; switch ($m[2]) { case 'black': $_handle = 30; break; case 'red': $_handle = 31; break; case 'green': $_handle = 32; break; case 'yellow': $_handle = 33; break; case 'blue': $_handle = 34; break; case 'magenta': $_handle = 35; break; case 'cyan': $_handle = 36; break; case 'white': $_handle = 37; break; case 'default': $_handle = 39; break; default: $_keyword = false; if (256 <= $tput->count('max_colors') && '#' === $m[2][0]) { $rgb = hexdec(substr($m[2], 1)); $r = ($rgb >> 16) & 255; $g = ($rgb >> 8) & 255; $b = $rgb & 255; $distance = null; foreach ($_rgbTo256 as $i => $_rgb) { $_rgb = hexdec($_rgb); $_r = ($_rgb >> 16) & 255; $_g = ($_rgb >> 8) & 255; $_b = $_rgb & 255; $d = sqrt( pow($_r - $r, 2) + pow($_g - $g, 2) + pow($_b - $b, 2) ); if (null === $distance || $d <= $distance) { $distance = $d; $_handle = $i; } } } else { $_handle = intval($m[2]); } } if (true === $_keyword) { $handle[] = $_handle + $shift; } else { $handle[] = (38 + $shift) . ';5;' . $_handle; } } } Console::getOutput()->writeAll("\033[" . implode(';', $handle) . "m"); return; } public static function changeColor($fromCode, $toColor) { $tput = Console::getTput(); if (true !== $tput->has('can_change')) { return; } $r = ($toColor >> 16) & 255; $g = ($toColor >> 8) & 255; $b = $toColor & 255; Console::getOutput()->writeAll( str_replace( [ '%p1%d', 'rgb:', '%p2%{255}%*%{1000}%/%2.2X/', '%p3%{255}%*%{1000}%/%2.2X/', '%p4%{255}%*%{1000}%/%2.2X' ], [ $fromCode, '', sprintf('%02x', $r), sprintf('%02x', $g), sprintf('%02x', $b) ], $tput->get('initialize_color') ) ); return; } public static function setStyle($style, $blink = true) { if (OS_WIN) { return; } switch ($style) { case 'b': case 'block': case '▋': $_style = 1; break; case 'u': case 'underline': case '_': $_style = 2; break; case 'v': case 'vertical': case '|': $_style = 5; break; } if (false === $blink) { ++$_style; } Console::getOutput()->writeAll("\033[" . $_style . " q"); return; } public static function bip() { Console::getOutput()->writeAll( Console::getTput()->get('bell') ); return; } } Console::advancedInteraction(); _infos[] = $default; } else { $this->_infos[$infos] = $default; } return $out; } public function current() { $out = parent::current(); foreach ($out as $key => &$value) { if (null === $value) { $value = $this->_infos[$key]; } } return $out; } } _callback = $callback; return; } public function current() { $handle = $this->_callback; return $this->_current = $handle($this->_key); } public function key() { return $this->_key; } public function next() { ++$this->_key; return; } public function rewind() { $this->_key = 0; $this->_current = null; return; } public function valid() { return true; } } _splFileInfoClass = $splFileInfoClass; parent::__construct($path); $this->setRelativePath($path); return; } public function current() { $out = parent::current(); if (null !== $this->_splFileInfoClass && $out instanceof \SplFileInfo) { $out->setInfoClass($this->_splFileInfoClass); $out = $out->getFileInfo(); if ($out instanceof \Hoa\Iterator\SplFileInfo) { $out->setRelativePath($this->getRelativePath()); } } return $out; } protected function setRelativePath($path) { $old = $this->_relativePath; $this->_relativePath = $path; return $old; } public function getRelativePath() { return $this->_relativePath; } } given( $foo = new LUT\Map(['f', 'o', 'o']), $bar = new LUT\Map(['b', 'a', 'r']), $multiple = new LUT\Multiple( LUT\Multiple::MIT_NEED_ANY | LUT\Multiple::MIT_KEYS_ASSOC ), $multiple->attachIterator($foo, 'one'), $multiple->attachIterator($bar, 'two') ) ->when($result = iterator_to_array($multiple, false)) ->then ->array($result) ->isEqualTo([ ['one' => 'f', 'two' => 'b'], ['one' => 'o', 'two' => 'a'], ['one' => 'o', 'two' => 'r'] ]); } public function case_default_value() { $this ->given( $foobar = new LUT\Map(['f', 'o', 'o', 'b', 'a', 'r']), $baz = new LUT\Map(['b', 'a', 'z']), $multiple = new LUT\Multiple( LUT\Multiple::MIT_NEED_ANY | LUT\Multiple::MIT_KEYS_ASSOC ), $multiple->attachIterator($foobar, 'one', '!'), $multiple->attachIterator($baz, 'two', '?') ) ->when($result = iterator_to_array($multiple, false)) ->then ->array($result) ->isEqualTo([ ['one' => 'f', 'two' => 'b'], ['one' => 'o', 'two' => 'a'], ['one' => 'o', 'two' => 'z'], ['one' => 'b', 'two' => '?'], ['one' => 'a', 'two' => '?'], ['one' => 'r', 'two' => '?'], ]); } public function case_empty() { $this ->given($multiple = new LUT\Multiple()) ->when($result = iterator_to_array($multiple)) ->then ->array($result) ->isEmpty(); } } given( $iterator = new LUT\CallbackGenerator(function ($key) { return $key * 2; }), $limit = new LUT\Limit($iterator, 0, 5) ) ->when($result = iterator_to_array($limit)) ->then ->array($result) ->isEqualTo([ 0, 2, 4, 6, 8 ]); } } given( $root = resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/A?type=file'), resolve('hoa://Test/Vfs/Root/Aa?type=file'), resolve('hoa://Test/Vfs/Root/Aaa?type=file'), $iterator = new LUT\Directory($root), $result = [] ) ->when(function () use ($iterator, &$result) { foreach ($iterator as $key => $file) { $result[$key] = $file->getFilename(); $this ->object($file) ->isInstanceOf(LUT\Directory::class); } }) ->then ->array($result) ->isEqualTo([ 0 => 'A', 1 => 'Aa', 2 => 'Aaa' ]); } public function case_seek_and_dots() { $this ->given( $root = resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/.?type=directory'), resolve('hoa://Test/Vfs/Root/..?type=directory'), resolve('hoa://Test/Vfs/Root/Skip?type=file'), resolve('hoa://Test/Vfs/Root/Gotcha?type=file'), $iterator = new LUT\Directory($root) ) ->when($result = $iterator->current()) ->then ->boolean($result->isDot()) ->isTrue() ->when( $iterator->next(), $result = $iterator->current() ) ->then ->boolean($result->isDot()) ->isTrue() ->when( $iterator->seek(3), $result = $iterator->current() ) ->then ->string($result->getFilename()) ->isEqualTo('Gotcha') ->when( $iterator->seek(2), $result = $iterator->current() ) ->then ->string($result->getFilename()) ->isEqualTo('Skip'); } public function case_recursive() { $this ->given( $root = resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/A?type=file'), resolve('hoa://Test/Vfs/Root/Aa?type=file'), resolve('hoa://Test/Vfs/Root/Aaa?type=file'), resolve('hoa://Test/Vfs/Root/Foo?type=directory'), resolve('hoa://Test/Vfs/Root/Foo/Bar?type=directory'), resolve('hoa://Test/Vfs/Root/Foo/Bar/B?type=file'), resolve('hoa://Test/Vfs/Root/Foo/Bar/Bb?type=file'), resolve('hoa://Test/Vfs/Root/Foo/Bar/Bbb?type=file'), resolve('hoa://Test/Vfs/Root/Foo/C?type=file'), resolve('hoa://Test/Vfs/Root/Foo/Cc?type=file'), resolve('hoa://Test/Vfs/Root/Foo/Ccc?type=file'), $directory = new LUT\Recursive\Directory($root), $iterator = new LUT\Recursive\Iterator($directory), $result = [] ) ->when(function () use ($iterator, &$result) { foreach ($iterator as $file) { $result[] = $file->getFilename(); } }) ->then ->array($result) ->isEqualTo([ 'A', 'Aa', 'Aaa', 'B', 'Bb', 'Bbb', 'C', 'Cc', 'Ccc' ]); } public function case_splFileClassInfo() { $this ->given( $splFileInfo = 'Hoa\Iterator\SplFileInfo', $root = resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/a?type=file'), resolve('hoa://Test/Vfs/Root/b?type=file'), resolve('hoa://Test/Vfs/Root/c?type=file'), resolve('hoa://Test/Vfs/Root/d?type=file'), resolve('hoa://Test/Vfs/Root/e?type=file'), resolve('hoa://Test/Vfs/Root/f?type=file'), $iterator = new LUT\Directory( $root, $splFileInfo ), $result = [] ) ->when(function () use ($iterator, $splFileInfo, &$result) { foreach ($iterator as $file) { $this ->object($file) ->isInstanceOf($splFileInfo); $result[] = $file->getFilename(); } }) ->then ->array($result) ->isEqualTo([ 'a', 'b', 'c', 'd', 'e', 'f' ]); } public function case_recursive_splFileClassInfo() { $this ->given( $splFileInfo = 'Hoa\Iterator\SplFileInfo', $root = resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/A?type=directory'), resolve('hoa://Test/Vfs/Root/A/a?type=file'), resolve('hoa://Test/Vfs/Root/A/b?type=file'), resolve('hoa://Test/Vfs/Root/A/c?type=file'), resolve('hoa://Test/Vfs/Root/B?type=directory'), resolve('hoa://Test/Vfs/Root/B/d?type=file'), resolve('hoa://Test/Vfs/Root/B/e?type=file'), resolve('hoa://Test/Vfs/Root/B/c?type=directory'), resolve('hoa://Test/Vfs/Root/B/c/f?type=file'), $directory = new LUT\Recursive\Directory( $root, LUT\FileSystem::CURRENT_AS_FILEINFO, $splFileInfo ), $iterator = new LUT\Recursive\Iterator($directory), $result = [] ) ->when(function () use ($iterator, $splFileInfo, &$result) { foreach ($iterator as $file) { $this ->object($file) ->isInstanceOf($splFileInfo); $result[] = $file->getFilename(); } }) ->then ->array($result) ->isEqualTo([ 'a', 'b', 'c', 'd', 'e', 'f' ]); } } given($pathname = 'hoa://Test/Vfs/Foo.bar?type=file') ->when($result = new LUT\SplFileInfo($pathname)) ->then ->boolean($result->isFile()) ->isTrue() ->string($result->getType()) ->isEqualTo('file'); } public function case_directory() { $this ->given($pathname = 'hoa://Test/Vfs/Foo?type=directory') ->when($result = new LUT\SplFileInfo($pathname)) ->then ->boolean($result->isDir()) ->isTrue() ->string($result->getType()) ->isEqualTo('dir'); } public function case_path_informations() { $this ->given( $relativePath = 'hoa://Test/Vfs/A/B/', $relativePathname = 'C/Foo.bar', $pathname = $relativePath . $relativePathname ) ->when($result = new LUT\SplFileInfo($pathname . '?type=file', $relativePath)) ->then ->boolean($result->isFile()) ->isTrue() ->string($result->getBasename()) ->isEqualTo('Foo.bar?type=file') ->string($result->getExtension()) ->isEqualTo('bar?type=file') ->string($result->getRelativePath()) ->isEqualTo($relativePath) ->string($result->getRelativePathname()) ->isEqualTo($relativePathname . '?type=file') ->string($result->getPath()) ->isEqualTo('hoa://Test/Vfs/A/B/C') ->string($result->getPathname()) ->isEqualTo($pathname . '?type=file'); } public function case_times() { $this ->given( $timestamp = $this->realdom->boundinteger( $this->realdom->timestamp('yesterday'), $this->realdom->timestamp('tomorrow') ), $atime = $this->sample($timestamp), $ctime = $this->sample($timestamp), $mtime = $this->sample($timestamp), $pathname = 'hoa://Test/Vfs/Foo.bar?' . http_build_query([ 'type' => 'file', 'atime' => $atime, 'ctime' => $ctime, 'mtime' => $mtime ]) ) ->when($result = new LUT\SplFileInfo($pathname)) ->then ->integer($result->getATime()) ->isEqualTo($atime) ->integer($result->getCTime()) ->isEqualTo($ctime) ->integer($result->getMTime()) ->isEqualTo($mtime); } public function case_permissions() { $this ->given($pathname = 'hoa://Test/Vfs/Fo.bar?type=file&permissions=0744') ->when($result = new LUT\SplFileInfo($pathname)) ->then ->boolean($result->isReadable()) ->isTrue() ->boolean($result->isWritable()) ->isTrue() ->boolean($result->isExecutable()) ->isTrue() ->given($pathname = 'hoa://Test/Vfs/Foo.bar?type=file&permissions=0644') ->when($result = new LUT\SplFileInfo($pathname)) ->then ->boolean($result->isReadable()) ->isTrue() ->boolean($result->isWritable()) ->isTrue() ->boolean($result->isExecutable()) ->isFalse() ->given($pathname = 'hoa://Test/Vfs/Fooo.bar?type=file&permissions=0444') ->when($result = new LUT\SplFileInfo($pathname)) ->then ->boolean($result->isReadable()) ->isTrue() ->boolean($result->isWritable()) ->isFalse() ->boolean($result->isExecutable()) ->isFalse() ->given($pathname = 'hoa://Test/Vfs/Foooo.bar?type=file&permissions=0044') ->when($result = new LUT\SplFileInfo($pathname)) ->then ->boolean($result->isReadable()) ->isFalse() ->boolean($result->isWritable()) ->isFalse() ->boolean($result->isExecutable()) ->isFalse(); } } given( $root = resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/.?type=directory'), resolve('hoa://Test/Vfs/Root/..?type=directory'), resolve('hoa://Test/Vfs/Root/A?type=file'), resolve('hoa://Test/Vfs/Root/B?type=file'), $iterator = new LUT\FileSystem($root), $result = [] ) ->when(function () use ($iterator, &$result) { foreach ($iterator as $pathname => $file) { $this ->object($file) ->isInstanceOf('SplFileInfo'); $result[basename($pathname)] = $file->getFilename(); } }) ->array($result) ->isEqualTo([ 'A' => 'A', 'B' => 'B' ]); } public function case_splFileClassInfo() { $this ->given( $splFileInfo = 'Hoa\Iterator\SplFileInfo', $root = resolve('hoa://Test/Vfs/Root?type=directory'), resolve('hoa://Test/Vfs/Root/a?type=file'), resolve('hoa://Test/Vfs/Root/b?type=file'), resolve('hoa://Test/Vfs/Root/c?type=file'), resolve('hoa://Test/Vfs/Root/d?type=file'), resolve('hoa://Test/Vfs/Root/e?type=file'), resolve('hoa://Test/Vfs/Root/f?type=file'), $iterator = new LUT\FileSystem( $root, LUT\FileSystem::CURRENT_AS_FILEINFO, $splFileInfo ), $result = [] ) ->when(function () use ($iterator, $splFileInfo, &$result) { foreach ($iterator as $file) { $this ->object($file) ->isInstanceOf($splFileInfo); $result[] = $file->getFilename(); } }) ->then ->array($result) ->isEqualTo([ 'a', 'b', 'c', 'd', 'e', 'f' ]); } } given( $foobar = $this->getDummyIterator(), $filter = new LUT\CallbackFilter( $foobar, function ($value) { return false === in_array($value, ['a', 'e', 'i', 'o', 'u']); } ) ) ->when($result = iterator_to_array($filter)) ->then ->array($result) ->isEqualTo([ 0 => 'f', 3 => 'b', 5 => 'r' ]); } public function case_all_callback_parameters() { $self = $this; $this ->given( $foobar = $this->getDummyIterator(), $keys = [], $values = [], $filter = new LUT\CallbackFilter( $foobar, function ($value, $key, $iterator) use ( $self, $foobar, &$keys, &$values ) { $self ->object($iterator) ->isIdenticalTo($foobar); $keys[] = $key; $values[] = $value; return false === in_array($value, ['a', 'e', 'i', 'o', 'u']); } ) ) ->when($result = iterator_to_array($filter)) ->then ->array($result) ->isEqualTo([ 0 => 'f', 3 => 'b', 5 => 'r' ]) ->array(array_combine($keys, $values)) ->isEqualTo(iterator_to_array($foobar)); } public function case_remove_all() { $this ->given( $foobar = $this->getDummyIterator(), $filter = new LUT\CallbackFilter( $foobar, function () { return false; } ) ) ->when($result = iterator_to_array($filter)) ->then ->array($result) ->isEmpty(); } public function case_remove_none() { $this ->given( $foobar = $this->getDummyIterator(), $filter = new LUT\CallbackFilter( $foobar, function () { return true; } ) ) ->when( $foobarResult = iterator_to_array($foobar), $filterResult = iterator_to_array($filter) ) ->then ->array($foobarResult) ->isEqualTo($filterResult); } public function case_recursive() { $this ->given( $foobar = $this->getDummyRecursiveIterator(), $filter = new LUT\Recursive\CallbackFilter( $foobar, function ($value) { return false === in_array($value, ['a', 'e', 'i', 'o', 'u']); } ), $iterator = new LUT\Recursive\Iterator($filter) ) ->when($result = iterator_to_array($iterator, false)) ->then ->array($result) ->isEqualTo([ 0 => 'f', 1 => 'b', 2 => 'r' ]); } public function case_recursive_remove_all() { $this ->given( $foobar = $this->getDummyRecursiveIterator(), $filter = new LUT\Recursive\CallbackFilter( $foobar, function () { return false; } ), $iterator = new LUT\Recursive\Iterator($filter) ) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEmpty(); } public function case_recursive_remove_none() { $this ->given( $foobar = $this->getDummyRecursiveIterator(), $filter = new LUT\Recursive\CallbackFilter( $foobar, function () { return true; } ), $foobarIterator = new LUT\Recursive\Iterator($foobar), $filterIterator = new LUT\Recursive\Iterator($filter) ) ->when( $foobarResult = iterator_to_array($foobarIterator), $filterResult = iterator_to_array($filterIterator) ) ->then ->array($foobarResult) ->isEqualTo($filterResult); } protected function getDummyIterator() { return new LUT\Map(['f', 'o', 'o', 'b', 'a', 'r']); } protected function getDummyRecursiveIterator() { return new LUT\Recursive\Map([['f', 'o', 'o'], ['b', 'a', 'r']]); } } given( $counter1 = new LUT\Counter(0, 12, 3), $counter2 = new LUT\Counter(13, 23, 2), $append = new LUT\Append(), $append->append($counter1), $append->append($counter2) ) ->when($result = iterator_to_array($append, false)) ->then ->array($result) ->isEqualTo([ 0, 3, 6, 9, 13, 15, 17, 19, 21 ]); } public function case_singleton() { $this ->given( $counter1 = new LUT\Counter(0, 12, 3), $append = new LUT\Append(), $append->append($counter1) ) ->when($result = iterator_to_array($append)) ->then ->array($result) ->isEqualTo([ 0, 3, 6, 9 ]); } public function case_empty() { $this ->given($append = new LUT\Append()) ->when($result = iterator_to_array($append)) ->then ->array($result) ->isEmpty(); } } given( $counter = new LUT\Counter(0, 10, 1), $multiple = new LUT\Multiple(), $multiple->attachIterator($counter), $multiple->attachIterator(clone $counter), $demultiplexer = new LUT\Demultiplexer( $multiple, function ($current) { return $current[0] * $current[1]; } ) ) ->when($result = iterator_to_array($demultiplexer, false)) ->then ->array($result) ->isEqualTo([ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 ]); } public function case_associative_keys() { $this ->given( $counter = new LUT\Counter(0, 10, 1), $multiple = new LUT\Multiple( LUT\Multiple::MIT_NEED_ANY | LUT\Multiple::MIT_KEYS_ASSOC ), $multiple->attachIterator($counter, 'one'), $multiple->attachIterator(clone $counter, 'two'), $demultiplexer = new LUT\Demultiplexer( $multiple, function ($current) { return $current['one'] * $current['two']; } ) ) ->when($result = iterator_to_array($demultiplexer, false)) ->then ->array($result) ->isEqualTo([ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81 ]); } } given($iterator = new LUT\Mock()) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEmpty(); } public function case_recursive_mock_mock() { $this ->when($iterator = new LUT\Recursive\Mock(new LUT\Mock())) ->then ->variable($iterator->getChildren()) ->isNull() ->boolean($iterator->hasChildren()) ->isFalse(); } public function case_recursive() { $this ->given( $map = new LUT\Map(['a', 'b', 'c']), $mock = new LUT\Recursive\Mock($map), $iteratoriterator = new LUT\Recursive\Iterator($mock) ) ->when($result = iterator_to_array($map, false)) ->then ->array($result) ->isEqualTo(['a', 'b', 'c']); } } given($iterator = new LUT\Map(self::$_dummyArray)) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo(self::$_dummyArray); } public function case_empty() { $this ->given($iterator = new LUT\Map()) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEmpty(); } public function case_recursive() { $this ->given( $array = [ 'a' => ['b', 'c', 'd'], 'e' => ['f', 'g', 'i'], 'k' ], $iterator = new LUT\Recursive\Map($array) ) ->when(function () use ($iterator) { foreach ($iterator as $key => $value) { if ('a' === $key) { $this ->boolean($iterator->hasChildren()) ->isTrue() ->object($children = $iterator->getChildren()) ->isInstanceOf(LUT\Recursive\Map::class) ->array(iterator_to_array($children)) ->isEqualTo(['b', 'c', 'd']); } elseif ('e' === $key) { $this ->boolean($iterator->hasChildren()) ->isTrue() ->object($children = $iterator->getChildren()) ->isInstanceOf(LUT\Recursive\Map::class) ->array(iterator_to_array($children)) ->isEqualTo(['f', 'g', 'i']); } elseif ('k' === $value) { $this ->boolean($iterator->hasChildren()) ->isFalse(); } } }); } } given( $iterator = new LUT\Map(self::$_dummyArray), $limit = new LUT\Limit($iterator, 2, 3) ) ->when($result = iterator_to_array($limit)) ->then ->array($result) ->isEqualTo([ 2 => 'o', 3 => 'b', 4 => 'a' ]); } public function case_negative_offset() { $this ->given($iterator = new LUT\Map(self::$_dummyArray)) ->exception(function () use ($iterator) { new LUT\Limit($iterator, -2, 3); }) ->isInstanceOf(\OutOfRangeException::class); } public function case_empty() { $this ->given( $iterator = new LUT\Map(self::$_dummyArray), $limit = new LUT\Limit($iterator, 0, 0) ) ->exception(function () use ($limit) { iterator_to_array($limit); }) ->isInstanceOf(\OutOfBoundsException::class); } } given($iterator = new LUT\Counter(0, 12, 3)) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo([0, 3, 6, 9]); } public function case_offset() { $this ->given($iterator = new LUT\Counter(6, 12, 3)) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo([6, 9]); } public function case_too_small() { $this ->exception(function () { new LUT\Counter(0, 0, 0); }) ->isInstanceOf(LUT\Exception::class); } public function case_too_big() { $this ->given($iterator = new LUT\Counter(0, 12, 13)) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo([0]); } } given( $iterator = new LUT\Map([]), $iteratoriterator = new LUT\IteratorIterator($iterator) ) ->when($result = $iteratoriterator->getInnerIterator()) ->then ->object($result) ->isIdenticalTo($iterator); } public function case_traverse() { $this ->given( $iterator = new LUT\Map(['a', 'b', 'c']), $iteratoriterator = new LUT\IteratorIterator($iterator) ) ->when($result = iterator_to_array($iteratoriterator)) ->then ->array($result) ->isEqualTo(['a', 'b', 'c']); } public function case_recursive_leaves_only() { $this ->given( $array = [ 'a' => ['b', 'c', 'd'], 'e' => ['f', 'g', 'i'] ], $iterator = new LUT\Recursive\Map($array), $iteratoriterator = new LUT\Recursive\Iterator( $iterator, LUT\Recursive\Iterator::LEAVES_ONLY ) ) ->when($result = iterator_to_array($iteratoriterator, false)) ->then ->array($result) ->isEqualTo(['b', 'c', 'd', 'f', 'g', 'i']); } public function case_recursive_self_first() { $this ->given( $array = [ 'a' => ['b', 'c', 'd'], 'e' => ['f', 'g', 'i'] ], $iterator = new LUT\Recursive\Map($array), $iteratoriterator = new LUT\Recursive\Iterator( $iterator, LUT\Recursive\Iterator::SELF_FIRST ) ) ->when($result = iterator_to_array($iteratoriterator, false)) ->then ->array($result) ->isEqualTo([ ['b', 'c', 'd'], 'b', 'c', 'd', ['f', 'g', 'i'], 'f', 'g', 'i' ]); } public function case_recursive_child_first() { $this ->given( $array = [ 'a' => ['b', 'c', 'd'], 'e' => ['f', 'g', 'i'] ], $iterator = new LUT\Recursive\Map($array), $iteratoriterator = new LUT\Recursive\Iterator( $iterator, LUT\Recursive\Iterator::CHILD_FIRST ) ) ->when($result = iterator_to_array($iteratoriterator, false)) ->then ->array($result) ->isEqualTo([ 'b', 'c', 'd', ['b', 'c', 'd'], 'f', 'g', 'i', ['f', 'g', 'i'] ]); } } given( $foobar = $this->getDummyIterator(), $filter = new \Mock\Hoa\Iterator\Test\Unit\MyFilter($foobar), $this->calling($filter)->accept = function () { $value = $this->current(); return false === in_array($value, ['a', 'e', 'i', 'o', 'u']); } ) ->when($result = iterator_to_array($filter)) ->then ->array($result) ->isEqualTo([ 0 => 'f', 3 => 'b', 5 => 'r' ]); } public function case_remove_all() { $this ->given( $foobar = $this->getDummyIterator(), $filter = new \Mock\Hoa\Iterator\Test\Unit\MyFilter($foobar), $this->calling($filter)->accept = false ) ->when($result = iterator_to_array($filter)) ->then ->array($result) ->isEmpty(); } public function case_remove_none() { $this ->given( $foobar = $this->getDummyIterator(), $filter = new MyFilter($foobar) ) ->when( $foobarResult = iterator_to_array($foobar), $filterResult = iterator_to_array($filter) ) ->then ->array($foobarResult) ->isEqualTo($filterResult); } protected function getDummyIterator() { return new LUT\Map(['f', 'o', 'o', 'b', 'a', 'r']); } } given( $iterator = new LUT\Map(self::$_dummyArray), $repeater = new LUT\Repeater($iterator, 3) ) ->when($result = iterator_to_array($repeater)) ->then ->array($result) ->isEqualTo( self::$_dummyArray + self::$_dummyArray + self::$_dummyArray ); } public function case_with_body() { $self = $this; $this ->given( $iterator = new LUT\Map(self::$_dummyArray), $count = 0, $repeater = new LUT\Repeater( $iterator, 3, function ($repetition) use ($self, &$count) { $this ->integer($repetition) ->isEqualTo($count + 1); ++$count; }) ) ->when($result = iterator_to_array($repeater)) ->then ->array($result) ->isEqualTo( self::$_dummyArray + self::$_dummyArray + self::$_dummyArray ) ->integer($count) ->isEqualTo(3); } } given( $map = new LUT\Map([ 'abc', 'dea', 'fgh', 'iaj', 'klm' ]), $iterator = new LUT\RegularExpression($map, '/a/') ) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo([ 0 => 'abc', 1 => 'dea', 3 => 'iaj' ]); } public function case_recursive() { $this ->given( $map = new LUT\Recursive\Map([ ['abc', 'dea', 'fgh'], ['iaj', 'klm'] ]), $regex = new LUT\Recursive\RegularExpression($map, '/a/'), $iterator = new LUT\Recursive\Iterator($regex) ) ->when($result = iterator_to_array($iterator, false)) ->then ->array($result) ->isEqualTo([ 0 => 'abc', 1 => 'dea', 2 => 'iaj' ]); } public function case_recursive_children_flags() { $this ->given( $map = new LUT\Recursive\Map([ ['abc', 'dea', 'fgh'], ['iaj', 'klm'] ]), $mode = LUT\Recursive\RegularExpression::ALL_MATCHES, $flag = LUT\Recursive\RegularExpression::USE_KEY, $pregFlag = LUT\Recursive\RegularExpression::ALL_MATCHES, $iterator = new LUT\Recursive\RegularExpression( $map, '/a/', $mode, $flag, $pregFlag ) ) ->when($result = $iterator->getChildren()) ->then ->object($result) ->isInstanceOf(LUT\Recursive\RegularExpression::class) ->integer($result->getMode()) ->isEqualTo($mode) ->integer($result->getFlags()) ->isEqualTo($flag) ->integer($result->getPregFlags()) ->isEqualTo($pregFlag); } } given( $iterator = new LUT\Map(['a']), $infinite = new LUT\Infinite($iterator), $limit = new LUT\Limit($infinite, 0, 100) ) ->when($result = iterator_to_array($limit, false)) ->then ->array($result) ->isEqualTo(array_fill(0, 100, 'a')) ->size ->isEqualTo(100); } } given( $dummyArray = ['f', 'o', 'o', 'b', 'a', 'r'], $iterator = new LUT\Map($dummyArray), $norewind = new LUT\NoRewind($iterator) ) ->when($result = iterator_to_array($norewind)) ->then ->array($result) ->isEqualTo($dummyArray) ->when($norewind->rewind()) ->boolean($norewind->valid()) ->isFalse() ->when($result = iterator_to_array($norewind)) ->then ->array($result) ->isEmpty(); } } given( $iterator = new LUT\Map(['a', 'b', 'c']), $lookahead = new LUT\Lookahead($iterator) ) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo(['a', 'b', 'c']); } public function case_check_ahead() { $this ->given( $iterator = new LUT\Map(['a', 'b', 'c']), $lookahead = new LUT\Lookahead($iterator) ) ->when( $lookahead->rewind(), $key = $lookahead->key(), $current = $lookahead->current(), $hasNext = $lookahead->hasNext(), $next = $lookahead->getNext() ) ->then ->integer($key) ->isEqualTo(0) ->string($current) ->isEqualTo('a') ->boolean($hasNext) ->isTrue() ->string($next) ->isEqualTo('b') ->when( $lookahead->next(), $key = $lookahead->key(), $current = $lookahead->current(), $hasNext = $lookahead->hasNext(), $next = $lookahead->getNext() ) ->then ->integer($key) ->isEqualTo(1) ->string($current) ->isEqualTo('b') ->boolean($hasNext) ->isTrue() ->string($next) ->isEqualTo('c') ->when( $lookahead->next(), $key = $lookahead->key(), $current = $lookahead->current(), $hasNext = $lookahead->hasNext(), $next = $lookahead->getNext() ) ->then ->integer($key) ->isEqualTo(2) ->string($current) ->isEqualTo('c') ->boolean($hasNext) ->isFalse() ->variable($next) ->isNull(); } public function case_double_rewind() { $this ->given( $iterator = new LUT\Map(['a', 'b', 'c']), $lookahead = new LUT\Lookahead($iterator) ) ->when( $lookahead->rewind(), $key = $lookahead->key(), $current = $lookahead->current(), $hasNext = $lookahead->hasNext(), $next = $lookahead->getNext() ) ->then ->integer($key) ->isEqualTo(0) ->string($current) ->isEqualTo('a') ->boolean($hasNext) ->isTrue() ->string($next) ->isEqualTo('b') ->when( $lookahead->rewind(), $key = $lookahead->key(), $current = $lookahead->current(), $hasNext = $lookahead->hasNext(), $next = $lookahead->getNext() ) ->then ->integer($key) ->isEqualTo(0) ->string($current) ->isEqualTo('a') ->boolean($hasNext) ->isTrue() ->string($next) ->isEqualTo('b'); } public function case_empty() { $this ->given( $iterator = new LUT\Mock(), $lookahead = new LUT\Lookahead($iterator) ) ->when( $lookahead->rewind(), $valid = $lookahead->valid() ) ->then ->boolean($valid) ->isFalse(); } } given( $iterator = new LUT\Map(['a', 'b', 'c']), $lookbehind = new LUT\Lookbehind($iterator) ) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo(['a', 'b', 'c']); } public function case_check_behind() { $this ->given( $iterator = new LUT\Map(['a', 'b', 'c']), $lookbehind = new LUT\Lookbehind($iterator) ) ->when( $lookbehind->rewind(), $key = $lookbehind->key(), $current = $lookbehind->current(), $hasPrevious = $lookbehind->hasPrevious(), $previous = $lookbehind->getPrevious() ) ->then ->integer($key) ->isEqualTo(0) ->string($current) ->isEqualTo('a') ->boolean($hasPrevious) ->isFalse() ->variable($previous) ->isNull() ->when( $lookbehind->next(), $key = $lookbehind->key(), $current = $lookbehind->current(), $hasPrevious = $lookbehind->hasPrevious(), $previous = $lookbehind->getPrevious() ) ->then ->integer($key) ->isEqualTo(1) ->string($current) ->isEqualTo('b') ->boolean($hasPrevious) ->isTrue() ->string($previous) ->isEqualTo('a') ->when( $lookbehind->next(), $key = $lookbehind->key(), $current = $lookbehind->current(), $hasPrevious = $lookbehind->hasPrevious(), $previous = $lookbehind->getPrevious() ) ->then ->integer($key) ->isEqualTo(2) ->string($current) ->isEqualTo('c') ->boolean($hasPrevious) ->isTrue() ->string($previous) ->isEqualTo('b'); } public function case_double_rewind() { $this ->given( $iterator = new LUT\Map(['a', 'b', 'c']), $lookbehind = new LUT\Lookbehind($iterator) ) ->when( $lookbehind->rewind(), $key = $lookbehind->key(), $current = $lookbehind->current(), $hasPrevious = $lookbehind->hasPrevious() ) ->then ->integer($key) ->isEqualTo(0) ->string($current) ->isEqualTo('a') ->boolean($hasPrevious) ->isFalse() ->when( $lookbehind->rewind(), $key = $lookbehind->key(), $current = $lookbehind->current(), $hasPrevious = $lookbehind->hasPrevious() ) ->then ->integer($key) ->isEqualTo(0) ->string($current) ->isEqualTo('a') ->boolean($hasPrevious) ->isFalse(); } public function case_empty() { $this ->given( $iterator = new LUT\Mock(), $lookbehind = new LUT\Lookbehind($iterator) ) ->when( $lookbehind->rewind(), $valid = $lookbehind->valid() ) ->then ->boolean($valid) ->isFalse(); } } given( $innerIterator = $this->getInnerIterator(), $bufferSize = 3 ) ->when($result = new SUT($innerIterator, $bufferSize)) ->then ->object($result->getInnerIterator()) ->isIdenticalTo($innerIterator) ->integer($result->getBufferSize()) ->isEqualTo($bufferSize) ->let($buffer = $this->invoke($result)->getBuffer()) ->object($buffer) ->isInstanceOf(\SplDoublyLinkedList::class) ->boolean($buffer->isEmpty()) ->isTrue(); } public function case_negative_buffer_size() { $this ->given( $innerIterator = $this->getInnerIterator(), $bufferSize = -42 ) ->when($result = new SUT($innerIterator, $bufferSize)) ->then ->integer($result->getBufferSize()) ->isEqualTo(1); } public function case_null_buffer_size() { $this ->given( $innerIterator = $this->getInnerIterator(), $bufferSize = 0 ) ->when($result = new SUT($innerIterator, $bufferSize)) ->then ->integer($result->getBufferSize()) ->isEqualTo(1); } public function case_fast_forward() { $this ->given($iterator = new SUT($this->getInnerIterator(), 3)) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo(['a', 'b', 'c', 'd', 'e']) ->array(iterator_to_array($this->invoke($iterator)->getBuffer())) ->isEqualTo([ 0 => [ $iterator::BUFFER_KEY => 3, $iterator::BUFFER_VALUE => 'd' ], 1 => [ $iterator::BUFFER_KEY => 4, $iterator::BUFFER_VALUE => 'e' ], 2 => [ $iterator::BUFFER_KEY => null, $iterator::BUFFER_VALUE => null ] ]); } public function case_fast_forward_with_too_big_buffer() { $this ->given($iterator = new SUT($this->getInnerIterator(), 10)) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo(['a', 'b', 'c', 'd', 'e']) ->array(iterator_to_array($this->invoke($iterator)->getBuffer())) ->isEqualTo([ 0 => [ $iterator::BUFFER_KEY => 0, $iterator::BUFFER_VALUE => 'a' ], 1 => [ $iterator::BUFFER_KEY => 1, $iterator::BUFFER_VALUE => 'b' ], 2 => [ $iterator::BUFFER_KEY => 2, $iterator::BUFFER_VALUE => 'c' ], 3 => [ $iterator::BUFFER_KEY => 3, $iterator::BUFFER_VALUE => 'd' ], 4 => [ $iterator::BUFFER_KEY => 4, $iterator::BUFFER_VALUE => 'e' ], 5 => [ $iterator::BUFFER_KEY => null, $iterator::BUFFER_VALUE => null ] ]); } public function case_fast_forward_with_smallest_buffer() { $this ->given($iterator = new SUT($this->getInnerIterator(), 1)) ->when($result = iterator_to_array($iterator)) ->then ->array($result) ->isEqualTo(['a', 'b', 'c', 'd', 'e']) ->array(iterator_to_array($this->invoke($iterator)->getBuffer())) ->isEqualTo([ 0 => [ $iterator::BUFFER_KEY => null, $iterator::BUFFER_VALUE => null ] ]); } public function case_forward_forward_forward() { $this ->when($result = new SUT(new LUT\Map(['a', 'b', 'c']), 2)) ->then ->variable($result->rewind()) ->isNull() ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(0) ->string($result->current()) ->isEqualTo('a') ->variable($result->next()) ->isNull() ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(1) ->string($result->current()) ->isEqualTo('b') ->variable($result->next()) ->isNull() ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(2) ->string($result->current()) ->isEqualTo('c') ->variable($result->next()) ->isNull() ->boolean($result->valid()) ->isFalse() ->variable($result->key()) ->isNull() ->variable($result->current()) ->isNull(); } public function case_forward_forward_backward_backward_forward_forward_forward_step_by_step() { $this ->when($result = new SUT(new LUT\Map(['a', 'b', 'c']), 3)) ->then ->variable($result->rewind()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 0, $result::BUFFER_VALUE => 'a' ] ]) ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(0) ->string($result->current()) ->isEqualTo('a') ->variable($result->next()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 0, $result::BUFFER_VALUE => 'a' ], 1 => [ $result::BUFFER_KEY => 1, $result::BUFFER_VALUE => 'b' ] ]) ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(1) ->string($result->current()) ->isEqualTo('b') ->variable($result->next()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 0, $result::BUFFER_VALUE => 'a' ], 1 => [ $result::BUFFER_KEY => 1, $result::BUFFER_VALUE => 'b' ], 2 => [ $result::BUFFER_KEY => 2, $result::BUFFER_VALUE => 'c' ] ]) ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(2) ->string($result->current()) ->isEqualTo('c') ->variable($result->previous()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 0, $result::BUFFER_VALUE => 'a' ], 1 => [ $result::BUFFER_KEY => 1, $result::BUFFER_VALUE => 'b' ], 2 => [ $result::BUFFER_KEY => 2, $result::BUFFER_VALUE => 'c' ] ]) ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(1) ->string($result->current()) ->isEqualTo('b') ->variable($result->previous()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 0, $result::BUFFER_VALUE => 'a' ], 1 => [ $result::BUFFER_KEY => 1, $result::BUFFER_VALUE => 'b' ], 2 => [ $result::BUFFER_KEY => 2, $result::BUFFER_VALUE => 'c' ] ]) ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(0) ->string($result->current()) ->isEqualTo('a') ->variable($result->next()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 0, $result::BUFFER_VALUE => 'a' ], 1 => [ $result::BUFFER_KEY => 1, $result::BUFFER_VALUE => 'b' ], 2 => [ $result::BUFFER_KEY => 2, $result::BUFFER_VALUE => 'c' ] ]) ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(1) ->string($result->current()) ->isEqualTo('b') ->variable($result->next()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 0, $result::BUFFER_VALUE => 'a' ], 1 => [ $result::BUFFER_KEY => 1, $result::BUFFER_VALUE => 'b' ], 2 => [ $result::BUFFER_KEY => 2, $result::BUFFER_VALUE => 'c' ] ]) ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(2) ->string($result->current()) ->isEqualTo('c') ->variable($result->next()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 1, $result::BUFFER_VALUE => 'b' ], 1 => [ $result::BUFFER_KEY => 2, $result::BUFFER_VALUE => 'c' ], 2 => [ $result::BUFFER_KEY => null, $result::BUFFER_VALUE => null ] ]) ->boolean($result->valid()) ->isFalse() ->variable($result->key()) ->isNull() ->variable($result->current()) ->isNull(); } public function case_backward_out_of_buffer() { $this ->when($result = new SUT(new LUT\Map(['a', 'b', 'c']), 1)) ->then ->variable($result->rewind()) ->isNull() ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(0) ->string($result->current()) ->isEqualTo('a') ->variable($result->next()) ->isNull() ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(1) ->string($result->current()) ->isEqualTo('b') ->variable($result->previous()) ->isNull() ->boolean($result->valid()) ->isFalse(); } public function case_rewind_rewind() { $this ->when($result = new SUT(new LUT\Map(['a', 'b']), 3)) ->then ->variable($result->rewind()) ->isNull() ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(0) ->string($result->current()) ->isEqualTo('a') ->variable($result->next()) ->isNull() ->variable($result->rewind()) ->isNull() ->boolean($result->valid()) ->isTrue() ->integer($result->key()) ->isEqualTo(0) ->string($result->current()) ->isEqualTo('a') ->variable($result->next()) ->isNull() ->array(iterator_to_array($this->invoke($result)->getBuffer())) ->isEqualTo([ 0 => [ $result::BUFFER_KEY => 0, $result::BUFFER_VALUE => 'a' ], 1 => [ $result::BUFFER_KEY => 1, $result::BUFFER_VALUE => 'b' ] ]); } protected function getInnerIterator() { return new LUT\Map(['a', 'b', 'c', 'd', 'e']); } } getMTime()) { $this->_hash = md5($this->getPathname() . $mtime); } $this->_relativePath = $relativePath; return; } public function getHash() { return $this->_hash; } public function getMTime() { try { return parent::getMTime(); } catch (\RuntimeException $e) { return -1; } } public function setRelativePath($relativePath) { $old = $this->_relativePath; $this->_relativePath = $relativePath; return $old; } public function getRelativePath() { return $this->_relativePath; } public function getRelativePathname() { if (null === $relative = $this->getRelativePath()) { return $this->getPathname(); } return substr($this->getPathname(), strlen($relative)); } } _splFileInfoClass = $splFileInfoClass; if (null === $flags) { parent::__construct($path); } else { parent::__construct($path, $flags); } return; } public function current() { $out = parent::current(); if (null !== $this->_splFileInfoClass && $out instanceof \SplFileInfo) { $out->setInfoClass($this->_splFileInfoClass); $out = $out->getFileInfo(); } return $out; } } getIterator(); } $this->_iterator = $iterator; $this->_demuxer = $demuxer; return; } public function current() { if (null !== $this->_current) { return $this->_current; } $demuxer = $this->_demuxer; return $this->_current = $demuxer($this->_iterator->current()); } public function key() { return $this->_iterator->key(); } public function next() { $this->_current = null; return $this->_iterator->next(); } public function rewind() { return $this->_iterator->rewind(); } public function valid() { return $this->_iterator->valid(); } } _from = $from; $this->_to = $to; $this->_step = $step; return; } public function current() { return $this->_i; } public function key() { return $this->_key; } public function next() { ++$this->_key; $this->_i += $this->_step; return; } public function rewind() { $this->_key = 0; $this->_i = $this->_from; return; } public function valid() { return $this->_i < $this->_to; } } _relativePath = self::$_handlePath; self::$_handlePath = null; } else { $this->_relativePath = $path; } $this->setSplFileInfoClass($splFileInfoClass); return; } public function current() { $out = parent::current(); if (null !== $this->_splFileInfoClass && $out instanceof \SplFileInfo) { $out->setInfoClass($this->_splFileInfoClass); $out = $out->getFileInfo(); if ($out instanceof \Hoa\Iterator\SplFileInfo) { $out->setRelativePath($this->getRelativePath()); } } return $out; } public function getChildren() { self::$_handlePath = $this->getRelativePath(); $out = parent::getChildren(); if ($out instanceof \RecursiveDirectoryIterator) { $out->setSplFileInfoClass($this->_splFileInfoClass); } return $out; } public function setSplFileInfoClass($splFileInfoClass) { $this->_splFileInfoClass = $splFileInfoClass; return; } public function getRelativePath() { return $this->_relativePath; } } getIterator(); } $this->_iterator = $iterator; return; } public function current() { return $this->_iterator->current(); } public function key() { return $this->_iterator->key(); } public function next() { return $this->_iterator->next(); } public function rewind() { return $this->_iterator->rewind(); } public function valid() { return $this->_iterator->valid(); } public function getChildren() { return null; } public function hasChildren() { return false; } } hasChildren() || true === parent::accept(); } public function getChildren() { return new static( true === $this->hasChildren() ? $this->getInnerIterator()->getChildren() : null, $this->getRegex(), $this->getMode(), $this->getFlags(), $this->getPregFlags() ); } public function hasChildren() { return $this->getInnerIterator()->hasChildren(); } } = $n) { throw new Exception( 'n must be greater than 0, given %d.', 0, $n ); } if ($iterator instanceof \IteratorAggregate) { $iterator = $iterator->getIterator(); } $this->_iterator = $iterator; $this->_n = $n; $this->_body = $body; return; } public function current() { return $this->_iterator->current(); } public function key() { return $this->_iterator->key(); } public function next() { return $this->_iterator->next(); } public function rewind() { return $this->_iterator->rewind(); } public function valid() { $valid = $this->_iterator->valid(); if (true === $valid) { return true; } if (null !== $this->_body) { $handle = &$this->_body; $handle($this->_i); } $this->rewind(); if ($this->_n <= $this->_i++) { $this->_i = 1; return false; } return true; } } _regex = $regex; $this->setMode($mode); $this->setFlags($flags); $this->setPregFlags($pregFlags); $this->replacement = null; return; } public function accept() { if (is_array(parent::current())) { return false; } $this->_key = parent::key(); $this->_current = parent::current(); $matches = []; $useKey = $this->_flags & self::USE_KEY; $subject = $useKey ? $this->_key : $this->_current; $out = false; switch ($this->_mode) { case self::MATCH: $out = 0 !== preg_match( $this->_regex, $subject, $matches, $this->_pregFlags ); break; case self::GET_MATCH: $this->_current = []; $out = 0 !== preg_match( $this->_regex, $subject, $this->_current, $this->_pregFlags ); break; case self::ALL_MATCHES: $this->_current = []; $out = 0 < preg_match_all( $this->_regex, $subject, $this->_current, $this->_pregFlags ); break; case self::SPLIT: $this->_current = preg_split( $this->_regex, $subject, null, $this->_pregFlags ); $out = is_array($this->_current) && 1 < count($this->_current); break; case self::REPLACE: $numberOfReplacement = 0; $result = preg_replace( $this->_regex, $this->replacement, $subject, -1, $numberOfReplacement ); if (null === $result || 0 === $numberOfReplacement) { $out = false; break; } if (0 !== $useKey) { $this->_key = $result; $out = true; break; } $this->_current = $result; $out = true; break; default: $out = false; break; } if (0 !== ($this->_flags & self::INVERT_MATCH)) { return false === $out; } return $out; } public function key() { return $this->_key; } public function current() { return $this->_current; } public function setMode($mode) { if ($mode < self::MATCH || $mode > self::REPLACE) { throw new \InvalidArgumentException( 'Illegal mode ' . $mode . '.' ); } $this->_mode = $mode; return; } public function setFlags($flags) { $this->_flags = $flags; return; } public function setPregFlags($pregFlags) { $this->_pregFlags = $pregFlags; return; } public function getRegex() { return $this->_regex; } public function getMode() { return $this->_mode; } public function getFlags() { return $this->_flags; } public function getPregFlags() { return $this->_pregFlags; } } _iterator = $iterator; return; } public function getInnerIterator() { return $this->_iterator; } public function current() { return $this->_current; } public function key() { return $this->_key; } public function next() { $innerIterator = $this->getInnerIterator(); $this->_valid = $innerIterator->valid(); if (false === $this->_valid) { return; } $this->_key = $innerIterator->key(); $this->_current = $innerIterator->current(); return $innerIterator->next(); } public function rewind() { $out = $this->getInnerIterator()->rewind(); $this->next(); return $out; } public function valid() { return $this->_valid; } public function hasNext() { return $this->getInnerIterator()->valid(); } public function getNext() { return $this->getInnerIterator()->current(); } public function getNextKey() { return $this->getInnerIterator()->key(); } } _iterator = $iterator; return; } public function getInnerIterator() { return $this->_iterator; } public function current() { return $this->getInnerIterator()->current(); } public function key() { return $this->getInnerIterator()->key(); } public function next() { $this->_previousKey = $this->key(); $this->_previousCurrent = $this->current(); return $this->getInnerIterator()->next(); } public function rewind() { $this->_previousKey = -1; $this->_previousCurrent = null; return $this->getInnerIterator()->rewind(); } public function valid() { return $this->getInnerIterator()->valid(); } public function hasPrevious() { return -1 !== $this->_previousKey; } public function getPrevious() { return $this->_previousCurrent; } public function getPreviousKey() { return $this->_previousKey; } } _iterator = $iterator; $this->_bufferSize = max($bufferSize, 1); $this->_buffer = new \SplDoublyLinkedList(); return; } public function getInnerIterator() { return $this->_iterator; } protected function getBuffer() { return $this->_buffer; } public function getBufferSize() { return $this->_bufferSize; } public function current() { return $this->getBuffer()->current()[self::BUFFER_VALUE]; } public function key() { return $this->getBuffer()->current()[self::BUFFER_KEY]; } public function next() { $innerIterator = $this->getInnerIterator(); $buffer = $this->getBuffer(); $buffer->next(); if (false === $buffer->valid()) { for ( $bufferSize = count($buffer), $maximumBufferSize = $this->getBufferSize(); $bufferSize >= $maximumBufferSize; --$bufferSize ) { $buffer->shift(); } $innerIterator->next(); $buffer->push([ self::BUFFER_KEY => $innerIterator->key(), self::BUFFER_VALUE => $innerIterator->current() ]); $buffer->setIteratorMode($buffer::IT_MODE_LIFO | $buffer::IT_MODE_KEEP); $buffer->rewind(); $buffer->setIteratorMode($buffer::IT_MODE_FIFO | $buffer::IT_MODE_KEEP); } return; } public function previous() { $this->getBuffer()->prev(); return; } public function rewind() { $innerIterator = $this->getInnerIterator(); $buffer = $this->getBuffer(); $innerIterator->rewind(); if (true === $buffer->isEmpty()) { $buffer->push([ self::BUFFER_KEY => $innerIterator->key(), self::BUFFER_VALUE => $innerIterator->current() ]); } $buffer->rewind(); return; } public function valid() { return $this->getBuffer()->valid() && $this->getInnerIterator()->valid(); } } given( $this->function->class_exists = $class, $this->function->interface_exists = $interface, $this->function->trait_exists = $trait ) ->when($result = SUT::entityExists('foo')) ->then ->boolean($result) ->isTrue(); } public function case_entity_exists_with_class() { return $this->_entity_exists_with_xxx(true, false, false); } public function case_entity_exists_with_interface() { return $this->_entity_exists_with_xxx(false, true, false); } public function case_entity_exists_with_trait() { return $this->_entity_exists_with_xxx(false, false, true); } public function case_entity_does_not_exists() { $this ->given( $this->function->class_exists = false, $this->function->interface_exists = false, $this->function->trait_exists = false ) ->when($result = SUT::entityExists('foo')) ->then ->boolean($result) ->isFalse(); } public function case_get_entity_shortest_name() { $this ->when($result = SUT::getEntityShortestName('Foo\Bar\Bar')) ->then ->string($result) ->isEqualTo('Foo\Bar'); } public function case_get_entity_shortest_name_with_already_the_shortest() { $this ->when($result = SUT::getEntityShortestName('Foo\Bar')) ->then ->string($result) ->isEqualTo('Foo\Bar'); } public function case_get_entity_shortest_name_with_no_namespace() { $this ->when($result = SUT::getEntityShortestName('Foo')) ->then ->string($result) ->isEqualTo('Foo'); } public function case_is_keyword() { $this ->given( $keywords = [ '__HALT_COMPILER', 'abstract', 'and', 'array', 'as', 'bool', 'break', 'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'false', 'final', 'float', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'int', 'interface', 'isset', 'list', 'mixed', 'namespace', 'new', 'null', 'numeric', 'object', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'resource', 'return', 'static', 'string', 'switch', 'throw', 'trait', 'true', 'try', 'unset', 'use', 'var', 'void', 'while', 'xor', 'yield', '__CLASS__', '__DIR__', '__FILE__', '__FUNCTION__', '__LINE__', '__METHOD__', '__NAMESPACE__', '__TRAIT__' ] ) ->when(function () use ($keywords) { foreach ($keywords as $keyword) { $this ->boolean(SUT::isKeyword($keyword)) ->isTrue(); } }); } public function case_is_identifier() { $this ->given($_identifier = $this->realdom->regex('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x80-\xff]*$#')) ->when(function () use ($_identifier) { foreach ($this->sampleMany($_identifier, 1000) as $identifier) { $this ->boolean(SUT::isIdentifier($identifier)) ->isTrue(); } }); } public function case_register_shutdown_function() { $self = $this; $this ->given( $callable = function () { }, $this->function->register_shutdown_function = function ($_callable) use (&$called, $self, &$callable) { $called = true; $self ->variable($_callable) ->isEqualTo($callable); return true; } ) ->when($result = SUT::registerShutdownFunction($callable)) ->then ->boolean($result) ->isTrue(); } public function case_get_php_binary_with_constant() { $this ->given($this->constant->PHP_BINARY = '/foo/php') ->when($result = SUT::getPHPBinary()) ->then ->string($result) ->isEqualTo('/foo/php'); } public function case_get_php_binary_with_server() { $this ->given( $this->function->defined = false, $_SERVER['_'] = '/bar/php' ) ->when($result = SUT::getPHPBinary()) ->then ->string($result) ->isEqualTo('/bar/php'); } public function case_get_php_binary_with_bin_directory() { unset($_SERVER['_']); $this ->given( $this->function->defined = false, $this->function->file_exists = true, $this->function->realpath = '/baz/php' ) ->when($result = SUT::getPHPBinary()) ->then ->string($result) ->isEqualTo('/baz/php'); } public function case_uuid() { $this ->given($this->function->mt_rand = 42) ->when($result = SUT::uuid()) ->then ->string($result) ->isEqualTo('002a002a-002a-402a-802a-002a002a002a'); } public function case_uuid_all_differents() { $this ->when(function () { $uuids = []; for ($i = 0; $i < 10000; ++$i) { $uuids[] = SUT::uuid(); } $this ->integer(count($uuids)) ->isEqualTo(count(array_unique($uuids))); }); } } when($result = new SUT('strtoupper')) ->then ->string($result('foo')) ->isEqualTo('FOO') ->string($result->getValidCallback()) ->isEqualTo('strtoupper') ->string($result->getHash()) ->isEqualTo('function#strtoupper') ->isEqualTo($result . '') ->object($reflection = $result->getReflection()) ->isInstanceOf('ReflectionFunction') ->string($reflection->getName()) ->isEqualTo('strtoupper'); } public function case_form_class___method() { $this ->when($result = new SUT(__CLASS__ . '::strtoupper')) ->then ->string($result('foo')) ->isEqualTo('FOO') ->array($result->getValidCallback()) ->isEqualTo([__CLASS__, 'strtoupper']) ->string($result->getHash()) ->isEqualTo('class#' . __CLASS__ . '::strtoupper') ->isEqualTo($result . '') ->object($reflection = $result->getReflection()) ->isInstanceOf('ReflectionMethod') ->string($reflection->getName()) ->isEqualTo('strtoupper'); } public function case_form_class_method() { $this ->when($result = new SUT(__CLASS__, 'strtoupper')) ->then ->string($result('foo')) ->isEqualTo('FOO') ->array($result->getValidCallback()) ->isEqualTo([__CLASS__, 'strtoupper']) ->string($result->getHash()) ->isEqualTo('class#' . __CLASS__ . '::strtoupper') ->isEqualTo($result . '') ->object($reflection = $result->getReflection()) ->isInstanceOf('ReflectionMethod') ->string($reflection->getName()) ->isEqualTo('strtoupper'); } public function case_form_object_method() { $this ->when($result = new SUT($this, 'strtolower')) ->then ->string($result('FOO')) ->isEqualTo('foo') ->array($result->getValidCallback()) ->isEqualTo([$this, 'strtolower']) ->string($result->getHash()) ->matches( '/^object\([^:]+\)#' . preg_quote(__CLASS__) . '::strtolower$/' ) ->isEqualTo($result . '') ->object($reflection = $result->getReflection()) ->isInstanceOf('ReflectionMethod') ->string($reflection->getName()) ->isEqualTo('strtolower'); } public function case_form_object_invoke() { $this ->when($result = new SUT($this)) ->then ->string($result('foo')) ->isEqualTo('FOO') ->array($result->getValidCallback()) ->isEqualTo([$this, '__invoke']) ->string($result->getHash()) ->matches( '/^object\([^:]+\)#' . preg_quote(__CLASS__) . '::__invoke$/' ) ->isEqualTo($result . '') ->object($reflection = $result->getReflection()) ->isInstanceOf('ReflectionMethod') ->string($reflection->getName()) ->isEqualTo('__invoke'); } public function case_form_closure() { $this ->given( $closure = function ($string) { return strtoupper($string); } ) ->when($result = new SUT($closure)) ->then ->string($result('foo')) ->isEqualTo('FOO') ->object($result->getValidCallback()) ->isIdenticalTo($closure) ->string($result->getHash()) ->matches('/^closure\([^:]+\)$/') ->isEqualTo($result . '') ->object($reflection = $result->getReflection()) ->isInstanceOf('ReflectionFunction') ->string($reflection->getName()) ->isEqualTo('Hoa\Consistency\Test\Unit\{closure}'); } public function case_form_array_of_class_method() { $this ->when($result = new SUT([__CLASS__, 'strtoupper'])) ->then ->string($result('foo')) ->isEqualTo('FOO') ->array($result->getValidCallback()) ->isEqualTo([__CLASS__, 'strtoupper']) ->string($result->getHash()) ->isEqualTo('class#' . __CLASS__ . '::strtoupper') ->isEqualTo($result . '') ->object($reflection = $result->getReflection()) ->isInstanceOf('ReflectionMethod') ->string($reflection->getName()) ->isEqualTo('strtoupper'); } public function case_form_array_of_object_method() { $this ->when($result = new SUT([$this, 'strtolower'])) ->then ->string($result('FOO')) ->isEqualTo('foo') ->array($result->getValidCallback()) ->isEqualTo([$this, 'strtolower']) ->string($result->getHash()) ->matches( '/^object\([^:]+\)#' . preg_quote(__CLASS__) . '::strtolower$/' ) ->isEqualTo($result . '') ->object($reflection = $result->getReflection()) ->isInstanceOf('ReflectionMethod') ->string($reflection->getName()) ->isEqualTo('strtolower'); } public function case_form_able_not_a_string() { $this ->exception(function () { new SUT(__CLASS__, 123); }) ->isInstanceOf('Hoa\Consistency\Exception'); } public function case_form_function_not_defined() { $this ->exception(function () { new SUT('__hoa_test_undefined_function__'); }) ->isInstanceOf('Hoa\Consistency\Exception'); } public function case_form_able_cannot_be_deduced() { $this ->given($this->function->method_exists = false) ->exception(function () { new SUT($this); }) ->isInstanceOf('Hoa\Consistency\Exception'); } public function case_invoke() { $this ->given( $callable = new SUT( function ($x, $y, $z) { return [$x, $y, $z]; } ) ) ->when($result = $callable(7, [4.2], 'foo')) ->then ->array($result) ->isEqualTo([7, [4.2], 'foo']); } public function case_distribute_arguments() { $this ->given( $callable = new SUT( function ($x, $y, $z) { return [$x, $y, $z]; } ) ) ->when($result = $callable->distributeArguments([7, [4.2], 'foo'])) ->then ->array($result) ->isEqualTo([7, [4.2], 'foo']); } protected function _get_valid_callback_stream_xxx($argument, $method) { $this ->given( $stream = new \Mock\Hoa\Stream\IStream\Out(), $arguments = [$argument], $xcallable = new SUT($stream) ) ->when($result = $xcallable->getValidCallback($arguments)) ->then ->array($result) ->isEqualTo([$stream, $method]); } public function case_get_valid_callback_stream_character() { return $this->_get_valid_callback_stream_xxx('f', 'writeCharacter'); } public function case_get_valid_callback_stream_string() { return $this->_get_valid_callback_stream_xxx('foo', 'writeString'); } public function case_get_valid_callback_stream_boolean() { return $this->_get_valid_callback_stream_xxx(true, 'writeBoolean'); } public function case_get_valid_callback_stream_integer() { return $this->_get_valid_callback_stream_xxx(7, 'writeInteger'); } public function case_get_valid_callback_stream_array() { return $this->_get_valid_callback_stream_xxx([4, 2], 'writeArray'); } public function case_get_valid_callback_stream_float() { return $this->_get_valid_callback_stream_xxx(4.2, 'writeFloat'); } public function case_get_valid_callback_stream_other() { return $this->_get_valid_callback_stream_xxx($this, 'writeAll'); } public static function strtoupper($string) { return strtoupper($string); } public function strtolower($string) { return strtolower($string); } public function __invoke($string) { return strtoupper($string); } public function __toString() { return 'hello'; } } when($result = new SUT('foo', 0)) ->then ->object($result) ->isInstanceOf('Hoa\Exception\Exception'); } } given( $autoloader = new SUT(), $prefix = 'Foo\Bar\\', $baseDirectoryA = 'Source/Foo/Bar/', $baseDirectoryB = 'Source/Foo/Bar/' ) ->when( $autoloader->addNamespace($prefix, $baseDirectoryA), $result = $autoloader->addNamespace($prefix, $baseDirectoryB) ) ->then ->boolean($autoloader->hasBaseDirectory($prefix)) ->isTrue() ->array($autoloader->getBaseDirectories($prefix)) ->isEqualTo([ $baseDirectoryB, $baseDirectoryA ]); } public function case_add_namespace_append() { $this ->given( $autoloader = new SUT(), $prefix = 'Foo\Bar\\', $baseDirectoryA = 'Source/Foo/Bar/', $baseDirectoryB = 'Source/Foo/Bar/' ) ->when( $autoloader->addNamespace($prefix, $baseDirectoryA), $result = $autoloader->addNamespace($prefix, $baseDirectoryB) ) ->then ->boolean($autoloader->hasBaseDirectory($prefix)) ->isTrue() ->array($autoloader->getBaseDirectories($prefix)) ->isEqualTo([ $baseDirectoryA, $baseDirectoryB ]); } public function case_add_namespace_with_invalid_prefix() { $this ->given( $autoloader = new SUT(), $prefix = '\\\\Foo\Bar', $baseDirectory = 'Source/Foo/Bar/' ) ->when($result = $autoloader->addNamespace($prefix, $baseDirectory)) ->then ->boolean($autoloader->hasBaseDirectory('Foo\Bar\\')) ->isTrue() ->array($autoloader->getBaseDirectories('Foo\Bar\\')) ->isEqualTo([$baseDirectory]); } public function case_add_namespace_with_invalid_base_directory() { $this ->given( $autoloader = new SUT(), $prefix = 'Foo\Bar\\', $baseDirectory = 'Source/Foo/Bar' ) ->when($result = $autoloader->addNamespace($prefix, $baseDirectory)) ->then ->boolean($autoloader->hasBaseDirectory('Foo\Bar\\')) ->isTrue() ->array($autoloader->getBaseDirectories('Foo\Bar\\')) ->isEqualTo(['Source/Foo/Bar/']); } public function case_add_namespace_with_crazy_invalid_base_directory() { $this ->given( $autoloader = new SUT(), $prefix = 'Foo\Bar\\', $baseDirectory = 'Source/Foo/Bar/////' ) ->when($result = $autoloader->addNamespace($prefix, $baseDirectory)) ->then ->boolean($autoloader->hasBaseDirectory('Foo\Bar\\')) ->isTrue() ->array($autoloader->getBaseDirectories('Foo\Bar\\')) ->isEqualTo(['Source/Foo/Bar/']); } public function case_load() { $this ->given( $autoloader = new \Mock\Hoa\Consistency\Autoloader(), $autoloader->addNamespace('Foo\Bar\\', 'Source/Foo/Bar/'), $this->calling($autoloader)->requireFile = function ($file) { return $file; } ) ->when($result = $autoloader->load('Foo\Bar\Baz\Qux')) ->then ->string($result) ->isEqualTo('Source/Foo/Bar/Baz/Qux.php'); } public function case_load_invalid_entity() { $this ->given($autoloader = new SUT()) ->when($result = $autoloader->load('Foo')) ->then ->variable($result) ->isNull(); } public function case_load_flex_entity() { $self = $this; $this ->given( $autoloader = new \Mock\Hoa\Consistency\Autoloader(), $autoloader->addNamespace('Foo\Bar\\', 'Source/Foo/'), $this->calling($autoloader)->runAutoloaderStack = function ($entity) use ($self, &$called) { $called = true; $self ->string($entity) ->isEqualTo('Foo\Bar\Baz\Baz'); return; }, $autoloader->register() ) ->when($result = $autoloader->load('Foo\Bar\Baz')) ->then ->variable($result) ->isNull() ->boolean($called) ->isTrue(); } public function case_load_unmapped_flex_entity() { $self = $this; $this ->given( $autoloader = new \Mock\Hoa\Consistency\Autoloader(), $this->calling($autoloader)->runAutoloaderStack = function ($entity) use ($self, &$called) { $called = true; return; }, $autoloader->register() ) ->when($result = $autoloader->load('Foo\Bar\Baz')) ->then ->variable($result) ->isNull() ->variable($called) ->isNull(); } public function case_require_existing_file() { $this ->given( $autoloader = new SUT(), $this->function->file_exists = true, $constantName = 'HOA_TEST_' . uniqid(), $filename = 'hoa://Test/Vfs/Foo?type=file', file_put_contents($filename, 'when($result = $autoloader->requireFile($filename)) ->then ->boolean($result) ->isTrue() ->string(constant($constantName)) ->isEqualTo('BAR'); } public function case_require_not_existing_file() { $this ->given( $autoloader = new SUT(), $this->function->file_exists = false ) ->when($result = $autoloader->requireFile('/hoa/flatland')) ->then ->boolean($result) ->isFalse(); } public function case_has_not_base_directory() { $this ->given($autoloader = new SUT()) ->when($result = $autoloader->hasBaseDirectory('foo')) ->then ->boolean($result) ->isFalse(); } public function case_get_base_undeclared_namespace_prefix() { $this ->given($autoloader = new SUT()) ->when($result = $autoloader->getBaseDirectories('foo')) ->then ->array($result) ->isEmpty(); } public function case_dnew() { $this ->given($classname = 'Hoa\Consistency\Autoloader') ->when($result = SUT::dnew($classname)) ->then ->object($result) ->isInstanceOf($classname); } public function case_dnew_unknown_class() { $this ->given($this->function->spl_autoload_call = null) ->exception(function () { SUT::dnew('Foo'); }) ->isInstanceOf('ReflectionException'); } public function case_get_loaded_classes() { $this ->given( $declaredClasses = get_declared_classes(), $this->function->get_declared_classes = $declaredClasses ) ->when($result = SUT::getLoadedClasses()) ->then ->array($result) ->isEqualTo($declaredClasses); } public function case_register() { $self = $this; $this ->given($autoloader = new SUT()) ->when($result = $autoloader->register()) ->then ->boolean($result) ->isTrue() ->array($autoloader->getRegisteredAutoloaders()) ->isEqualTo(spl_autoload_functions()); } public function case_unregister() { $this ->given( $autoloader = new SUT(), $oldRegisteredAutoloaders = $autoloader->getRegisteredAutoloaders() ) ->when($result = $autoloader->register()) ->then ->boolean($result) ->isTrue() ->integer(count($autoloader->getRegisteredAutoloaders())) ->isEqualTo(count($oldRegisteredAutoloaders) + 1) ->when($result = $autoloader->unregister()) ->then ->boolean($result) ->isTrue() ->array($autoloader->getRegisteredAutoloaders()) ->isEqualTo($oldRegisteredAutoloaders); } } = $count) { return $entityName; } if ($parts[$count - 2] === $parts[$count - 1]) { return implode('\\', array_slice($parts, 0, -1)); } return $entityName; } public static function flexEntity($entityName) { return class_alias( $entityName, static::getEntityShortestName($entityName), false ); } public static function isKeyword($word) { static $_list = [ '__halt_compiler', 'abstract', 'and', 'array', 'as', 'bool', 'break', 'callable', 'case', 'catch', 'class', 'clone', 'const', 'continue', 'declare', 'default', 'die', 'do', 'echo', 'else', 'elseif', 'empty', 'enddeclare', 'endfor', 'endforeach', 'endif', 'endswitch', 'endwhile', 'eval', 'exit', 'extends', 'false', 'final', 'float', 'for', 'foreach', 'function', 'global', 'goto', 'if', 'implements', 'include', 'include_once', 'instanceof', 'insteadof', 'int', 'interface', 'isset', 'list', 'mixed', 'namespace', 'new', 'null', 'numeric', 'object', 'or', 'print', 'private', 'protected', 'public', 'require', 'require_once', 'resource', 'return', 'static', 'string', 'switch', 'throw', 'trait', 'true', 'try', 'unset', 'use', 'var', 'void', 'while', 'xor', 'yield', '__class__', '__dir__', '__file__', '__function__', '__line__', '__method__', '__namespace__', '__trait__' ]; return in_array(strtolower($word), $_list); } public static function isIdentifier($id) { return 0 !== preg_match( '#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x80-\xff]*$#', $id ); } public static function registerShutdownFunction($callable) { return register_shutdown_function($callable); } public static function getPHPBinary() { if (defined('PHP_BINARY')) { return PHP_BINARY; } if (isset($_SERVER['_'])) { return $_SERVER['_']; } foreach (['', '.exe'] as $extension) { if (file_exists($_ = PHP_BINDIR . DS . 'php' . $extension)) { return realpath($_); } } return null; } public static function uuid() { return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) ); } } } namespace { if (70000 > PHP_VERSION_ID && false === interface_exists('Throwable', false)) { interface Throwable { public function getMessage(); public function getCode(); public function getFile(); public function getLine(); public function getTrace(); public function getPrevious(); public function getTraceAsString(); public function __toString(); } } if (50600 > PHP_VERSION_ID) { $define = function ($constantName, $constantValue, $case = false) { if (!defined($constantName)) { return define($constantName, $constantValue, $case); } return false; }; $define('STREAM_CRYPTO_METHOD_TLSv1_0_SERVER', 8); $define('STREAM_CRYPTO_METHOD_TLSv1_1_SERVER', 16); $define('STREAM_CRYPTO_METHOD_TLSv1_2_SERVER', 32); $define('STREAM_CRYPTO_METHOD_ANY_SERVER', 62); $define('STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT', 9); $define('STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT', 17); $define('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT', 33); $define('STREAM_CRYPTO_METHOD_ANY_CLIENT', 63); } if (!function_exists('curry')) { function curry($callable) { $arguments = func_get_args(); array_shift($arguments); $ii = array_keys($arguments, …, true); return function () use ($callable, $arguments, $ii) { return call_user_func_array( $callable, array_replace($arguments, array_combine($ii, func_get_args())) ); }; } } Hoa\Consistency\Consistency::flexEntity('Hoa\Consistency\Consistency'); } _callback = $call; return; } if (!is_string($able)) { throw new Exception( 'Bad callback form; the able part must be a string.', 0 ); } if ('' === $able) { if (is_string($call)) { if (false === strpos($call, '::')) { if (!function_exists($call)) { throw new Exception( 'Bad callback form; function %s does not exist.', 1, $call ); } $this->_callback = $call; return; } list($call, $able) = explode('::', $call); } elseif (is_object($call)) { if ($call instanceof Stream\IStream\Out) { $able = null; } elseif (method_exists($call, '__invoke')) { $able = '__invoke'; } else { throw new Exception( 'Bad callback form; an object but without a known ' . 'method.', 2 ); } } elseif (is_array($call) && isset($call[0])) { if (!isset($call[1])) { return $this->__construct($call[0]); } return $this->__construct($call[0], $call[1]); } else { throw new Exception( 'Bad callback form.', 3 ); } } $this->_callback = [$call, $able]; return; } public function __invoke() { $arguments = func_get_args(); $valid = $this->getValidCallback($arguments); return call_user_func_array($valid, $arguments); } public function distributeArguments(array $arguments) { return call_user_func_array([$this, '__invoke'], $arguments); } public function getValidCallback(array &$arguments = []) { $callback = $this->_callback; $head = null; if (isset($arguments[0])) { $head = &$arguments[0]; } if (null !== $head && is_array($callback) && null === $callback[1]) { if ($head instanceof Event\Bucket) { $head = $head->getData(); } switch ($type = gettype($head)) { case 'string': if (1 === strlen($head)) { $method = 'writeCharacter'; } else { $method = 'writeString'; } break; case 'boolean': case 'integer': case 'array': $method = 'write' . ucfirst($type); break; case 'double': $method = 'writeFloat'; break; default: $method = 'writeAll'; $head = $head . "\n"; } $callback[1] = $method; } return $callback; } public function getHash() { if (null !== $this->_hash) { return $this->_hash; } $_ = &$this->_callback; if (is_string($_)) { return $this->_hash = 'function#' . $_; } if (is_array($_)) { return $this->_hash = (is_object($_[0]) ? 'object(' . spl_object_hash($_[0]) . ')' . '#' . get_class($_[0]) : 'class#' . $_[0]) . '::' . (null !== $_[1] ? $_[1] : '???'); } return $this->_hash = 'closure(' . spl_object_hash($_) . ')'; } public function getReflection() { $arguments = func_get_args(); $valid = $this->getValidCallback($arguments); if (is_string($valid)) { return new \ReflectionFunction($valid); } if ($valid instanceof \Closure) { return new \ReflectionFunction($valid); } if (is_array($valid)) { if (is_string($valid[0])) { if (false === method_exists($valid[0], $valid[1])) { return new \ReflectionClass($valid[0]); } return new \ReflectionMethod($valid[0], $valid[1]); } $object = new \ReflectionObject($valid[0]); if (null === $valid[1]) { return $object; } return $object->getMethod($valid[1]); } } public function __toString() { return $this->getHash(); } } _namespacePrefixesToBaseDirectories[$prefix])) { $this->_namespacePrefixesToBaseDirectories[$prefix] = []; } if (true === $prepend) { array_unshift( $this->_namespacePrefixesToBaseDirectories[$prefix], $baseDirectory ); } else { array_push( $this->_namespacePrefixesToBaseDirectories[$prefix], $baseDirectory ); } return; } public function load($entity) { $entityPrefix = $entity; $hasBaseDirectory = false; while (false !== $pos = strrpos($entityPrefix, '\\')) { $currentEntityPrefix = substr($entity, 0, $pos + 1); $entityPrefix = rtrim($currentEntityPrefix, '\\'); $entitySuffix = substr($entity, $pos + 1); $entitySuffixAsPath = str_replace('\\', '/', $entitySuffix); if (false === $this->hasBaseDirectory($currentEntityPrefix)) { continue; } $hasBaseDirectory = true; foreach ($this->getBaseDirectories($currentEntityPrefix) as $baseDirectory) { $file = $baseDirectory . $entitySuffixAsPath . '.php'; if (false !== $this->requireFile($file)) { return $file; } } } if (true === $hasBaseDirectory && $entity === Consistency::getEntityShortestName($entity) && false !== $pos = strrpos($entity, '\\')) { return $this->runAutoloaderStack( $entity . '\\' . substr($entity, $pos + 1) ); } return null; } public function requireFile($filename) { if (false === file_exists($filename)) { return false; } require $filename; return true; } public function hasBaseDirectory($namespacePrefix) { return isset($this->_namespacePrefixesToBaseDirectories[$namespacePrefix]); } public function getBaseDirectories($namespacePrefix) { if (false === $this->hasBaseDirectory($namespacePrefix)) { return []; } return $this->_namespacePrefixesToBaseDirectories[$namespacePrefix]; } public static function getLoadedClasses() { return get_declared_classes(); } public function runAutoloaderStack($entity) { return spl_autoload_call($entity); } public function register($prepend = false) { return spl_autoload_register([$this, 'load'], true, $prepend); } public function unregister() { return spl_autoload_unregister([$this, 'load']); } public function getRegisteredAutoloaders() { return spl_autoload_functions(); } public static function dnew($classname, array $arguments = []) { $classname = ltrim($classname, '\\'); if (false === Consistency::entityExists($classname, false)) { spl_autoload_call($classname); } $class = new \ReflectionClass($classname); if (empty($arguments) || false === $class->hasMethod('__construct')) { return $class->newInstance(); } return $class->newInstanceArgs($arguments); } } $autoloader = new Autoloader(); $autoloader->addNamespace('Hoa', dirname(__DIR__)); $autoloader->register(); 'Content-Type', 'CONTENT_LENGTH' => 'Content-Length', 'CONTENT_MD5' => 'Content-Md5', ); foreach ($_SERVER as $key => $value) { if (substr($key, 0, 5) === 'HTTP_') { $key = substr($key, 5); if (!isset($copy_server[$key]) || !isset($_SERVER[$key])) { $key = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', $key)))); $headers[$key] = $value; } } elseif (isset($copy_server[$key])) { $headers[$copy_server[$key]] = $value; } } if (!isset($headers['Authorization'])) { if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { $headers['Authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; } elseif (isset($_SERVER['PHP_AUTH_USER'])) { $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : ''; $headers['Authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass); } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) { $headers['Authorization'] = $_SERVER['PHP_AUTH_DIGEST']; } } return $headers; } } wantTo('create article'); * $I->click('New Article'); * $I->fillField('Title', sq('Article')); * $I->fillField('Body', 'Demo article with Lorem Ipsum'); * $I->click('save'); * $I->see(sq('Article') ,'#articles') * ``` * * Populating Database: * * ``` php * haveInDatabase('users', array('login' => sq("user$i"), 'email' => sq("user$i").'@email.com'); * } * ?> * ``` * * Cest Suite tests: * * ``` php * createUser(sqs('user') . '@mailserver.com', sqs('login'), sqs('pwd')); * } * * public function checkEmail(AcceptanceTester $I) * { * $I->seeInEmailTo(sqs('user') . '@mailserver.com', sqs('login')); * } * * public function removeUser(AcceptanceTester $I) * { * $I->removeUser(sqs('user') . '@mailserver.com'); * } * } * ?> * ``` * * ### Config * * By default produces unique string with param as a prefix: * * ``` * sq('user') => 'user_876asd8as87a' * ``` * * This behavior can be configured using `prefix` config param. * * Old style sequences: * * ```yaml * Sequence: * prefix: '_' * ``` * * Using id param inside prefix: * * ```yaml * Sequence: * prefix: '{id}.' * ``` */ class Sequence extends CodeceptionModule { public static $hash = []; public static $suiteHash = []; public static $prefix = ''; protected $config = ['prefix' => '{id}_']; public function _initialize() { static::$prefix = $this->config['prefix']; } public function _after(TestInterface $t) { self::$hash = []; } public function _afterSuite() { self::$suiteHash = []; } } if (!function_exists('sq') && !function_exists('sqs')) { require_once __DIR__ . '/../Util/sq.php'; } else { throw new ModuleException('Codeception\Module\Sequence', "function 'sq' and 'sqs' already defined"); } client = new \MongoDB\Client($dsn, $options); $this->dbh = $this->client->selectDatabase($this->dbName); } catch (\MongoDB\Driver\Exception $e) { throw new ModuleException($this, sprintf('Failed to open Mongo connection: %s', $e->getMessage())); } } /** * Connect to the Mongo server using the legacy mongo extension. */ protected function setupMongo($dsn, $options) { try { $this->client = new \MongoClient($dsn, $options); $this->dbh = $this->client->selectDB($this->dbName); } catch (\MongoConnectionException $e) { throw new ModuleException($this, sprintf('Failed to open Mongo connection: %s', $e->getMessage())); } } /** * Clean up the Mongo database using the MongoDB extension. */ protected function cleanupMongoDB() { try { $this->dbh->drop(); } catch (\MongoDB\Driver\Exception $e) { throw new \Exception(sprintf('Failed to drop the DB: %s', $e->getMessage())); } } /** * Clean up the Mongo database using the legacy Mongo extension. */ protected function cleanupMongo() { try { $list = $this->dbh->listCollections(); } catch (\MongoException $e) { throw new \Exception(sprintf('Failed to list collections of the DB: %s', $e->getMessage())); } foreach ($list as $collection) { try { $collection->drop(); } catch (\MongoException $e) { throw new \Exception(sprintf('Failed to drop collection: %s', $e->getMessage())); } } } /** * $dsn has to contain db_name after the host. E.g. "mongodb://localhost:27017/mongo_test_db" * * @static * * @param $dsn * @param $user * @param $password * * @throws ModuleConfigException * @throws \Exception */ public function __construct($dsn, $user, $password) { $this->legacy = extension_loaded('mongodb') === false && class_exists('\\MongoClient') && strpos(\MongoClient::VERSION, 'mongofill') === false; /* defining DB name */ $this->dbName = preg_replace('/\?.*/', '', substr($dsn, strrpos($dsn, '/') + 1)); if (strlen($this->dbName) == 0) { throw new ModuleConfigException($this, 'Please specify valid $dsn with DB name after the host:port'); } /* defining host */ if (strpos($dsn, 'mongodb://') !== false) { $this->host = str_replace('mongodb://', '', preg_replace('/\?.*/', '', $dsn)); } else { $this->host = $dsn; } $this->host = rtrim(str_replace($this->dbName, '', $this->host), '/'); $options = [ 'connect' => true ]; if ($user && $password) { $options += [ 'username' => $user, 'password' => $password ]; } $this->{$this->legacy ? 'setupMongo' : 'setupMongoDB'}($dsn, $options); $this->dsn = $dsn; $this->user = $user; $this->password = $password; } /** * @static * * @param $dsn * @param $user * @param $password * * @return MongoDb */ public static function create($dsn, $user, $password) { return new MongoDb($dsn, $user, $password); } public function cleanup() { $this->{$this->legacy ? 'cleanupMongo' : 'cleanupMongoDB'}(); } /** * dump file has to be a javascript document where one can use all the mongo shell's commands * just FYI: this file can be easily created be RockMongo's export button * * @param $dumpFile */ public function load($dumpFile) { $cmd = sprintf( 'mongo %s %s%s', $this->host . '/' . $this->dbName, $this->createUserPasswordCmdString(), escapeshellarg($dumpFile) ); shell_exec($cmd); } public function loadFromMongoDump($dumpFile) { list($host, $port) = $this->getHostPort(); $cmd = sprintf( "mongorestore %s --host %s --port %s -d %s %s %s", $this->quiet, $host, $port, $this->dbName, $this->createUserPasswordCmdString(), escapeshellarg($dumpFile) ); shell_exec($cmd); } public function loadFromTarGzMongoDump($dumpFile) { list($host, $port) = $this->getHostPort(); $getDirCmd = sprintf( "tar -tf %s | awk 'BEGIN { FS = \"/\" } ; { print $1 }' | uniq", escapeshellarg($dumpFile) ); $dirCountCmd = $getDirCmd . ' | wc -l'; if (trim(shell_exec($dirCountCmd)) !== '1') { throw new ModuleException( $this, 'Archive MUST contain single directory with db dump' ); } $dirName = trim(shell_exec($getDirCmd)); $cmd = sprintf( 'tar -xzf %s && mongorestore %s --host %s --port %s -d %s %s %s && rm -r %s', escapeshellarg($dumpFile), $this->quiet, $host, $port, $this->dbName, $this->createUserPasswordCmdString(), $dirName, $dirName ); shell_exec($cmd); } private function createUserPasswordCmdString() { if ($this->user && $this->password) { return sprintf( '--username %s --password %s ', $this->user, $this->password ); } return ''; } public function getDbh() { return $this->dbh; } public function setDatabase($dbName) { $this->dbh = $this->client->{$this->legacy ? 'selectDB' : 'selectDatabase'}($dbName); } public function getDbHash() { $result = $this->dbh->command(['dbHash' => 1]); if (!is_array($result)) { $result = iterator_to_array($result); } return isset($result[0]->md5) ? $result[0]->md5 : null; } /** * Determine if this driver is using the legacy extension or not. * * @return bool */ public function isLegacy() { return $this->legacy; } private function getHostPort() { $hostPort = explode(':', $this->host); if (count($hostPort) === 2) { return $hostPort; } if (count($hostPort) === 1) { return [$hostPort[0], self::DEFAULT_PORT]; } throw new ModuleException($this, '$dsn MUST be like (mongodb://):/'); } public function setQuiet($quiet) { $this->quiet = $quiet ? '--quiet' : ''; } } .tar.gz ```. * Just put it in ``` tests/_data ``` dir (by default) and specify path to it in config. * Next time after database is cleared all your data will be restored from dump. * The DB preparation should as following: * - clean database * - system collection system.users should contain the user which will be authenticated while script performs DB operations * * Connection is done by MongoDb driver, which is stored in Codeception\Lib\Driver namespace. * Check out the driver if you get problems loading dumps and cleaning databases. * * HINT: This module can be used with [Mongofill](https://github.com/mongofill/mongofill) library which is Mongo client written in PHP without extension. * * ## Status * * * Maintainer: **judgedim**, **davert** * * Stability: **beta** * * Contact: davert@codeception.com * * *Please review the code of non-stable modules and provide patches if you have issues.* * * ## Config * * * dsn *required* - MongoDb DSN with the db name specified at the end of the host after slash * * user *required* - user to access database * * password *required* - password * * dump_type *required* - type of dump. * One of 'js' (MongoDb::DUMP_TYPE_JS), 'mongodump' (MongoDb::DUMP_TYPE_MONGODUMP) or 'mongodump-tar-gz' (MongoDb::DUMP_TYPE_MONGODUMP_TAR_GZ). * default: MongoDb::DUMP_TYPE_JS). * * dump - path to database dump * * populate: true - should the dump be loaded before test suite is started. * * cleanup: true - should the dump be reloaded after each test. * Boolean or 'dirty'. If cleanup is set to 'dirty', the dump is only reloaded if any data has been written to the db during a test. This is * checked using the [dbHash](https://docs.mongodb.com/manual/reference/command/dbHash/) command. * */ class MongoDb extends CodeceptionModule implements RequiresPackage { const DUMP_TYPE_JS = 'js'; const DUMP_TYPE_MONGODUMP = 'mongodump'; const DUMP_TYPE_MONGODUMP_TAR_GZ = 'mongodump-tar-gz'; /** * @api * @var */ public $dbh; /** * @var */ protected $dumpFile; protected $isDumpFileEmpty = true; protected $dbHash; protected $config = [ 'populate' => true, 'cleanup' => true, 'dump' => null, 'dump_type' => self::DUMP_TYPE_JS, 'user' => null, 'password' => null, 'quiet' => false, ]; protected $populated = false; /** * @var \Codeception\Lib\Driver\MongoDb */ public $driver; protected $requiredFields = ['dsn']; public function _initialize() { try { $this->driver = MongoDbDriver::create( $this->config['dsn'], $this->config['user'], $this->config['password'] ); } catch (\MongoConnectionException $e) { throw new ModuleException(__CLASS__, $e->getMessage() . ' while creating Mongo connection'); } // starting with loading dump if ($this->config['populate']) { $this->cleanup(); $this->loadDump(); $this->populated = true; } } private function validateDump() { if ($this->config['dump'] && ($this->config['cleanup'] or ($this->config['populate']))) { if (!file_exists(Configuration::projectDir() . $this->config['dump'])) { throw new ModuleConfigException( __CLASS__, "File with dump doesn't exist.\n Please, check path for dump file: " . $this->config['dump'] ); } $this->dumpFile = Configuration::projectDir() . $this->config['dump']; $this->isDumpFileEmpty = false; if ($this->config['dump_type'] === self::DUMP_TYPE_JS) { $content = file_get_contents($this->dumpFile); $content = trim(preg_replace('%/\*(?:(?!\*/).)*\*/%s', "carview.php?tsp=", $content)); if (!sizeof(explode("\n", $content))) { $this->isDumpFileEmpty = true; } return; } if ($this->config['dump_type'] === self::DUMP_TYPE_MONGODUMP) { if (!is_dir($this->dumpFile)) { throw new ModuleConfigException( __CLASS__, "Dump must be a directory.\n Please, check dump: " . $this->config['dump'] ); } $this->isDumpFileEmpty = true; $dumpDir = dir($this->dumpFile); while (false !== ($entry = $dumpDir->read())) { if ($entry !== '..' && $entry !== '.') { $this->isDumpFileEmpty = false; break; } } $dumpDir->close(); return; } if ($this->config['dump_type'] === self::DUMP_TYPE_MONGODUMP_TAR_GZ) { if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { throw new ModuleConfigException( __CLASS__, "Tar gunzip archives are not supported for Windows systems" ); } if (!preg_match('/(\.tar\.gz|\.tgz)$/', $this->dumpFile)) { throw new ModuleConfigException( __CLASS__, "Dump file must be a valid tar gunzip archive.\n Please, check dump file: " . $this->config['dump'] ); } return; } throw new ModuleConfigException( __CLASS__, '\"dump_type\" must be one of ["' . self::DUMP_TYPE_JS . '", "' . self::DUMP_TYPE_MONGODUMP . '", "' . self::DUMP_TYPE_MONGODUMP_TAR_GZ . '"].' ); } } public function _before(TestInterface $test) { if ($this->shouldCleanup()) { $this->cleanup(); $this->loadDump(); } } public function _after(TestInterface $test) { $this->populated = false; } protected function shouldCleanup() { if ($this->populated) { return false; } return $this->config['cleanup'] === 'dirty' ? ($this->dbHash === null || $this->driver->getDbHash() !== $this->dbHash) : (bool)$this->config['cleanup']; } protected function cleanup() { $dbh = $this->driver->getDbh(); if (!$dbh) { throw new ModuleConfigException( __CLASS__, "No connection to database. Remove this module from config if you don't need database repopulation" ); } try { $this->driver->cleanup(); } catch (\Exception $e) { throw new ModuleException(__CLASS__, $e->getMessage()); } } protected function loadDump() { $this->validateDump(); if ($this->isDumpFileEmpty) { return; } try { if ($this->config['dump_type'] === self::DUMP_TYPE_JS) { $this->driver->load($this->dumpFile); } if ($this->config['dump_type'] === self::DUMP_TYPE_MONGODUMP) { $this->driver->setQuiet($this->config['quiet']); $this->driver->loadFromMongoDump($this->dumpFile); } if ($this->config['dump_type'] === self::DUMP_TYPE_MONGODUMP_TAR_GZ) { $this->driver->setQuiet($this->config['quiet']); $this->driver->loadFromTarGzMongoDump($this->dumpFile); } } catch (\Exception $e) { throw new ModuleException(__CLASS__, $e->getMessage()); } if ($this->config['cleanup'] === 'dirty') { $this->dbHash = $this->driver->getDbHash(); } } /** * Specify the database to use * * ``` php * useDatabase('db_1'); * ``` * * @param $dbName */ public function useDatabase($dbName) { $this->driver->setDatabase($dbName); } /** * Inserts data into collection * * ``` php * haveInCollection('users', array('name' => 'John', 'email' => 'john@coltrane.com')); * $user_id = $I->haveInCollection('users', array('email' => 'john@coltrane.com')); * ``` * * @param $collection * @param array $data */ public function haveInCollection($collection, array $data) { $collection = $this->driver->getDbh()->selectCollection($collection); if ($this->driver->isLegacy()) { $collection->insert($data); return $data['_id']; } $response = $collection->insertOne($data); return (string) $response->getInsertedId(); } /** * Checks if collection contains an item. * * ``` php * seeInCollection('users', array('name' => 'miles')); * ``` * * @param $collection * @param array $criteria */ public function seeInCollection($collection, $criteria = []) { $collection = $this->driver->getDbh()->selectCollection($collection); $res = $collection->count($criteria); \PHPUnit\Framework\Assert::assertGreaterThan(0, $res); } /** * Checks if collection doesn't contain an item. * * ``` php * dontSeeInCollection('users', array('name' => 'miles')); * ``` * * @param $collection * @param array $criteria */ public function dontSeeInCollection($collection, $criteria = []) { $collection = $this->driver->getDbh()->selectCollection($collection); $res = $collection->count($criteria); \PHPUnit\Framework\Assert::assertLessThan(1, $res); } /** * Grabs a data from collection * * ``` php * grabFromCollection('users', array('name' => 'miles')); * ``` * * @param $collection * @param array $criteria * @return array */ public function grabFromCollection($collection, $criteria = []) { $collection = $this->driver->getDbh()->selectCollection($collection); return $collection->findOne($criteria); } /** * Grabs the documents count from a collection * * ``` php * grabCollectionCount('users'); * // or * $count = $I->grabCollectionCount('users', array('isAdmin' => true)); * ``` * * @param $collection * @param array $criteria * @return integer */ public function grabCollectionCount($collection, $criteria = []) { $collection = $this->driver->getDbh()->selectCollection($collection); return $collection->count($criteria); } /** * Asserts that an element in a collection exists and is an Array * * ``` php * seeElementIsArray('users', array('name' => 'John Doe') , 'data.skills'); * ``` * * @param String $collection * @param Array $criteria * @param String $elementToCheck */ public function seeElementIsArray($collection, $criteria = [], $elementToCheck = null) { $collection = $this->driver->getDbh()->selectCollection($collection); $res = $collection->count( array_merge( $criteria, [ $elementToCheck => ['$exists' => true], '$where' => "Array.isArray(this.{$elementToCheck})" ] ) ); if ($res > 1) { throw new \PHPUnit\Framework\ExpectationFailedException( 'Error: you should test against a single element criteria when asserting that elementIsArray' ); } \PHPUnit\Framework\Assert::assertEquals(1, $res, 'Specified element is not a Mongo Object'); } /** * Asserts that an element in a collection exists and is an Object * * ``` php * seeElementIsObject('users', array('name' => 'John Doe') , 'data'); * ``` * * @param String $collection * @param Array $criteria * @param String $elementToCheck */ public function seeElementIsObject($collection, $criteria = [], $elementToCheck = null) { $collection = $this->driver->getDbh()->selectCollection($collection); $res = $collection->count( array_merge( $criteria, [ $elementToCheck => ['$exists' => true], '$where' => "! Array.isArray(this.{$elementToCheck}) && isObject(this.{$elementToCheck})" ] ) ); if ($res > 1) { throw new \PHPUnit\Framework\ExpectationFailedException( 'Error: you should test against a single element criteria when asserting that elementIsObject' ); } \PHPUnit\Framework\Assert::assertEquals(1, $res, 'Specified element is not a Mongo Object'); } /** * Count number of records in a collection * * ``` php * seeNumElementsInCollection('users', 2); * $I->seeNumElementsInCollection('users', 1, array('name' => 'miles')); * ``` * * @param $collection * @param integer $expected * @param array $criteria */ public function seeNumElementsInCollection($collection, $expected, $criteria = []) { $collection = $this->driver->getDbh()->selectCollection($collection); $res = $collection->count($criteria); \PHPUnit\Framework\Assert::assertSame($expected, $res); } /** * Returns list of classes and corresponding packages required for this module */ public function _requires() { return ['MongoDB\Client' => '"mongodb/mongodb": "^1.0"']; } } expectException(MyException::class, function() { * $this->doSomethingBad(); * }); * * $I->expectException(new MyException(), function() { * $this->doSomethingBad(); * }); * ``` * If you want to check message or exception code, you can pass them with exception instance: * ```php * expectException(new MyException("Don't do bad things"), function() { * $this->doSomethingBad(); * }); * ``` * * @deprecated Use expectThrowable() instead * @param \Exception|string $exception * @param callable $callback */ public function expectException($exception, $callback) { Notification::deprecate('Use expectThrowable() instead'); $this->expectThrowable($exception, $callback); } /** * Handles and checks throwables (Exceptions/Errors) called inside the callback function. * Either throwable class name or throwable instance should be provided. * * ```php * expectThrowable(MyThrowable::class, function() { * $this->doSomethingBad(); * }); * * $I->expectThrowable(new MyException(), function() { * $this->doSomethingBad(); * }); * ``` * If you want to check message or throwable code, you can pass them with throwable instance: * ```php * expectThrowable(new MyError("Don't do bad things"), function() { * $this->doSomethingBad(); * }); * ``` * * @param \Throwable|string $throwable * @param callable $callback */ public function expectThrowable($throwable, $callback) { if (is_object($throwable)) { $class = get_class($throwable); $msg = $throwable->getMessage(); $code = $throwable->getCode(); } else { $class = $throwable; $msg = null; $code = null; } try { $callback(); } catch (\Exception $t) { $this->checkThrowable($t, $class, $msg, $code); return; } catch (\Throwable $t) { $this->checkThrowable($t, $class, $msg, $code); return; } $this->fail("Expected throwable of class '$class' to be thrown, but nothing was caught"); } /** * Check if the given throwable matches the expected data, * fail (throws an exception) if it does not. * * @param \Throwable $throwable * @param string $expectedClass * @param string $expectedMsg * @param int $expectedCode */ protected function checkThrowable($throwable, $expectedClass, $expectedMsg, $expectedCode) { if (!($throwable instanceof $expectedClass)) { $this->fail(sprintf( "Exception of class '$expectedClass' expected to be thrown, but class '%s' was caught", get_class($throwable) )); } if (null !== $expectedMsg && $throwable->getMessage() !== $expectedMsg) { $this->fail(sprintf( "Exception of class '$expectedClass' expected to have message '$expectedMsg', but actual message was '%s'", $throwable->getMessage() )); } if (null !== $expectedCode && $throwable->getCode() !== $expectedCode) { $this->fail(sprintf( "Exception of class '$expectedClass' expected to have code '$expectedCode', but actual code was '%s'", $throwable->getCode() )); } $this->assertTrue(true); // increment assertion counter } } testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED); if ($success) { $this->successful++; } if ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE) { $status = "\033[41;37mFAIL\033[0m"; } elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED) { $status = 'Skipped'; } elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE) { $status = 'Incomplete'; } elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_RISKY) { $status = 'Useless'; } elseif ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR) { $status = 'ERROR'; } else { $status = 'Ok'; } if (strlen($name) > 75) { $name = substr($name, 0, 70); } $line = $name . str_repeat('.', 75 - strlen($name)); $line .= $status; $this->write($line . "\n"); } protected function endRun() : void { } public function printResult(\PHPUnit\Framework\TestResult $result) { $this->write("\nCodeception Results\n"); $this->write(sprintf( "Successful: %d. Failed: %d. Incomplete: %d. Skipped: %d. Useless: %d", $this->successful, $this->failed, $this->incomplete, $this->skipped, $this->risky ) . "\n"); } public function write(string $buffer) : void { parent::write($buffer); } }

{toggle} {name} {time}s

{steps} {failure} {png} {html}
Test results
{header} {scenarios}

Summary

Successful scenarios: {successfulScenarios}
Failed scenarios: {failedScenarios}
Skipped scenarios: {skippedScenarios}
Incomplete scenarios: {incompleteScenarios}
Useless scenarios: {uselessScenarios}

{suite} Tests

+ {metaStep}

{steps}

{name} {status} ({time}s)

    {action} {fail} templatePath = sprintf( '%s%stemplate%s', __DIR__, DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR ); } /** * Handler for 'start class' event. * * @param string $name */ protected function startClass(string $name):void { } public function endTest(\PHPUnit\Framework\Test $test, float $time) : void { $steps = []; $success = ($this->testStatus == \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED); if ($success) { $this->successful++; } if ($test instanceof ScenarioDriven) { $steps = $test->getScenario()->getSteps(); } $this->timeTaken += $time; switch ($this->testStatus) { case \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE: $scenarioStatus = 'scenarioFailed'; break; case \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED: $scenarioStatus = 'scenarioSkipped'; break; case \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE: $scenarioStatus = 'scenarioIncomplete'; break; case \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR: $scenarioStatus = 'scenarioFailed'; break; default: $scenarioStatus = 'scenarioSuccess'; } $stepsBuffer = ''; $subStepsRendered = []; foreach ($steps as $step) { if ($step->getMetaStep()) { $key = $step->getMetaStep()->getLine() . $step->getMetaStep()->getAction(); $subStepsRendered[$key][] = $this->renderStep($step); } } foreach ($steps as $step) { if ($step->getMetaStep()) { $key = $step->getMetaStep()->getLine() . $step->getMetaStep()->getAction(); if (! empty($subStepsRendered[$key])) { $subStepsBuffer = implode('', $subStepsRendered[$key]); unset($subStepsRendered[$key]); $stepsBuffer .= $this->renderSubsteps($step->getMetaStep(), $subStepsBuffer); } } else { $stepsBuffer .= $this->renderStep($step); } } $scenarioTemplate = new \Text_Template( $this->templatePath . 'scenario.html' ); $failures = ''; $name = Descriptor::getTestSignatureUnique($test); if (isset($this->failures[$name])) { $failTemplate = new \Text_Template( $this->templatePath . 'fail.html' ); foreach ($this->failures[$name] as $failure) { $failTemplate->setVar(['fail' => nl2br($failure)]); $failures .= $failTemplate->render() . PHP_EOL; } $this->failures[$name] = []; } $png = ''; $html = ''; if ($test instanceof TestInterface) { $reports = $test->getMetadata()->getReports(); if (isset($reports['png'])) { $localPath = PathResolver::getRelativeDir($reports['png'], codecept_output_dir()); $png = "
failure screenshot
"; } if (isset($reports['html'])) { $localPath = PathResolver::getRelativeDir($reports['html'], codecept_output_dir()); $html = "See HTML snapshot of a failed page"; } } $toggle = $stepsBuffer ? '+' : ''; $testString = htmlspecialchars(ucfirst(Descriptor::getTestAsString($test))); $testString = preg_replace('~^([\s\w\\\]+):\s~', '$1 » ', $testString); $scenarioTemplate->setVar( [ 'id' => ++$this->id, 'name' => $testString, 'scenarioStatus' => $scenarioStatus, 'steps' => $stepsBuffer, 'toggle' => $toggle, 'failure' => $failures, 'png' => $png, 'html' => $html, 'time' => round($time, 2) ] ); $this->scenarios .= $scenarioTemplate->render(); } public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) : void { $suiteTemplate = new \Text_Template( $this->templatePath . 'suite.html' ); if (!$suite->getName()) { return; } $suiteTemplate->setVar(['suite' => ucfirst($suite->getName())]); $this->scenarios .= $suiteTemplate->render(); } /** * Handler for 'end run' event. */ protected function endRun():void { $scenarioHeaderTemplate = new \Text_Template( $this->templatePath . 'scenario_header.html' ); $status = !$this->failed ? 'OK' : 'FAILED'; $scenarioHeaderTemplate->setVar( [ 'name' => 'Codeception Results', 'status' => $status, 'time' => round($this->timeTaken, 1) ] ); $header = $scenarioHeaderTemplate->render(); $scenariosTemplate = new \Text_Template( $this->templatePath . 'scenarios.html' ); $scenariosTemplate->setVar( [ 'header' => $header, 'scenarios' => $this->scenarios, 'successfulScenarios' => $this->successful, 'failedScenarios' => $this->failed, 'skippedScenarios' => $this->skipped, 'incompleteScenarios' => $this->incomplete, 'uselessScenarios' => $this->risky, ] ); $this->write($scenariosTemplate->render()); } /** * An error occurred. * * @param \PHPUnit\Framework\Test $test * @param \Exception $e * @param float $time */ public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->failures[Descriptor::getTestSignatureUnique($test)][] = $this->cleanMessage($e); parent::addError($test, $e, $time); } /** * A failure occurred. * * @param \PHPUnit\Framework\Test $test * @param \PHPUnit\Framework\AssertionFailedError $e * @param float $time */ public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void { $this->failures[Descriptor::getTestSignatureUnique($test)][] = $this->cleanMessage($e); parent::addFailure($test, $e, $time); } /** * Starts test * * @param \PHPUnit\Framework\Test $test */ public function startTest(\PHPUnit\Framework\Test $test):void { $name = Descriptor::getTestSignatureUnique($test); if (isset($this->failures[$name])) { // test failed in before hook return; } // start test and mark initialize as passed parent::startTest($test); } /** * @param $step * @return string */ protected function renderStep(Step $step) { $stepTemplate = new \Text_Template($this->templatePath . 'step.html'); $stepTemplate->setVar(['action' => $step->getHtml(), 'error' => $step->hasFailed() ? 'failedStep' : '']); return $stepTemplate->render(); } /** * @param $metaStep * @param $substepsBuffer * @return string */ protected function renderSubsteps(Meta $metaStep, $substepsBuffer) { $metaTemplate = new \Text_Template($this->templatePath . 'substeps.html'); $metaTemplate->setVar(['metaStep' => $metaStep->getHtml(), 'error' => $metaStep->hasFailed() ? 'failedStep' : '', 'steps' => $substepsBuffer, 'id' => uniqid()]); return $metaTemplate->render(); } private function cleanMessage($exception) { $msg = $exception->getMessage(); if ($exception instanceof \PHPUnit\Framework\ExpectationFailedException && $exception->getComparisonFailure()) { $msg .= $exception->getComparisonFailure()->getDiff(); } $msg = str_replace(['','','',''], ['','','',''], $msg); return htmlentities($msg); } } OutputInterface::VERBOSITY_NORMAL, $options['colors'] ? 'always' : 'never'); $this->dispatcher = $dispatcher; } protected function printDefect(\PHPUnit\Framework\TestFailure $defect, int $count): void { $this->write("\n---------\n"); $this->dispatch( $this->dispatcher, Events::TEST_FAIL_PRINT, new FailEvent($defect->failedTest(), null, $defect->thrownException(), $count) ); } /** * @param \PHPUnit\Framework\TestFailure $defect */ protected function printDefectTrace(\PHPUnit\Framework\TestFailure $defect): void { $this->write($defect->getExceptionAsString()); $this->writeNewLine(); $stackTrace = \PHPUnit\Util\Filter::getFilteredStacktrace($defect->thrownException()); foreach ($stackTrace as $i => $frame) { if (!isset($frame['file'])) { continue; } $this->write( sprintf( "#%d %s(%s)", $i + 1, $frame['file'], isset($frame['line']) ? $frame['line'] : '?' ) ); $this->writeNewLine(); } } public function startTest(\PHPUnit\Framework\Test $test) : void { if ($test instanceof Unit) { parent::startTest($test); } } public function endTest(\PHPUnit\Framework\Test $test, float $time) : void { if ($test instanceof \PHPUnit\Framework\TestCase or $test instanceof \Codeception\Test\Test) { $this->numAssertions += $test->getNumAssertions(); } $this->lastTestFailed = false; } public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->lastTestFailed = true; } public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void { $this->lastTestFailed = true; } public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time) : void { $this->lastTestFailed = true; } public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->lastTestFailed = true; } public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->lastTestFailed = true; } public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->lastTestFailed = true; } } dispatcher = $dispatcher; } /** * Risky test. * * @param PHPUnit\Framework\Test $test * @param \Throwable $e * @param float $time * @since Method available since Release 4.0.0 */ public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->unsuccessfulTests[] = spl_object_hash($test); $this->fire('test.useless', new FailEvent($test, $time, $e)); } public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void { $this->unsuccessfulTests[] = spl_object_hash($test); $this->fire(Events::TEST_FAIL, new FailEvent($test, $time, $e)); } public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->unsuccessfulTests[] = spl_object_hash($test); $this->fire(Events::TEST_ERROR, new FailEvent($test, $time, $e)); } // This method was added in PHPUnit 6 public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time) : void { $this->unsuccessfulTests[] = spl_object_hash($test); $this->fire(Events::TEST_WARNING, new FailEvent($test, $time, $e)); } public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { if (in_array(spl_object_hash($test), $this->skippedTests)) { return; } $this->unsuccessfulTests[] = spl_object_hash($test); $this->fire(Events::TEST_INCOMPLETE, new FailEvent($test, $time, $e)); $this->skippedTests[] = spl_object_hash($test); } public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { if (in_array(spl_object_hash($test), $this->skippedTests)) { return; } $this->unsuccessfulTests[] = spl_object_hash($test); $this->fire(Events::TEST_SKIPPED, new FailEvent($test, $time, $e)); $this->skippedTests[] = spl_object_hash($test); } public function startTestSuite(\PHPUnit\Framework\TestSuite $suite) : void { $this->dispatch($this->dispatcher, 'suite.start', new SuiteEvent($suite)); } public function endTestSuite(\PHPUnit\Framework\TestSuite $suite) : void { $this->dispatch($this->dispatcher, 'suite.end', new SuiteEvent($suite)); } public function startTest(\PHPUnit\Framework\Test $test) : void { $this->dispatch($this->dispatcher, Events::TEST_START, new TestEvent($test)); if (!$test instanceof TestInterface) { return; } if ($test->getMetadata()->isBlocked()) { return; } try { $this->startedTests[] = spl_object_hash($test); $this->fire(Events::TEST_BEFORE, new TestEvent($test)); } catch (\PHPUnit\Framework\IncompleteTestError $e) { $test->getTestResultObject()->addFailure($test, $e, 0); } catch (\PHPUnit\Framework\SkippedTestError $e) { $test->getTestResultObject()->addFailure($test, $e, 0); } catch (\Throwable $e) { $test->getTestResultObject()->addError($test, $e, 0); } } public function endTest(\PHPUnit\Framework\Test $test, float $time) : void { $hash = spl_object_hash($test); if (!in_array($hash, $this->unsuccessfulTests)) { $this->fire(Events::TEST_SUCCESS, new TestEvent($test, $time)); } if (in_array($hash, $this->startedTests)) { $this->fire(Events::TEST_AFTER, new TestEvent($test, $time)); } $this->dispatch($this->dispatcher, Events::TEST_END, new TestEvent($test, $time)); } protected function fire($event, TestEvent $eventType) { $test = $eventType->getTest(); if ($test instanceof TestInterface) { foreach ($test->getMetadata()->getGroups() as $group) { $this->dispatch($this->dispatcher, $event . '.' . $group, $eventType); } } $this->dispatch($this->dispatcher, $event, $eventType); } } dispatch($eventObject, $eventType); } else { //Symfony 4.2 or lower $dispatcher->dispatch($eventType, $eventObject); } } } string) { throw new \PHPUnit\Framework\ExpectationFailedException( "Element $selector was found", $comparisonFailure ); } $output = "There was $selector element"; $output .= $this->uriMessage("on page"); $output .= $this->nodesList($nodes, $this->string); $output .= "\ncontaining '{$this->string}'"; throw new \PHPUnit\Framework\ExpectationFailedException( $output, $comparisonFailure ); } public function toString() : string { if ($this->string) { return 'that contains text "' . $this->string . '"'; } } } expected = $expected; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * * @return bool */ protected function matches($other) : bool { $jsonResponseArray = new JsonArray($other); if (!is_array($jsonResponseArray->toArray())) { throw new \PHPUnit\Framework\AssertionFailedError('JSON response is not an array: ' . $other); } if ($jsonResponseArray->containsArray($this->expected)) { return true; } $comparator = new ArrayComparator(); $comparator->setFactory(new Factory); try { $comparator->assertEquals($this->expected, $jsonResponseArray->toArray()); } catch (ComparisonFailure $failure) { throw new \PHPUnit\Framework\ExpectationFailedException( "Response JSON does not contain the provided JSON\n", $failure ); } return false; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() : string { //unused return ''; } protected function failureDescription($other) : string { //unused return ''; } } string = $this->normalizeText((string)$string); $this->uri = $uri; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * * @return bool */ protected function matches($other) : bool { $other = $this->normalizeText($other); return mb_stripos($other, $this->string, null, 'UTF-8') !== false; } /** * @param $text * @return string */ private function normalizeText($text) { $text = strtr($text, "\r\n", " "); return trim(preg_replace('/\\s{2,}/', ' ', $text)); } /** * Returns a string representation of the constraint. * * @return string */ public function toString() : string { return sprintf( 'contains "carview.php?tsp=%s"', $this->string ); } protected function failureDescription($pageContent) : string { $message = $this->uriMessage('on page'); $message->append("\n--> "); $message->append(mb_substr($pageContent, 0, 300, 'utf-8')); if (mb_strlen($pageContent, 'utf-8') > 300) { $debugMessage = new Message( "[Content too long to display. See complete response in '" . codecept_output_dir() . "' directory]" ); $message->append("\n")->append($debugMessage); } $message->append("\n--> "); return $message->getMessage() . $this->toString(); } protected function uriMessage($onPage = "carview.php?tsp=") { if (!$this->uri) { return new Message(''); } $message = new Message($this->uri); $message->prepend(" $onPage "); return $message; } } string) { throw new \PHPUnit\Framework\ExpectationFailedException( "Element '$selector' was found", $comparisonFailure ); } /** @var $nodes DomCrawler * */ $output = "There was '$selector' element"; $output .= $this->uriMessage('on page'); $output .= $this->nodesList($nodes, $this->string); $output .= "\ncontaining '{$this->string}'"; throw new \PHPUnit\Framework\ExpectationFailedException( $output, $comparisonFailure ); } public function toString() : string { if ($this->string) { return 'that contains text "' . $this->string . '"'; } } } jsonType = $jsonType; $this->match = $match; } /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $jsonArray Value or object to evaluate. * * @return bool */ protected function matches($jsonArray) : bool { if ($jsonArray instanceof JsonArray) { $jsonArray = $jsonArray->toArray(); } $matched = (new JsonTypeUtil($jsonArray))->matches($this->jsonType); if ($this->match) { if ($matched !== true) { throw new \PHPUnit\Framework\ExpectationFailedException($matched); } } else { if ($matched === true) { throw new \PHPUnit\Framework\ExpectationFailedException('Unexpectedly response matched: ' . json_encode($jsonArray)); } } return true; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() : string { //unused return ''; } protected function failureDescription($other) : string { //unused return ''; } } count()) { return false; } if ($this->string === '') { return true; } foreach ($nodes as $node) { if (parent::matches($node->nodeValue)) { return true; } } return false; } protected function fail($nodes, $selector, ComparisonFailure $comparisonFailure = null):void { /** @var $nodes DomCrawler * */ if (!$nodes->count()) { throw new ElementNotFound($selector, 'Element located either by name, CSS or XPath'); } $output = "Failed asserting that any element by '$selector'"; $output .= $this->uriMessage('on page'); $output .= " "; if ($nodes->count() < 10) { $output .= $this->nodesList($nodes); } else { $message = new Message("[total %s elements]"); $output .= $message->with($nodes->count())->getMessage(); } $output .= "\ncontains text '{$this->string}'"; throw new \PHPUnit\Framework\ExpectationFailedException( $output, $comparisonFailure ); } protected function failureDescription($other) : string { $desc = ''; foreach ($other as $o) { $desc .= parent::failureDescription($o->textContent); } return $desc; } protected function nodesList(DomCrawler $nodes, $contains = null) { $output = "carview.php?tsp="; foreach ($nodes as $node) { if ($contains && strpos($node->nodeValue, $contains) === false) { continue; } $output .= "\n+ " . $node->C14N(); } return $output; } } string === '') { return true; } foreach ($nodes as $node) { /** @var $node \WebDriverElement * */ if (!$node->isDisplayed()) { continue; } if (parent::matches(htmlspecialchars_decode($node->getText()))) { return true; } } return false; } protected function fail($nodes, $selector, ComparisonFailure $comparisonFailure = null) : void { if (!count($nodes)) { throw new ElementNotFound($selector, 'Element located either by name, CSS or XPath'); } $output = "Failed asserting that any element by " . Locator::humanReadableString($selector); $output .= $this->uriMessage('on page'); if (count($nodes) < 5) { $output .= "\nElements: "; $output .= $this->nodesList($nodes); } else { $message = new Message("[total %s elements]"); $output .= $message->with(count($nodes)); } $output .= "\ncontains text '" . $this->string . "'"; throw new \PHPUnit\Framework\ExpectationFailedException( $output, $comparisonFailure ); } protected function failureDescription($nodes) : string { $desc = ''; foreach ($nodes as $node) { $desc .= parent::failureDescription($node->getText()); } return $desc; } protected function nodesList($nodes, $contains = null) { $output = "carview.php?tsp="; foreach ($nodes as $node) { if ($contains && strpos($node->getText(), $contains) === false) { continue; } /** @var $node \WebDriverElement * */ $message = new Message("\n+ <%s> %s"); $output .= $message->with($node->getTagName(), $node->getText()); } return $output; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Codeception\PHPUnit\NonFinal; use PHPUnit\Framework\Error\Deprecated; use PHPUnit\Framework\Error\Notice; use PHPUnit\Framework\Error\Warning; use PHPUnit\Framework\Exception; use PHPUnit\Framework\Test; use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestListener; use PHPUnit\Framework\TestResult; use PHPUnit\Framework\TestSuite; use PHPUnit\Runner\AfterLastTestHook; use PHPUnit\Runner\BaseTestRunner; use PHPUnit\Runner\BeforeFirstTestHook; use PHPUnit\Runner\Filter\ExcludeGroupFilterIterator; use PHPUnit\Runner\Filter\Factory; use PHPUnit\Runner\Filter\IncludeGroupFilterIterator; use PHPUnit\Runner\Filter\NameFilterIterator; use PHPUnit\Runner\Hook; use PHPUnit\Runner\NullTestResultCache; use PHPUnit\Runner\ResultCacheExtension; use PHPUnit\Runner\StandardTestSuiteLoader; use PHPUnit\Runner\TestHook; use PHPUnit\Runner\TestListenerAdapter; use PHPUnit\Runner\TestResultCache; use PHPUnit\Runner\TestSuiteLoader; use PHPUnit\Runner\TestSuiteSorter; use PHPUnit\Runner\Version; use PHPUnit\TextUI\ResultPrinter; use PHPUnit\Util\Configuration; use PHPUnit\Util\Filesystem; use PHPUnit\Util\Log\JUnit; use PHPUnit\Util\Log\TeamCity; use PHPUnit\Util\Printer; use PHPUnit\Util\TestDox\CliTestDoxPrinter; use PHPUnit\Util\TestDox\HtmlResultPrinter; use PHPUnit\Util\TestDox\TextResultPrinter; use PHPUnit\Util\TestDox\XmlResultPrinter; use PHPUnit\Util\XdebugFilterScriptGenerator; use ReflectionClass; use SebastianBergmann\CodeCoverage\CodeCoverage; use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException; use SebastianBergmann\CodeCoverage\Filter as CodeCoverageFilter; use SebastianBergmann\CodeCoverage\Report\Clover as CloverReport; use SebastianBergmann\CodeCoverage\Report\Crap4j as Crap4jReport; use SebastianBergmann\CodeCoverage\Report\Html\Facade as HtmlReport; use SebastianBergmann\CodeCoverage\Report\PHP as PhpReport; use SebastianBergmann\CodeCoverage\Report\Text as TextReport; use SebastianBergmann\CodeCoverage\Report\Xml\Facade as XmlReport; use SebastianBergmann\Comparator\Comparator; use SebastianBergmann\Environment\Runtime; use SebastianBergmann\Invoker\Invoker; /** * @internal This class is not covered by the backward compatibility promise for PHPUnit */ class TestRunner extends BaseTestRunner { public const SUCCESS_EXIT = 0; public const FAILURE_EXIT = 1; public const EXCEPTION_EXIT = 2; /** * @var bool */ protected static $versionStringPrinted = false; /** * @var CodeCoverageFilter */ protected $codeCoverageFilter; /** * @var TestSuiteLoader */ protected $loader; /** * @var ResultPrinter */ protected $printer; /** * @var Runtime */ private $runtime; /** * @var bool */ private $messagePrinted = false; /** * @var Hook[] */ private $extensions = []; /** * @param ReflectionClass|Test $test * @param bool $exit * * @throws \RuntimeException * @throws \InvalidArgumentException * @throws Exception * @throws \ReflectionException */ public static function run($test, array $arguments = [], $exit = true): TestResult { if ($test instanceof ReflectionClass) { $test = new TestSuite($test); } if ($test instanceof Test) { $aTestRunner = new self; return $aTestRunner->doRun( $test, $arguments, $exit ); } throw new Exception('No test case or test suite found.'); } public function __construct(TestSuiteLoader $loader = null, CodeCoverageFilter $filter = null) { if ($filter === null) { $filter = new CodeCoverageFilter; } $this->codeCoverageFilter = $filter; $this->loader = $loader; $this->runtime = new Runtime; } /** * @throws \PHPUnit\Runner\Exception * @throws Exception * @throws \InvalidArgumentException * @throws \RuntimeException * @throws \ReflectionException */ public function doRun(Test $suite, array $arguments = [], bool $exit = true): TestResult { if (isset($arguments['configuration'])) { $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] = $arguments['configuration']; } $this->handleConfiguration($arguments); if (\is_int($arguments['columns']) && $arguments['columns'] < 16) { $arguments['columns'] = 16; $tooFewColumnsRequested = true; } if (isset($arguments['bootstrap'])) { $GLOBALS['__PHPUNIT_BOOTSTRAP'] = $arguments['bootstrap']; } if ($suite instanceof TestCase || $suite instanceof TestSuite) { if ($arguments['backupGlobals'] === true) { $suite->setBackupGlobals(true); } if ($arguments['backupStaticAttributes'] === true) { $suite->setBackupStaticAttributes(true); } if ($arguments['beStrictAboutChangesToGlobalState'] === true) { $suite->setBeStrictAboutChangesToGlobalState(true); } } if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { \mt_srand($arguments['randomOrderSeed']); } if ($arguments['cacheResult']) { if (isset($arguments['cacheResultFile'])) { $cache = new TestResultCache($arguments['cacheResultFile']); } else { $cache = new TestResultCache; } $this->addExtension(new ResultCacheExtension($cache)); } if ($arguments['executionOrder'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['executionOrderDefects'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['resolveDependencies']) { $cache = $cache ?? new NullTestResultCache; $cache->load(); $sorter = new TestSuiteSorter($cache); $sorter->reorderTestsInSuite($suite, $arguments['executionOrder'], $arguments['resolveDependencies'], $arguments['executionOrderDefects']); $originalExecutionOrder = $sorter->getOriginalExecutionOrder(); unset($sorter); } if (\is_int($arguments['repeat']) && $arguments['repeat'] > 0) { $_suite = new TestSuite; /* @noinspection PhpUnusedLocalVariableInspection */ foreach (\range(1, $arguments['repeat']) as $step) { $_suite->addTest($suite); } $suite = $_suite; unset($_suite); } $result = $this->createTestResult(); $listener = new TestListenerAdapter; $listenerNeeded = false; foreach ($this->extensions as $extension) { if ($extension instanceof TestHook) { $listener->add($extension); $listenerNeeded = true; } } if ($listenerNeeded) { $result->addListener($listener); } unset($listener, $listenerNeeded); if (!$arguments['convertErrorsToExceptions']) { $result->convertErrorsToExceptions(false); } if (!$arguments['convertDeprecationsToExceptions']) { Deprecated::$enabled = false; } if (!$arguments['convertNoticesToExceptions']) { Notice::$enabled = false; } if (!$arguments['convertWarningsToExceptions']) { Warning::$enabled = false; } if ($arguments['stopOnError']) { $result->stopOnError(true); } if ($arguments['stopOnFailure']) { $result->stopOnFailure(true); } if ($arguments['stopOnWarning']) { $result->stopOnWarning(true); } if ($arguments['stopOnIncomplete']) { $result->stopOnIncomplete(true); } if ($arguments['stopOnRisky']) { $result->stopOnRisky(true); } if ($arguments['stopOnSkipped']) { $result->stopOnSkipped(true); } if ($arguments['stopOnDefect']) { $result->stopOnDefect(true); } if ($arguments['registerMockObjectsFromTestArgumentsRecursively']) { $result->setRegisterMockObjectsFromTestArgumentsRecursively(true); } if ($this->printer === null) { if (isset($arguments['printer']) && $arguments['printer'] instanceof Printer) { $this->printer = $arguments['printer']; } else { $printerClass = ResultPrinter::class; if (isset($arguments['printer']) && \is_string($arguments['printer']) && \class_exists($arguments['printer'], false)) { $class = new ReflectionClass($arguments['printer']); if ($class->isSubclassOf(ResultPrinter::class)) { $printerClass = $arguments['printer']; } } $this->printer = new $printerClass( (isset($arguments['stderr']) && $arguments['stderr'] === true) ? 'php://stderr' : null, $arguments['verbose'], $arguments['colors'], $arguments['debug'], $arguments['columns'], $arguments['reverseList'] ); if (isset($originalExecutionOrder) && ($this->printer instanceof CliTestDoxPrinter)) { /* @var CliTestDoxPrinter */ $this->printer->setOriginalExecutionOrder($originalExecutionOrder); } } } $this->printer->write( Version::getVersionString() . "\n" ); self::$versionStringPrinted = true; if ($arguments['verbose']) { $this->writeMessage('Runtime', $this->runtime->getNameWithVersionAndCodeCoverageDriver()); if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { $this->writeMessage( 'Random seed', (string) $arguments['randomOrderSeed'] ); } if (isset($arguments['configuration'])) { $this->writeMessage( 'Configuration', $arguments['configuration']->getFilename() ); } foreach ($arguments['loadedExtensions'] as $extension) { $this->writeMessage( 'Extension', $extension ); } foreach ($arguments['notLoadedExtensions'] as $extension) { $this->writeMessage( 'Extension', $extension ); } } if (isset($tooFewColumnsRequested)) { $this->writeMessage('Error', 'Less than 16 columns requested, number of columns set to 16'); } if ($this->runtime->discardsComments()) { $this->writeMessage('Warning', 'opcache.save_comments=0 set; annotations will not work'); } if (isset($arguments['configuration']) && $arguments['configuration']->hasValidationErrors()) { $this->write( "\n Warning - The configuration file did not pass validation!\n The following problems have been detected:\n" ); foreach ($arguments['configuration']->getValidationErrors() as $line => $errors) { $this->write(\sprintf("\n Line %d:\n", $line)); foreach ($errors as $msg) { $this->write(\sprintf(" - %s\n", $msg)); } } $this->write("\n Test results may not be as expected.\n\n"); } foreach ($arguments['listeners'] as $listener) { $result->addListener($listener); } $result->addListener($this->printer); $codeCoverageReports = 0; if (!isset($arguments['noLogging'])) { if (isset($arguments['testdoxHTMLFile'])) { $result->addListener( new HtmlResultPrinter( $arguments['testdoxHTMLFile'], $arguments['testdoxGroups'], $arguments['testdoxExcludeGroups'] ) ); } if (isset($arguments['testdoxTextFile'])) { $result->addListener( new TextResultPrinter( $arguments['testdoxTextFile'], $arguments['testdoxGroups'], $arguments['testdoxExcludeGroups'] ) ); } if (isset($arguments['testdoxXMLFile'])) { $result->addListener( new XmlResultPrinter( $arguments['testdoxXMLFile'] ) ); } if (isset($arguments['teamcityLogfile'])) { $result->addListener( new TeamCity($arguments['teamcityLogfile']) ); } if (isset($arguments['junitLogfile'])) { $result->addListener( new JUnit( $arguments['junitLogfile'], $arguments['reportUselessTests'] ) ); } if (isset($arguments['coverageClover'])) { $codeCoverageReports++; } if (isset($arguments['coverageCrap4J'])) { $codeCoverageReports++; } if (isset($arguments['coverageHtml'])) { $codeCoverageReports++; } if (isset($arguments['coveragePHP'])) { $codeCoverageReports++; } if (isset($arguments['coverageText'])) { $codeCoverageReports++; } if (isset($arguments['coverageXml'])) { $codeCoverageReports++; } } if (isset($arguments['noCoverage'])) { $codeCoverageReports = 0; } if ($codeCoverageReports > 0 && !$this->runtime->canCollectCodeCoverage()) { $this->writeMessage('Error', 'No code coverage driver is available'); $codeCoverageReports = 0; } if ($codeCoverageReports > 0 || isset($arguments['xdebugFilterFile'])) { $whitelistFromConfigurationFile = false; $whitelistFromOption = false; if (isset($arguments['whitelist'])) { $this->codeCoverageFilter->addDirectoryToWhitelist($arguments['whitelist']); $whitelistFromOption = true; } if (isset($arguments['configuration'])) { $filterConfiguration = $arguments['configuration']->getFilterConfiguration(); if (!empty($filterConfiguration['whitelist'])) { $whitelistFromConfigurationFile = true; } if (!empty($filterConfiguration['whitelist'])) { foreach ($filterConfiguration['whitelist']['include']['directory'] as $dir) { $this->codeCoverageFilter->addDirectoryToWhitelist( $dir['path'], $dir['suffix'], $dir['prefix'] ); } foreach ($filterConfiguration['whitelist']['include']['file'] as $file) { $this->codeCoverageFilter->addFileToWhitelist($file); } foreach ($filterConfiguration['whitelist']['exclude']['directory'] as $dir) { $this->codeCoverageFilter->removeDirectoryFromWhitelist( $dir['path'], $dir['suffix'], $dir['prefix'] ); } foreach ($filterConfiguration['whitelist']['exclude']['file'] as $file) { $this->codeCoverageFilter->removeFileFromWhitelist($file); } } } } if ($codeCoverageReports > 0) { $codeCoverage = new CodeCoverage( null, $this->codeCoverageFilter ); $codeCoverage->setUnintentionallyCoveredSubclassesWhitelist( [Comparator::class] ); $codeCoverage->setCheckForUnintentionallyCoveredCode( $arguments['strictCoverage'] ); $codeCoverage->setCheckForMissingCoversAnnotation( $arguments['strictCoverage'] ); if (isset($arguments['forceCoversAnnotation'])) { $codeCoverage->setForceCoversAnnotation( $arguments['forceCoversAnnotation'] ); } if (isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) { $codeCoverage->setIgnoreDeprecatedCode( $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] ); } if (isset($arguments['disableCodeCoverageIgnore'])) { $codeCoverage->setDisableIgnoredLines(true); } if (!empty($filterConfiguration['whitelist'])) { $codeCoverage->setAddUncoveredFilesFromWhitelist( $filterConfiguration['whitelist']['addUncoveredFilesFromWhitelist'] ); $codeCoverage->setProcessUncoveredFilesFromWhitelist( $filterConfiguration['whitelist']['processUncoveredFilesFromWhitelist'] ); } if (!$this->codeCoverageFilter->hasWhitelist()) { if (!$whitelistFromConfigurationFile && !$whitelistFromOption) { $this->writeMessage('Error', 'No whitelist is configured, no code coverage will be generated.'); } else { $this->writeMessage('Error', 'Incorrect whitelist config, no code coverage will be generated.'); } $codeCoverageReports = 0; unset($codeCoverage); } } if (isset($arguments['xdebugFilterFile'], $filterConfiguration)) { $this->write("\n"); $script = (new XdebugFilterScriptGenerator)->generate($filterConfiguration['whitelist']); if ($arguments['xdebugFilterFile'] !== 'php://stdout' && $arguments['xdebugFilterFile'] !== 'php://stderr' && !Filesystem::createDirectory(\dirname($arguments['xdebugFilterFile']))) { $this->write(\sprintf('Cannot write Xdebug filter script to %s ' . \PHP_EOL, $arguments['xdebugFilterFile'])); exit(self::EXCEPTION_EXIT); } \file_put_contents($arguments['xdebugFilterFile'], $script); $this->write(\sprintf('Wrote Xdebug filter script to %s ' . \PHP_EOL, $arguments['xdebugFilterFile'])); exit(self::SUCCESS_EXIT); } $this->printer->write("\n"); if (isset($codeCoverage)) { $result->setCodeCoverage($codeCoverage); if ($codeCoverageReports > 1 && isset($arguments['cacheTokens'])) { $codeCoverage->setCacheTokens($arguments['cacheTokens']); } } $result->beStrictAboutTestsThatDoNotTestAnything($arguments['reportUselessTests']); $result->beStrictAboutOutputDuringTests($arguments['disallowTestOutput']); $result->beStrictAboutTodoAnnotatedTests($arguments['disallowTodoAnnotatedTests']); $result->beStrictAboutResourceUsageDuringSmallTests($arguments['beStrictAboutResourceUsageDuringSmallTests']); if ($arguments['enforceTimeLimit'] === true) { if (!\class_exists(Invoker::class)) { $this->writeMessage('Error', 'Package phpunit/php-invoker is required for enforcing time limits'); } if (!\extension_loaded('pcntl') || \strpos(\ini_get('disable_functions'), 'pcntl') !== false) { $this->writeMessage('Error', 'PHP extension pcntl is required for enforcing time limits'); } } $result->enforceTimeLimit($arguments['enforceTimeLimit']); $result->setDefaultTimeLimit($arguments['defaultTimeLimit']); $result->setTimeoutForSmallTests($arguments['timeoutForSmallTests']); $result->setTimeoutForMediumTests($arguments['timeoutForMediumTests']); $result->setTimeoutForLargeTests($arguments['timeoutForLargeTests']); if ($suite instanceof TestSuite) { $this->processSuiteFilters($suite, $arguments); $suite->setRunTestInSeparateProcess($arguments['processIsolation']); } foreach ($this->extensions as $extension) { if ($extension instanceof BeforeFirstTestHook) { $extension->executeBeforeFirstTest(); } } $suite->run($result); foreach ($this->extensions as $extension) { if ($extension instanceof AfterLastTestHook) { $extension->executeAfterLastTest(); } } $result->flushListeners(); if ($this->printer instanceof ResultPrinter) { $this->printer->printResult($result); } if (isset($codeCoverage)) { if (isset($arguments['coverageClover'])) { $this->printer->write( "\nGenerating code coverage report in Clover XML format ..." ); try { $writer = new CloverReport; $writer->process($codeCoverage, $arguments['coverageClover']); $this->printer->write(" done\n"); unset($writer); } catch (CodeCoverageException $e) { $this->printer->write( " failed\n" . $e->getMessage() . "\n" ); } } if (isset($arguments['coverageCrap4J'])) { $this->printer->write( "\nGenerating Crap4J report XML file ..." ); try { $writer = new Crap4jReport($arguments['crap4jThreshold']); $writer->process($codeCoverage, $arguments['coverageCrap4J']); $this->printer->write(" done\n"); unset($writer); } catch (CodeCoverageException $e) { $this->printer->write( " failed\n" . $e->getMessage() . "\n" ); } } if (isset($arguments['coverageHtml'])) { $this->printer->write( "\nGenerating code coverage report in HTML format ..." ); try { $writer = new HtmlReport( $arguments['reportLowUpperBound'], $arguments['reportHighLowerBound'], \sprintf( ' and PHPUnit %s', Version::id() ) ); $writer->process($codeCoverage, $arguments['coverageHtml']); $this->printer->write(" done\n"); unset($writer); } catch (CodeCoverageException $e) { $this->printer->write( " failed\n" . $e->getMessage() . "\n" ); } } if (isset($arguments['coveragePHP'])) { $this->printer->write( "\nGenerating code coverage report in PHP format ..." ); try { $writer = new PhpReport; $writer->process($codeCoverage, $arguments['coveragePHP']); $this->printer->write(" done\n"); unset($writer); } catch (CodeCoverageException $e) { $this->printer->write( " failed\n" . $e->getMessage() . "\n" ); } } if (isset($arguments['coverageText'])) { if ($arguments['coverageText'] == 'php://stdout') { $outputStream = $this->printer; $colors = $arguments['colors'] && $arguments['colors'] != ResultPrinter::COLOR_NEVER; } else { $outputStream = new Printer($arguments['coverageText']); $colors = false; } $processor = new TextReport( $arguments['reportLowUpperBound'], $arguments['reportHighLowerBound'], $arguments['coverageTextShowUncoveredFiles'], $arguments['coverageTextShowOnlySummary'] ); $outputStream->write( $processor->process($codeCoverage, $colors) ); } if (isset($arguments['coverageXml'])) { $this->printer->write( "\nGenerating code coverage report in PHPUnit XML format ..." ); try { $writer = new XmlReport(Version::id()); $writer->process($codeCoverage, $arguments['coverageXml']); $this->printer->write(" done\n"); unset($writer); } catch (CodeCoverageException $e) { $this->printer->write( " failed\n" . $e->getMessage() . "\n" ); } } } if ($exit) { if ($result->wasSuccessful()) { if ($arguments['failOnRisky'] && !$result->allHarmless()) { exit(self::FAILURE_EXIT); } if ($arguments['failOnWarning'] && $result->warningCount() > 0) { exit(self::FAILURE_EXIT); } exit(self::SUCCESS_EXIT); } if ($result->errorCount() > 0) { exit(self::EXCEPTION_EXIT); } if ($result->failureCount() > 0) { exit(self::FAILURE_EXIT); } } return $result; } public function setPrinter(ResultPrinter $resultPrinter): void { $this->printer = $resultPrinter; } /** * Returns the loader to be used. */ public function getLoader(): TestSuiteLoader { if ($this->loader === null) { $this->loader = new StandardTestSuiteLoader; } return $this->loader; } public function addExtension(TestHook $extension): void { $this->extensions[] = $extension; } protected function createTestResult(): TestResult { return new TestResult; } /** * Override to define how to handle a failed loading of * a test suite. */ protected function runFailed(string $message): void { $this->write($message . \PHP_EOL); exit(self::FAILURE_EXIT); } protected function write(string $buffer): void { if (\PHP_SAPI != 'cli' && \PHP_SAPI != 'phpdbg') { $buffer = \htmlspecialchars($buffer); } if ($this->printer !== null) { $this->printer->write($buffer); } else { print $buffer; } } /** * @throws Exception */ protected function handleConfiguration(array &$arguments): void { if (isset($arguments['configuration']) && !$arguments['configuration'] instanceof Configuration) { $arguments['configuration'] = Configuration::getInstance( $arguments['configuration'] ); } $arguments['debug'] = $arguments['debug'] ?? false; $arguments['filter'] = $arguments['filter'] ?? false; $arguments['listeners'] = $arguments['listeners'] ?? []; if (isset($arguments['configuration'])) { $arguments['configuration']->handlePHPConfiguration(); $phpunitConfiguration = $arguments['configuration']->getPHPUnitConfiguration(); if (isset($phpunitConfiguration['backupGlobals']) && !isset($arguments['backupGlobals'])) { $arguments['backupGlobals'] = $phpunitConfiguration['backupGlobals']; } if (isset($phpunitConfiguration['backupStaticAttributes']) && !isset($arguments['backupStaticAttributes'])) { $arguments['backupStaticAttributes'] = $phpunitConfiguration['backupStaticAttributes']; } if (isset($phpunitConfiguration['beStrictAboutChangesToGlobalState']) && !isset($arguments['beStrictAboutChangesToGlobalState'])) { $arguments['beStrictAboutChangesToGlobalState'] = $phpunitConfiguration['beStrictAboutChangesToGlobalState']; } if (isset($phpunitConfiguration['bootstrap']) && !isset($arguments['bootstrap'])) { $arguments['bootstrap'] = $phpunitConfiguration['bootstrap']; } if (isset($phpunitConfiguration['cacheResult']) && !isset($arguments['cacheResult'])) { $arguments['cacheResult'] = $phpunitConfiguration['cacheResult']; } if (isset($phpunitConfiguration['cacheResultFile']) && !isset($arguments['cacheResultFile'])) { $arguments['cacheResultFile'] = $phpunitConfiguration['cacheResultFile']; } if (isset($phpunitConfiguration['cacheTokens']) && !isset($arguments['cacheTokens'])) { $arguments['cacheTokens'] = $phpunitConfiguration['cacheTokens']; } if (isset($phpunitConfiguration['cacheTokens']) && !isset($arguments['cacheTokens'])) { $arguments['cacheTokens'] = $phpunitConfiguration['cacheTokens']; } if (isset($phpunitConfiguration['colors']) && !isset($arguments['colors'])) { $arguments['colors'] = $phpunitConfiguration['colors']; } if (isset($phpunitConfiguration['convertDeprecationsToExceptions']) && !isset($arguments['convertDeprecationsToExceptions'])) { $arguments['convertDeprecationsToExceptions'] = $phpunitConfiguration['convertDeprecationsToExceptions']; } if (isset($phpunitConfiguration['convertErrorsToExceptions']) && !isset($arguments['convertErrorsToExceptions'])) { $arguments['convertErrorsToExceptions'] = $phpunitConfiguration['convertErrorsToExceptions']; } if (isset($phpunitConfiguration['convertNoticesToExceptions']) && !isset($arguments['convertNoticesToExceptions'])) { $arguments['convertNoticesToExceptions'] = $phpunitConfiguration['convertNoticesToExceptions']; } if (isset($phpunitConfiguration['convertWarningsToExceptions']) && !isset($arguments['convertWarningsToExceptions'])) { $arguments['convertWarningsToExceptions'] = $phpunitConfiguration['convertWarningsToExceptions']; } if (isset($phpunitConfiguration['processIsolation']) && !isset($arguments['processIsolation'])) { $arguments['processIsolation'] = $phpunitConfiguration['processIsolation']; } if (isset($phpunitConfiguration['stopOnDefect']) && !isset($arguments['stopOnDefect'])) { $arguments['stopOnDefect'] = $phpunitConfiguration['stopOnDefect']; } if (isset($phpunitConfiguration['stopOnError']) && !isset($arguments['stopOnError'])) { $arguments['stopOnError'] = $phpunitConfiguration['stopOnError']; } if (isset($phpunitConfiguration['stopOnFailure']) && !isset($arguments['stopOnFailure'])) { $arguments['stopOnFailure'] = $phpunitConfiguration['stopOnFailure']; } if (isset($phpunitConfiguration['stopOnWarning']) && !isset($arguments['stopOnWarning'])) { $arguments['stopOnWarning'] = $phpunitConfiguration['stopOnWarning']; } if (isset($phpunitConfiguration['stopOnIncomplete']) && !isset($arguments['stopOnIncomplete'])) { $arguments['stopOnIncomplete'] = $phpunitConfiguration['stopOnIncomplete']; } if (isset($phpunitConfiguration['stopOnRisky']) && !isset($arguments['stopOnRisky'])) { $arguments['stopOnRisky'] = $phpunitConfiguration['stopOnRisky']; } if (isset($phpunitConfiguration['stopOnSkipped']) && !isset($arguments['stopOnSkipped'])) { $arguments['stopOnSkipped'] = $phpunitConfiguration['stopOnSkipped']; } if (isset($phpunitConfiguration['failOnWarning']) && !isset($arguments['failOnWarning'])) { $arguments['failOnWarning'] = $phpunitConfiguration['failOnWarning']; } if (isset($phpunitConfiguration['failOnRisky']) && !isset($arguments['failOnRisky'])) { $arguments['failOnRisky'] = $phpunitConfiguration['failOnRisky']; } if (isset($phpunitConfiguration['timeoutForSmallTests']) && !isset($arguments['timeoutForSmallTests'])) { $arguments['timeoutForSmallTests'] = $phpunitConfiguration['timeoutForSmallTests']; } if (isset($phpunitConfiguration['timeoutForMediumTests']) && !isset($arguments['timeoutForMediumTests'])) { $arguments['timeoutForMediumTests'] = $phpunitConfiguration['timeoutForMediumTests']; } if (isset($phpunitConfiguration['timeoutForLargeTests']) && !isset($arguments['timeoutForLargeTests'])) { $arguments['timeoutForLargeTests'] = $phpunitConfiguration['timeoutForLargeTests']; } if (isset($phpunitConfiguration['reportUselessTests']) && !isset($arguments['reportUselessTests'])) { $arguments['reportUselessTests'] = $phpunitConfiguration['reportUselessTests']; } if (isset($phpunitConfiguration['strictCoverage']) && !isset($arguments['strictCoverage'])) { $arguments['strictCoverage'] = $phpunitConfiguration['strictCoverage']; } if (isset($phpunitConfiguration['ignoreDeprecatedCodeUnitsFromCodeCoverage']) && !isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) { $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] = $phpunitConfiguration['ignoreDeprecatedCodeUnitsFromCodeCoverage']; } if (isset($phpunitConfiguration['disallowTestOutput']) && !isset($arguments['disallowTestOutput'])) { $arguments['disallowTestOutput'] = $phpunitConfiguration['disallowTestOutput']; } if (isset($phpunitConfiguration['defaultTimeLimit']) && !isset($arguments['defaultTimeLimit'])) { $arguments['defaultTimeLimit'] = $phpunitConfiguration['defaultTimeLimit']; } if (isset($phpunitConfiguration['enforceTimeLimit']) && !isset($arguments['enforceTimeLimit'])) { $arguments['enforceTimeLimit'] = $phpunitConfiguration['enforceTimeLimit']; } if (isset($phpunitConfiguration['disallowTodoAnnotatedTests']) && !isset($arguments['disallowTodoAnnotatedTests'])) { $arguments['disallowTodoAnnotatedTests'] = $phpunitConfiguration['disallowTodoAnnotatedTests']; } if (isset($phpunitConfiguration['beStrictAboutResourceUsageDuringSmallTests']) && !isset($arguments['beStrictAboutResourceUsageDuringSmallTests'])) { $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $phpunitConfiguration['beStrictAboutResourceUsageDuringSmallTests']; } if (isset($phpunitConfiguration['verbose']) && !isset($arguments['verbose'])) { $arguments['verbose'] = $phpunitConfiguration['verbose']; } if (isset($phpunitConfiguration['reverseDefectList']) && !isset($arguments['reverseList'])) { $arguments['reverseList'] = $phpunitConfiguration['reverseDefectList']; } if (isset($phpunitConfiguration['forceCoversAnnotation']) && !isset($arguments['forceCoversAnnotation'])) { $arguments['forceCoversAnnotation'] = $phpunitConfiguration['forceCoversAnnotation']; } if (isset($phpunitConfiguration['disableCodeCoverageIgnore']) && !isset($arguments['disableCodeCoverageIgnore'])) { $arguments['disableCodeCoverageIgnore'] = $phpunitConfiguration['disableCodeCoverageIgnore']; } if (isset($phpunitConfiguration['registerMockObjectsFromTestArgumentsRecursively']) && !isset($arguments['registerMockObjectsFromTestArgumentsRecursively'])) { $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $phpunitConfiguration['registerMockObjectsFromTestArgumentsRecursively']; } if (isset($phpunitConfiguration['executionOrder']) && !isset($arguments['executionOrder'])) { $arguments['executionOrder'] = $phpunitConfiguration['executionOrder']; } if (isset($phpunitConfiguration['executionOrderDefects']) && !isset($arguments['executionOrderDefects'])) { $arguments['executionOrderDefects'] = $phpunitConfiguration['executionOrderDefects']; } if (isset($phpunitConfiguration['resolveDependencies']) && !isset($arguments['resolveDependencies'])) { $arguments['resolveDependencies'] = $phpunitConfiguration['resolveDependencies']; } $groupCliArgs = []; if (!empty($arguments['groups'])) { $groupCliArgs = $arguments['groups']; } $groupConfiguration = $arguments['configuration']->getGroupConfiguration(); if (!empty($groupConfiguration['include']) && !isset($arguments['groups'])) { $arguments['groups'] = $groupConfiguration['include']; } if (!empty($groupConfiguration['exclude']) && !isset($arguments['excludeGroups'])) { $arguments['excludeGroups'] = \array_diff($groupConfiguration['exclude'], $groupCliArgs); } foreach ($arguments['configuration']->getExtensionConfiguration() as $extension) { if (!\class_exists($extension['class'], false) && $extension['file'] !== '') { require_once $extension['file']; } if (!\class_exists($extension['class'])) { throw new Exception( \sprintf( 'Class "carview.php?tsp=%s" does not exist', $extension['class'] ) ); } $extensionClass = new ReflectionClass($extension['class']); if (!$extensionClass->implementsInterface(Hook::class)) { throw new Exception( \sprintf( 'Class "carview.php?tsp=%s" does not implement a PHPUnit\Runner\Hook interface', $extension['class'] ) ); } if (\count($extension['arguments']) == 0) { $extensionObject = $extensionClass->newInstance(); } else { $extensionObject = $extensionClass->newInstanceArgs( $extension['arguments'] ); } \assert($extensionObject instanceof TestHook); $this->addExtension($extensionObject); } foreach ($arguments['configuration']->getListenerConfiguration() as $listener) { if (!\class_exists($listener['class'], false) && $listener['file'] !== '') { require_once $listener['file']; } if (!\class_exists($listener['class'])) { throw new Exception( \sprintf( 'Class "carview.php?tsp=%s" does not exist', $listener['class'] ) ); } $listenerClass = new ReflectionClass($listener['class']); if (!$listenerClass->implementsInterface(TestListener::class)) { throw new Exception( \sprintf( 'Class "carview.php?tsp=%s" does not implement the PHPUnit\Framework\TestListener interface', $listener['class'] ) ); } if (\count($listener['arguments']) == 0) { $listener = new $listener['class']; } else { $listener = $listenerClass->newInstanceArgs( $listener['arguments'] ); } $arguments['listeners'][] = $listener; } $loggingConfiguration = $arguments['configuration']->getLoggingConfiguration(); if (isset($loggingConfiguration['coverage-clover']) && !isset($arguments['coverageClover'])) { $arguments['coverageClover'] = $loggingConfiguration['coverage-clover']; } if (isset($loggingConfiguration['coverage-crap4j']) && !isset($arguments['coverageCrap4J'])) { $arguments['coverageCrap4J'] = $loggingConfiguration['coverage-crap4j']; if (isset($loggingConfiguration['crap4jThreshold']) && !isset($arguments['crap4jThreshold'])) { $arguments['crap4jThreshold'] = $loggingConfiguration['crap4jThreshold']; } } if (isset($loggingConfiguration['coverage-html']) && !isset($arguments['coverageHtml'])) { if (isset($loggingConfiguration['lowUpperBound']) && !isset($arguments['reportLowUpperBound'])) { $arguments['reportLowUpperBound'] = $loggingConfiguration['lowUpperBound']; } if (isset($loggingConfiguration['highLowerBound']) && !isset($arguments['reportHighLowerBound'])) { $arguments['reportHighLowerBound'] = $loggingConfiguration['highLowerBound']; } $arguments['coverageHtml'] = $loggingConfiguration['coverage-html']; } if (isset($loggingConfiguration['coverage-php']) && !isset($arguments['coveragePHP'])) { $arguments['coveragePHP'] = $loggingConfiguration['coverage-php']; } if (isset($loggingConfiguration['coverage-text']) && !isset($arguments['coverageText'])) { $arguments['coverageText'] = $loggingConfiguration['coverage-text']; if (isset($loggingConfiguration['coverageTextShowUncoveredFiles'])) { $arguments['coverageTextShowUncoveredFiles'] = $loggingConfiguration['coverageTextShowUncoveredFiles']; } else { $arguments['coverageTextShowUncoveredFiles'] = false; } if (isset($loggingConfiguration['coverageTextShowOnlySummary'])) { $arguments['coverageTextShowOnlySummary'] = $loggingConfiguration['coverageTextShowOnlySummary']; } else { $arguments['coverageTextShowOnlySummary'] = false; } } if (isset($loggingConfiguration['coverage-xml']) && !isset($arguments['coverageXml'])) { $arguments['coverageXml'] = $loggingConfiguration['coverage-xml']; } if (isset($loggingConfiguration['plain'])) { $arguments['listeners'][] = new ResultPrinter( $loggingConfiguration['plain'], true ); } if (isset($loggingConfiguration['teamcity']) && !isset($arguments['teamcityLogfile'])) { $arguments['teamcityLogfile'] = $loggingConfiguration['teamcity']; } if (isset($loggingConfiguration['junit']) && !isset($arguments['junitLogfile'])) { $arguments['junitLogfile'] = $loggingConfiguration['junit']; } if (isset($loggingConfiguration['testdox-html']) && !isset($arguments['testdoxHTMLFile'])) { $arguments['testdoxHTMLFile'] = $loggingConfiguration['testdox-html']; } if (isset($loggingConfiguration['testdox-text']) && !isset($arguments['testdoxTextFile'])) { $arguments['testdoxTextFile'] = $loggingConfiguration['testdox-text']; } if (isset($loggingConfiguration['testdox-xml']) && !isset($arguments['testdoxXMLFile'])) { $arguments['testdoxXMLFile'] = $loggingConfiguration['testdox-xml']; } $testdoxGroupConfiguration = $arguments['configuration']->getTestdoxGroupConfiguration(); if (isset($testdoxGroupConfiguration['include']) && !isset($arguments['testdoxGroups'])) { $arguments['testdoxGroups'] = $testdoxGroupConfiguration['include']; } if (isset($testdoxGroupConfiguration['exclude']) && !isset($arguments['testdoxExcludeGroups'])) { $arguments['testdoxExcludeGroups'] = $testdoxGroupConfiguration['exclude']; } } $arguments['addUncoveredFilesFromWhitelist'] = $arguments['addUncoveredFilesFromWhitelist'] ?? true; $arguments['backupGlobals'] = $arguments['backupGlobals'] ?? null; $arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? null; $arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? null; $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? false; $arguments['cacheResult'] = $arguments['cacheResult'] ?? true; $arguments['cacheTokens'] = $arguments['cacheTokens'] ?? false; $arguments['colors'] = $arguments['colors'] ?? ResultPrinter::COLOR_DEFAULT; $arguments['columns'] = $arguments['columns'] ?? 80; $arguments['convertDeprecationsToExceptions'] = $arguments['convertDeprecationsToExceptions'] ?? true; $arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? true; $arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? true; $arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? true; $arguments['crap4jThreshold'] = $arguments['crap4jThreshold'] ?? 30; $arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? false; $arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? false; $arguments['defaultTimeLimit'] = $arguments['defaultTimeLimit'] ?? 0; $arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? false; $arguments['excludeGroups'] = $arguments['excludeGroups'] ?? []; $arguments['failOnRisky'] = $arguments['failOnRisky'] ?? false; $arguments['failOnWarning'] = $arguments['failOnWarning'] ?? false; $arguments['executionOrderDefects'] = $arguments['executionOrderDefects'] ?? TestSuiteSorter::ORDER_DEFAULT; $arguments['groups'] = $arguments['groups'] ?? []; $arguments['processIsolation'] = $arguments['processIsolation'] ?? false; $arguments['processUncoveredFilesFromWhitelist'] = $arguments['processUncoveredFilesFromWhitelist'] ?? false; $arguments['randomOrderSeed'] = $arguments['randomOrderSeed'] ?? \time(); $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? false; $arguments['repeat'] = $arguments['repeat'] ?? false; $arguments['reportHighLowerBound'] = $arguments['reportHighLowerBound'] ?? 90; $arguments['reportLowUpperBound'] = $arguments['reportLowUpperBound'] ?? 50; $arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? true; $arguments['reverseList'] = $arguments['reverseList'] ?? false; $arguments['executionOrder'] = $arguments['executionOrder'] ?? TestSuiteSorter::ORDER_DEFAULT; $arguments['resolveDependencies'] = $arguments['resolveDependencies'] ?? true; $arguments['stopOnError'] = $arguments['stopOnError'] ?? false; $arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? false; $arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? false; $arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? false; $arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? false; $arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? false; $arguments['stopOnDefect'] = $arguments['stopOnDefect'] ?? false; $arguments['strictCoverage'] = $arguments['strictCoverage'] ?? false; $arguments['testdoxExcludeGroups'] = $arguments['testdoxExcludeGroups'] ?? []; $arguments['testdoxGroups'] = $arguments['testdoxGroups'] ?? []; $arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? 60; $arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? 10; $arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? 1; $arguments['verbose'] = $arguments['verbose'] ?? false; } /** * @throws \ReflectionException * @throws \InvalidArgumentException */ private function processSuiteFilters(TestSuite $suite, array $arguments): void { if (!$arguments['filter'] && empty($arguments['groups']) && empty($arguments['excludeGroups'])) { return; } $filterFactory = new Factory; if (!empty($arguments['excludeGroups'])) { $filterFactory->addFilter( new ReflectionClass(ExcludeGroupFilterIterator::class), $arguments['excludeGroups'] ); } if (!empty($arguments['groups'])) { $filterFactory->addFilter( new ReflectionClass(IncludeGroupFilterIterator::class), $arguments['groups'] ); } if ($arguments['filter']) { $filterFactory->addFilter( new ReflectionClass(NameFilterIterator::class), $arguments['filter'] ); } $suite->injectFilter($filterFactory); } private function writeMessage(string $type, string $message): void { if (!$this->messagePrinted) { $this->write("\n"); } $this->write( \sprintf( "%-15s%s\n", $type . ':', $message ) ); $this->messagePrinted = true; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Codeception\PHPUnit\NonFinal; use PHPUnit\Framework\TestSuite; use PHPUnit\Framework\WarningTestCase; use PHPUnit\Util\RegularExpression; use RecursiveFilterIterator; use RecursiveIterator; /** * @internal This class is not covered by the backward compatibility promise for PHPUnit */ class NameFilterIterator extends RecursiveFilterIterator { /** * @var string */ protected $filter; /** * @var int */ protected $filterMin; /** * @var int */ protected $filterMax; /** * @throws \Exception */ public function __construct(RecursiveIterator $iterator, string $filter) { parent::__construct($iterator); $this->setFilter($filter); } /** * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException */ public function accept(): bool { $test = $this->getInnerIterator()->current(); if ($test instanceof TestSuite) { return true; } $tmp = \PHPUnit\Util\Test::describe($test); if ($test instanceof WarningTestCase) { $name = $test->getMessage(); } else { if ($tmp[0] !== '') { $name = \implode('::', $tmp); } else { $name = $tmp[1]; } } $accepted = @\preg_match($this->filter, $name, $matches); if ($accepted && isset($this->filterMax)) { $set = \end($matches); $accepted = $set >= $this->filterMin && $set <= $this->filterMax; } return (bool) $accepted; } /** * @throws \Exception */ protected function setFilter(string $filter): void { if (RegularExpression::safeMatch($filter, '') === false) { // Handles: // * testAssertEqualsSucceeds#4 // * testAssertEqualsSucceeds#4-8 if (\preg_match('/^(.*?)#(\d+)(?:-(\d+))?$/', $filter, $matches)) { if (isset($matches[3]) && $matches[2] < $matches[3]) { $filter = \sprintf( '%s.*with data set #(\d+)$', $matches[1] ); $this->filterMin = $matches[2]; $this->filterMax = $matches[3]; } else { $filter = \sprintf( '%s.*with data set #%s$', $matches[1], $matches[2] ); } } // Handles: // * testDetermineJsonError@JSON_ERROR_NONE // * testDetermineJsonError@JSON.* elseif (\preg_match('/^(.*?)@(.+)$/', $filter, $matches)) { $filter = \sprintf( '%s.*with data set "carview.php?tsp=%s"$', $matches[1], $matches[2] ); } // Escape delimiters in regular expression. Do NOT use preg_quote, // to keep magic characters. $filter = \sprintf('/%s/i', \str_replace( '/', '\\/', $filter )); } $this->filter = $filter; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Codeception\PHPUnit\NonFinal; use DOMDocument; use DOMElement; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\ExceptionWrapper; use PHPUnit\Framework\SelfDescribing; use PHPUnit\Framework\Test; use PHPUnit\Framework\TestFailure; use PHPUnit\Framework\TestListener; use PHPUnit\Framework\TestSuite; use PHPUnit\Framework\Warning; use PHPUnit\Util\Filter; use PHPUnit\Util\Printer; use PHPUnit\Util\Xml; use ReflectionClass; use ReflectionException; /** * @internal This class is not covered by the backward compatibility promise for PHPUnit */ class JUnit extends Printer implements TestListener { /** * @var DOMDocument */ protected $document; /** * @var DOMElement */ protected $root; /** * @var bool */ protected $reportUselessTests = false; /** * @var bool */ protected $writeDocument = true; /** * @var DOMElement[] */ protected $testSuites = []; /** * @var int[] */ protected $testSuiteTests = [0]; /** * @var int[] */ protected $testSuiteAssertions = [0]; /** * @var int[] */ protected $testSuiteErrors = [0]; /** * @var int[] */ protected $testSuiteFailures = [0]; /** * @var int[] */ protected $testSuiteSkipped = [0]; /** * @var int[] */ protected $testSuiteTimes = [0]; /** * @var int */ protected $testSuiteLevel = 0; /** * @var DOMElement */ protected $currentTestCase; /** * Constructor. * * @param null|mixed $out * * @throws \PHPUnit\Framework\Exception */ public function __construct($out = null, bool $reportUselessTests = false) { $this->document = new DOMDocument('1.0', 'UTF-8'); $this->document->formatOutput = true; $this->root = $this->document->createElement('testsuites'); $this->document->appendChild($this->root); parent::__construct($out); $this->reportUselessTests = $reportUselessTests; } /** * Flush buffer and close output. */ public function flush(): void { if ($this->writeDocument === true) { $this->write($this->getXML()); } parent::flush(); } /** * An error occurred. * * @throws \InvalidArgumentException * @throws ReflectionException */ public function addError(Test $test, \Throwable $t, float $time): void { $this->doAddFault($test, $t, $time, 'error'); $this->testSuiteErrors[$this->testSuiteLevel]++; } /** * A warning occurred. * * @throws \InvalidArgumentException * @throws ReflectionException */ public function addWarning(Test $test, Warning $e, float $time): void { $this->doAddFault($test, $e, $time, 'warning'); $this->testSuiteFailures[$this->testSuiteLevel]++; } /** * A failure occurred. * * @throws \InvalidArgumentException * @throws ReflectionException */ public function addFailure(Test $test, AssertionFailedError $e, float $time): void { $this->doAddFault($test, $e, $time, 'failure'); $this->testSuiteFailures[$this->testSuiteLevel]++; } /** * Incomplete test. */ public function addIncompleteTest(Test $test, \Throwable $t, float $time): void { $this->doAddSkipped($test); } /** * Risky test. * * @throws ReflectionException */ public function addRiskyTest(Test $test, \Throwable $t, float $time): void { if (!$this->reportUselessTests || $this->currentTestCase === null) { return; } $error = $this->document->createElement( 'error', Xml::prepareString( "Risky Test\n" . Filter::getFilteredStacktrace($t) ) ); $error->setAttribute('type', \get_class($t)); $this->currentTestCase->appendChild($error); $this->testSuiteErrors[$this->testSuiteLevel]++; } /** * Skipped test. */ public function addSkippedTest(Test $test, \Throwable $t, float $time): void { $this->doAddSkipped($test); } /** * A testsuite started. */ public function startTestSuite(TestSuite $suite): void { $testSuite = $this->document->createElement('testsuite'); $testSuite->setAttribute('name', $suite->getName()); if (\class_exists($suite->getName(), false)) { try { $class = new ReflectionClass($suite->getName()); $testSuite->setAttribute('file', $class->getFileName()); } catch (ReflectionException $e) { } } if ($this->testSuiteLevel > 0) { $this->testSuites[$this->testSuiteLevel]->appendChild($testSuite); } else { $this->root->appendChild($testSuite); } $this->testSuiteLevel++; $this->testSuites[$this->testSuiteLevel] = $testSuite; $this->testSuiteTests[$this->testSuiteLevel] = 0; $this->testSuiteAssertions[$this->testSuiteLevel] = 0; $this->testSuiteErrors[$this->testSuiteLevel] = 0; $this->testSuiteFailures[$this->testSuiteLevel] = 0; $this->testSuiteSkipped[$this->testSuiteLevel] = 0; $this->testSuiteTimes[$this->testSuiteLevel] = 0; } /** * A testsuite ended. */ public function endTestSuite(TestSuite $suite): void { $this->testSuites[$this->testSuiteLevel]->setAttribute( 'tests', (string) $this->testSuiteTests[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'assertions', (string) $this->testSuiteAssertions[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'errors', (string) $this->testSuiteErrors[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'failures', (string) $this->testSuiteFailures[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'skipped', (string) $this->testSuiteSkipped[$this->testSuiteLevel] ); $this->testSuites[$this->testSuiteLevel]->setAttribute( 'time', \sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel]) ); if ($this->testSuiteLevel > 1) { $this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel]; $this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel]; $this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel]; $this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel]; $this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel]; $this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel]; } $this->testSuiteLevel--; } /** * A test started. * * @throws \SebastianBergmann\RecursionContext\InvalidArgumentException * @throws ReflectionException */ public function startTest(Test $test): void { $usesDataprovider = false; if (\method_exists($test, 'usesDataProvider')) { $usesDataprovider = $test->usesDataProvider(); } $testCase = $this->document->createElement('testcase'); $testCase->setAttribute('name', $test->getName()); $class = new ReflectionClass($test); $methodName = $test->getName(!$usesDataprovider); if ($class->hasMethod($methodName)) { $method = $class->getMethod($methodName); $testCase->setAttribute('class', $class->getName()); $testCase->setAttribute('classname', \str_replace('\\', '.', $class->getName())); $testCase->setAttribute('file', $class->getFileName()); $testCase->setAttribute('line', (string) $method->getStartLine()); } $this->currentTestCase = $testCase; } /** * A test ended. */ public function endTest(Test $test, float $time): void { $numAssertions = 0; if (\method_exists($test, 'getNumAssertions')) { $numAssertions = $test->getNumAssertions(); } $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions; $this->currentTestCase->setAttribute( 'assertions', (string) $numAssertions ); $this->currentTestCase->setAttribute( 'time', \sprintf('%F', $time) ); $this->testSuites[$this->testSuiteLevel]->appendChild( $this->currentTestCase ); $this->testSuiteTests[$this->testSuiteLevel]++; $this->testSuiteTimes[$this->testSuiteLevel] += $time; $testOutput = ''; if (\method_exists($test, 'hasOutput') && \method_exists($test, 'getActualOutput')) { $testOutput = $test->hasOutput() ? $test->getActualOutput() : ''; } if (!empty($testOutput)) { $systemOut = $this->document->createElement( 'system-out', Xml::prepareString($testOutput) ); $this->currentTestCase->appendChild($systemOut); } $this->currentTestCase = null; } /** * Returns the XML as a string. */ public function getXML(): string { return $this->document->saveXML(); } /** * Enables or disables the writing of the document * in flush(). * * This is a "hack" needed for the integration of * PHPUnit with Phing. */ public function setWriteDocument(/*bool*/ $flag): void { if (\is_bool($flag)) { $this->writeDocument = $flag; } } /** * Method which generalizes addError() and addFailure() * * @throws \InvalidArgumentException * @throws ReflectionException */ private function doAddFault(Test $test, \Throwable $t, float $time, $type): void { if ($this->currentTestCase === null) { return; } if ($test instanceof SelfDescribing) { $buffer = $test->toString() . "\n"; } else { $buffer = ''; } $buffer .= TestFailure::exceptionToString($t) . "\n" . Filter::getFilteredStacktrace($t); $fault = $this->document->createElement( $type, Xml::prepareString($buffer) ); if ($t instanceof ExceptionWrapper) { $fault->setAttribute('type', $t->getClassName()); } else { $fault->setAttribute('type', \get_class($t)); } $this->currentTestCase->appendChild($fault); } private function doAddSkipped(Test $test): void { if ($this->currentTestCase === null) { return; } $skipped = $this->document->createElement('skipped'); $this->currentTestCase->appendChild($skipped); $this->testSuiteSkipped[$this->testSuiteLevel]++; } } _setUp(); } } protected function tearDown(): void { if (method_exists($this, '_tearDown')) { $this->_tearDown(); } } public static function setUpBeforeClass(): void { if (method_exists(get_called_class(), '_setUpBeforeClass')) { static::_setUpBeforeClass(); } } public static function tearDownAfterClass(): void { if (method_exists(get_called_class(), '_tearDownAfterClass')) { static::_tearDownAfterClass(); } } public function expectExceptionMessageRegExp(string $regularExpression): void { $this->expectExceptionMessageMatches($regularExpression); } } getFileName(); } else { $reflector = new \ReflectionClass($test); $filename = $reflector->getFileName(); } if ($filename !== $this->currentFile) { if ($this->currentFile !== null) { parent::endTestSuite(new TestSuite()); } //initialize all values to avoid warnings $this->testSuiteAssertions[self::FILE_LEVEL] = 0; $this->testSuiteTests[self::FILE_LEVEL] = 0; $this->testSuiteTimes[self::FILE_LEVEL] = 0; $this->testSuiteErrors[self::FILE_LEVEL] = 0; $this->testSuiteFailures[self::FILE_LEVEL] = 0; $this->testSuiteSkipped[self::FILE_LEVEL] = 0; $this->testSuiteLevel = self::FILE_LEVEL; $this->currentFile = $filename; $this->currentFileSuite = $this->document->createElement('testsuite'); if ($test instanceof Reported) { $reportFields = $test->getReportFields(); $class = isset($reportFields['class']) ? $reportFields['class'] : $reportFields['name']; $this->currentFileSuite->setAttribute('name', $class); } else { $this->currentFileSuite->setAttribute('name', get_class($test)); } $this->currentFileSuite->setAttribute('file', $filename); $this->testSuites[self::SUITE_LEVEL]->appendChild($this->currentFileSuite); $this->testSuites[self::FILE_LEVEL] = $this->currentFileSuite; } if (!$test instanceof Reported) { parent::startTest($test); return; } $this->currentTestCase = $this->document->createElement('testcase'); $isStrict = Configuration::config()['settings']['strict_xml']; foreach ($test->getReportFields() as $attr => $value) { if ($isStrict and !in_array($attr, $this->strictAttributes)) { continue; } $this->currentTestCase->setAttribute($attr, $value); } } public function endTest(\PHPUnit\Framework\Test $test, float $time):void { if ($this->currentTestCase !== null && $test instanceof Test) { $numAssertions = $test->getNumAssertions(); $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions; $this->currentTestCase->setAttribute( 'assertions', $numAssertions ); } if ($test instanceof TestCase) { parent::endTest($test, $time); return; } // In PhpUnit 7.4.*, parent::endTest ignores tests that aren't instances of TestCase // so I copied this code from PhpUnit 7.3.5 $this->currentTestCase->setAttribute( 'time', \sprintf('%F', $time) ); $this->testSuites[$this->testSuiteLevel]->appendChild( $this->currentTestCase ); $this->testSuiteTests[$this->testSuiteLevel]++; $this->testSuiteTimes[$this->testSuiteLevel] += $time; $this->currentTestCase = null; } /** * Cleans the mess caused by test suite manipulation in startTest */ public function endTestSuite(TestSuite $suite): void { if ($suite->getName()) { if ($this->currentFile) { //close last file in the test suite parent::endTestSuite(new TestSuite()); $this->currentFile = null; } $this->testSuiteLevel = self::SUITE_LEVEL; } parent::endTestSuite($suite); } } currentTestCase = $this->document->createElement('testcase'); $isStrict = Configuration::config()['settings']['strict_xml']; foreach ($test->getReportFields() as $attr => $value) { if ($isStrict and !in_array($attr, $this->strictAttributes)) { continue; } $this->currentTestCase->setAttribute($attr, $value); } } public function endTest(\PHPUnit\Framework\Test $test, float $time):void { if ($this->currentTestCase !== null and $test instanceof Test) { $numAssertions = $test->getNumAssertions(); $this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions; $this->currentTestCase->setAttribute( 'assertions', $numAssertions ); } if ($test instanceof TestCase) { parent::endTest($test, $time); return; } // since PhpUnit 7.4.0, parent::endTest ignores tests that aren't instances of TestCase // so I copied this code from PhpUnit 7.3.5 $this->currentTestCase->setAttribute( 'time', \sprintf('%F', $time) ); $this->testSuites[$this->testSuiteLevel]->appendChild( $this->currentTestCase ); $this->testSuiteTests[$this->testSuiteLevel]++; $this->testSuiteTimes[$this->testSuiteLevel] += $time; $this->currentTestCase = null; } } testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR; $this->failed++; } /** * A failure occurred. * * @param \PHPUnit\Framework\Test $test * @param \PHPUnit\Framework\AssertionFailedError $e * @param float $time */ public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time) : void { $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE; $this->failed++; } /** * A warning occurred. * * @param \PHPUnit\Framework\Test $test * @param \PHPUnit\Framework\Warning $e * @param float $time */ public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time): void { $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_WARNING; $this->warned++; } /** * Incomplete test. * * @param \PHPUnit\Framework\Test $test * @param \Throwable $e * @param float $time */ public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE; $this->incomplete++; } /** * Risky test. * * @param \PHPUnit\Framework\Test $test * @param \Throwable $e * @param float $time * * @since Method available since Release 4.0.0 */ public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_RISKY; $this->risky++; } /** * Skipped test. * * @param \PHPUnit\Framework\Test $test * @param \Throwable $e * @param float $time * * @since Method available since Release 3.0.0 */ public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time) : void { $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED; $this->skipped++; } public function startTest(\PHPUnit\Framework\Test $test) : void { $this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace { if (!class_exists('PHPUnit_Util_String')) { /** * String helpers. */ class PHPUnit_Util_String { /** * Converts a string to UTF-8 encoding. * * @param string $string * * @return string */ public static function convertToUtf8($string) { return mb_convert_encoding($string, 'UTF-8'); } /** * Checks a string for UTF-8 encoding. * * @param string $string * * @return bool */ protected static function isUtf8($string) { $length = strlen($string); for ($i = 0; $i < $length; $i++) { if (ord($string[$i]) < 0x80) { $n = 0; } elseif ((ord($string[$i]) & 0xE0) == 0xC0) { $n = 1; } elseif ((ord($string[$i]) & 0xF0) == 0xE0) { $n = 2; } elseif ((ord($string[$i]) & 0xF0) == 0xF0) { $n = 3; } else { return false; } for ($j = 0; $j < $n; $j++) { if ((++$i == $length) || ((ord($string[$i]) & 0xC0) != 0x80)) { return false; } } } return true; } } } } namespace PHPUnit\Util\Log { /* * This file is part of PHPUnit. * * (c) Sebastian Bergmann * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use Codeception\Test\Descriptor; /** * A TestListener that generates JSON messages. */ if (!class_exists('\PHPUnit\Util\Log\JSON')) { class JSON extends \PHPUnit\Util\Printer implements \PHPUnit\Framework\TestListener { /** * @var string */ protected $currentTestSuiteName = ''; /** * @var string */ protected $currentTestName = ''; /** * @var bool */ protected $currentTestPass = true; /** * @var array */ protected $logEvents = []; /** * An error occurred. * * @param \PHPUnit\Framework\Test $test * @param \Throwable $e * @param float $time */ public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void { $this->writeCase( 'error', $time, \PHPUnit\Util\Filter::getFilteredStacktrace($e, false), \PHPUnit\Framework\TestFailure::exceptionToString($e), $test ); $this->currentTestPass = false; } /** * A warning occurred. * * @param \PHPUnit\Framework\Test $test * @param \PHPUnit\Framework\Warning $e * @param float $time */ public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time): void { $this->writeCase( 'warning', $time, \PHPUnit\Util\Filter::getFilteredStacktrace($e, false), \PHPUnit\Framework\TestFailure::exceptionToString($e), $test ); $this->currentTestPass = false; } /** * A failure occurred. * * @param \PHPUnit\Framework\Test $test * @param \PHPUnit\Framework\AssertionFailedError $e * @param float $time */ public function addFailure( \PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time ): void{ $this->writeCase( 'fail', $time, \PHPUnit\Util\Filter::getFilteredStacktrace($e, false), \PHPUnit\Framework\TestFailure::exceptionToString($e), $test ); $this->currentTestPass = false; } /** * Incomplete test. * * @param \PHPUnit\Framework\Test $test * @param Throwable $e * @param float $time */ public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void { $this->writeCase( 'error', $time, \PHPUnit\Util\Filter::getFilteredStacktrace($e, false), 'Incomplete Test: ' . $e->getMessage(), $test ); $this->currentTestPass = false; } /** * Risky test. * * @param \PHPUnit\Framework\Test $test * @param Throwable $e * @param float $time */ public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void { $this->writeCase( 'error', $time, \PHPUnit\Util\Filter::getFilteredStacktrace($e, false), 'Risky Test: ' . $e->getMessage(), $test ); $this->currentTestPass = false; } /** * Skipped test. * * @param \PHPUnit\Framework\Test $test * @param Throwable $e * @param float $time */ public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void { $this->writeCase( 'error', $time, \PHPUnit\Util\Filter::getFilteredStacktrace($e, false), 'Skipped Test: ' . $e->getMessage(), $test ); $this->currentTestPass = false; } /** * A testsuite started. * * @param \PHPUnit\Framework\TestSuite $suite */ public function startTestSuite(\PHPUnit\Framework\TestSuite $suite): void { $this->currentTestSuiteName = $suite->getName(); $this->currentTestName = ''; $this->addLogEvent( [ 'event' => 'suiteStart', 'suite' => $this->currentTestSuiteName, 'tests' => count($suite) ] ); } /** * A testsuite ended. * * @param \PHPUnit\Framework\TestSuite $suite */ public function endTestSuite(\PHPUnit\Framework\TestSuite $suite): void { $this->currentTestSuiteName = ''; $this->currentTestName = ''; $this->writeArray($this->logEvents); } /** * A test started. * * @param \PHPUnit\Framework\Test $test */ public function startTest(\PHPUnit\Framework\Test $test): void { $this->currentTestName = \PHPUnit\Util\Test::describe($test); $this->currentTestPass = true; $this->addLogEvent( [ 'event' => 'testStart', 'suite' => $this->currentTestSuiteName, 'test' => $this->currentTestName ] ); } /** * A test ended. * * @param \PHPUnit\Framework\Test $test * @param float $time */ public function endTest(\PHPUnit\Framework\Test $test, float $time): void { if ($this->currentTestPass) { $this->writeCase('pass', $time, [], '', $test); } } /** * @param string $status * @param float $time * @param array $trace * @param string $message * @param \PHPUnit\Framework\TestCase|null $test */ protected function writeCase($status, float $time, array $trace = [], $message = '', $test = null): void { $output = ''; // take care of TestSuite producing error (e.g. by running into exception) as TestSuite doesn't have hasOutput if ($test !== null && method_exists($test, 'hasOutput') && $test->hasOutput()) { $output = $test->getActualOutput(); } $this->addLogEvent( [ 'event' => 'test', 'suite' => $this->currentTestSuiteName, 'test' => $this->currentTestName, 'status' => $status, 'time' => $time, 'trace' => $trace, 'message' => \PHPUnit_Util_String::convertToUtf8($message), 'output' => $output, ] ); } /** * @param array $event_data */ protected function addLogEvent($event_data = []): void { if (count($event_data)) { array_push($this->logEvents, $event_data); } } /** * @param array $buffer */ public function writeArray($buffer) { array_walk_recursive( $buffer, function (&$input){ if (is_string($input)) { $input = \PHPUnit_Util_String::convertToUtf8($input); } } ); $this->write(json_encode($buffer, JSON_PRETTY_PRINT)); } } } /* * This file is part of PHPUnit. * * (c) Sebastian Bergmann * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ if (!class_exists('\PHPUnit\Util\Log\TAP')) { /** * A TestListener that generates a logfile of the * test execution using the Test Anything Protocol (TAP). */ class TAP extends \PHPUnit\Util\Printer implements \PHPUnit\Framework\TestListener { /** * @var int */ protected $testNumber = 0; /** * @var int */ protected $testSuiteLevel = 0; /** * @var bool */ protected $testSuccessful = true; /** * Constructor. * * @param mixed $out * * @throws \PHPUnit\Framework\Throwable */ public function __construct($out = null) { parent::__construct($out); $this->write("TAP version 13\n"); } /** * An error occurred. * * @param \PHPUnit\Framework\Test $test * @param Throwable $e * @param float $time */ public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void { $this->writeNotOk($test, 'Error'); } /** * A warning occurred. * * @param \PHPUnit\Framework\Test $test * @param \PHPUnit\Framework\Warning $e * @param float $time */ public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time): void { $this->writeNotOk($test, 'Warning'); } /** * A failure occurred. * * @param \PHPUnit\Framework\Test $test * @param \PHPUnit\Framework\AssertionFailedError $e * @param float $time */ public function addFailure( \PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time ): void{ $this->writeNotOk($test, 'Failure'); $message = explode( "\n", \PHPUnit\Framework\TestFailure::exceptionToString($e) ); $diagnostic = [ 'message' => $message[0], 'severity' => 'fail' ]; if ($e instanceof \PHPUnit\Framework\ExpectationFailedThrowable) { $cf = $e->getComparisonFailure(); if ($cf !== null) { $diagnostic['data'] = [ 'got' => $cf->getActual(), 'expected' => $cf->getExpected() ]; } } $yaml = new \Symfony\Component\Yaml\Dumper; $this->write( sprintf( " ---\n%s ...\n", $yaml->dump($diagnostic, 2, 2) ) ); } /** * Incomplete test. * * @param \PHPUnit\Framework\Test $test * @param \Throwable $e * @param float $time */ public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void { $this->writeNotOk($test, '', 'TODO Incomplete Test'); } /** * Risky test. * * @param \PHPUnit\Framework\Test $test * @param Throwable $e * @param float $time */ public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void { $this->write( sprintf( "ok %d - # RISKY%s\n", $this->testNumber, $e->getMessage() != '' ? ' ' . $e->getMessage() : '' ) ); $this->testSuccessful = false; } /** * Skipped test. * * @param \PHPUnit\Framework\Test $test * @param Throwable $e * @param float $time */ public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void { $this->write( sprintf( "ok %d - # SKIP%s\n", $this->testNumber, $e->getMessage() != '' ? ' ' . $e->getMessage() : '' ) ); $this->testSuccessful = false; } /** * A testsuite started. * * @param \PHPUnit\Framework\TestSuite $suite */ public function startTestSuite(\PHPUnit\Framework\TestSuite $suite): void { $this->testSuiteLevel++; } /** * A testsuite ended. * * @param \PHPUnit\Framework\TestSuite $suite */ public function endTestSuite(\PHPUnit\Framework\TestSuite $suite): void { $this->testSuiteLevel--; if ($this->testSuiteLevel == 0) { $this->write(sprintf("1..%d\n", $this->testNumber)); } } /** * A test started. * * @param \PHPUnit\Framework\Test $test */ public function startTest(\PHPUnit\Framework\Test $test): void { $this->testNumber++; $this->testSuccessful = true; } /** * A test ended. * * @param \PHPUnit\Framework\Test $test * @param float $time */ public function endTest(\PHPUnit\Framework\Test $test, float $time): void { if ($this->testSuccessful === true) { $this->write( sprintf( "ok %d - %s\n", $this->testNumber, Descriptor::getTestSignature($test) ) ); } $this->writeDiagnostics($test); } /** * @param \PHPUnit\Framework\Test $test * @param string $prefix * @param string $directive */ protected function writeNotOk(\PHPUnit\Framework\Test $test, $prefix = '', $directive = '') { $this->write( sprintf( "not ok %d - %s%s%s\n", $this->testNumber, $prefix != '' ? $prefix . ': ' : '', \PHPUnit\Util\Test::describeAsString($test), $directive != '' ? ' # ' . $directive : '' ) ); $this->testSuccessful = false; } /** * @param \PHPUnit\Framework\Test $test */ private function writeDiagnostics(\PHPUnit\Framework\Test $test) { if (!$test instanceof \PHPUnit\Framework\TestCase) { return; } if (!$test->hasOutput()) { return; } foreach (explode("\n", trim($test->getActualOutput())) as $line) { $this->write( sprintf( "# %s\n", $line ) ); } } } } } // @codingStandardsIgnoreEnd false, 'phpunit-xml' => false, 'html' => false, 'tap' => false, 'json' => false, 'report' => false ]; protected $config = []; protected $logDir = null; public function __construct() { $this->config = Configuration::config(); $this->logDir = Configuration::outputDir(); // prepare log dir $this->phpUnitOverriders(); parent::__construct(); } public function phpUnitOverriders() { require_once __DIR__ . DIRECTORY_SEPARATOR . 'Overrides/Filter.php'; } /** * @return null|\PHPUnit\TextUI\ResultPrinter */ public function getPrinter() { return $this->printer; } public function prepareSuite(\PHPUnit\Framework\Test $suite, array &$arguments) { $this->handleConfiguration($arguments); $filterAdded = false; $filterFactory = new \PHPUnit\Runner\Filter\Factory(); if ($arguments['groups']) { $filterAdded = true; $filterFactory->addFilter( new \ReflectionClass('PHPUnit\Runner\Filter\IncludeGroupFilterIterator'), $arguments['groups'] ); } if ($arguments['excludeGroups']) { $filterAdded = true; $filterFactory->addFilter( new \ReflectionClass('PHPUnit\Runner\Filter\ExcludeGroupFilterIterator'), $arguments['excludeGroups'] ); } if ($arguments['filter']) { $filterAdded = true; $filterFactory->addFilter( new \ReflectionClass('Codeception\PHPUnit\FilterTest'), $arguments['filter'] ); } if ($filterAdded) { $suite->injectFilter($filterFactory); } } public function doEnhancedRun( \PHPUnit\Framework\Test $suite, \PHPUnit\Framework\TestResult $result, array $arguments = [] ) { unset($GLOBALS['app']); // hook for not to serialize globals $result->convertErrorsToExceptions(false); if (isset($arguments['report_useless_tests'])) { $result->beStrictAboutTestsThatDoNotTestAnything((bool)$arguments['report_useless_tests']); } if (isset($arguments['disallow_test_output'])) { $result->beStrictAboutOutputDuringTests((bool)$arguments['disallow_test_output']); } if (empty(self::$persistentListeners)) { $this->applyReporters($result, $arguments); } if (class_exists('\Symfony\Bridge\PhpUnit\SymfonyTestsListener')) { $arguments['listeners'] = isset($arguments['listeners']) ? $arguments['listeners'] : []; $listener = new \Symfony\Bridge\PhpUnit\SymfonyTestsListener(); $listener->globalListenerDisabled(); $arguments['listeners'][] = $listener; } $arguments['listeners'][] = $this->printer; // clean up listeners between suites foreach ($arguments['listeners'] as $listener) { $result->addListener($listener); } $suite->run($result); unset($suite); foreach ($arguments['listeners'] as $listener) { $result->removeListener($listener); } return $result; } /** * @param \PHPUnit\Framework\TestResult $result * @param array $arguments * * @return array */ protected function applyReporters(\PHPUnit\Framework\TestResult $result, array $arguments) { foreach ($this->defaultListeners as $listener => $value) { if (!isset($arguments[$listener])) { $arguments[$listener] = $value; } } if ($arguments['report']) { self::$persistentListeners[] = $this->instantiateReporter('report'); } if ($arguments['html']) { codecept_debug('Printing HTML report into ' . $arguments['html']); self::$persistentListeners[] = $this->instantiateReporter( 'html', [$this->absolutePath($arguments['html'])] ); } if ($arguments['xml']) { codecept_debug('Printing JUNIT report into ' . $arguments['xml']); self::$persistentListeners[] = $this->instantiateReporter( 'xml', [$this->absolutePath($arguments['xml']), (bool)$arguments['log_incomplete_skipped']] ); } if ($arguments['phpunit-xml']) { codecept_debug('Printing PHPUNIT report into ' . $arguments['phpunit-xml']); self::$persistentListeners[] = $this->instantiateReporter( 'phpunit-xml', [$this->absolutePath($arguments['phpunit-xml']), (bool)$arguments['log_incomplete_skipped']] ); } if ($arguments['tap']) { codecept_debug('Printing TAP report into ' . $arguments['tap']); self::$persistentListeners[] = $this->instantiateReporter('tap', [$this->absolutePath($arguments['tap'])]); } if ($arguments['json']) { codecept_debug('Printing JSON report into ' . $arguments['json']); self::$persistentListeners[] = $this->instantiateReporter( 'json', [$this->absolutePath($arguments['json'])] ); } foreach (self::$persistentListeners as $listener) { if ($listener instanceof ConsolePrinter) { $this->printer = $listener; continue; } $result->addListener($listener); } } protected function instantiateReporter($name, $args = []) { if (!isset($this->config['reporters'][$name])) { throw new ConfigurationException("Reporter $name not defined"); } return (new \ReflectionClass($this->config['reporters'][$name]))->newInstanceArgs($args); } private function absolutePath($path) { if ((strpos($path, '/') === 0) or (strpos($path, ':') === 1)) { // absolute path return $path; } return $this->logDir . $path; } } getInnerIterator()->current(); if ($test instanceof \PHPUnit\Framework\TestSuite) { return true; } $name = Descriptor::getTestSignature($test); $index = Descriptor::getTestDataSetIndex($test); if (!is_null($index)) { $name .= " with data set #{$index}"; } $accepted = preg_match($this->filter, $name, $matches); // This fix the issue when an invalid dataprovider method generate a warning // See issue https://github.com/Codeception/Codeception/issues/4888 if($test instanceof \PHPUnit\Framework\WarningTestCase) { $message = $test->getMessage(); $accepted = preg_match($this->filter, $message, $matches); } if ($accepted && isset($this->filterMax)) { $set = end($matches); $accepted = $set >= $this->filterMin && $set <= $this->filterMax; } return $accepted; } } getPrevious() ? $e->getPrevious()->getTrace() : $e->getTrace(); if ($e instanceof \PHPUnit\Framework\ExceptionWrapper) { $trace = $e->getSerializableTrace(); } $eFile = $e->getFile(); $eLine = $e->getLine(); if (!self::frameExists($trace, $eFile, $eLine)) { array_unshift( $trace, ['file' => $eFile, 'line' => $eLine] ); } foreach ($trace as $step) { if (self::classIsFiltered($step) and $filter) { continue; } if (self::fileIsFiltered($step) and $filter) { continue; } if (!$asString) { $stackTrace[] = $step; continue; } if (!isset($step['file'])) { continue; } $stackTrace .= $step['file'] . ':' . $step['line'] . "\n"; } return $stackTrace; } protected static function classIsFiltered($step) { if (!isset($step['class'])) { return false; } $className = $step['class']; foreach (self::$filteredClassesPattern as $filteredClassName) { if (strpos($className, $filteredClassName) === 0) { return true; } } return false; } protected static function fileIsFiltered($step) { if (!isset($step['file'])) { return false; } if (strpos($step['file'], 'codecept.phar/') !== false) { return true; } if (strpos($step['file'], 'vendor' . DIRECTORY_SEPARATOR . 'phpunit') !== false) { return true; } if (strpos($step['file'], 'vendor' . DIRECTORY_SEPARATOR . 'codeception') !== false) { return true; } $modulePath = 'src' . DIRECTORY_SEPARATOR . 'Codeception' . DIRECTORY_SEPARATOR . 'Module'; if (strpos($step['file'], $modulePath) !== false) { return false; // don`t filter modules } if (strpos($step['file'], 'src' . DIRECTORY_SEPARATOR . 'Codeception' . DIRECTORY_SEPARATOR) !== false) { return true; } return false; } /** * @param array $trace * @param string $file * @param int $line * * @return bool */ private static function frameExists(array $trace, $file, $line) { foreach ($trace as $frame) { if (isset($frame['file']) && $frame['file'] == $file && isset($frame['line']) && $frame['line'] == $line) { return true; } } return false; } } '*']; file_put_contents(__DIR__ . '/composer.json', json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); } public function prepareTests() { $this->_copyDir(__DIR__ . '/vendor/codeception/codeception/tests', __DIR__ . '/tests'); $this->_copy(__DIR__ . '/vendor/codeception/codeception/codeception.yml', __DIR__ .'/codeception.yml'); $this->_symlink(__DIR__ . '/vendor/bin/codecept', __DIR__ . '/codecept'); } public function prepareTestAutoloading() { $config = json_decode(file_get_contents(__DIR__ . '/composer.json'), true); $config['autoload-dev'] = [ 'classmap' => [ 'tests/cli/_steps', 'tests/data/DummyClass.php', 'tests/data/claypit/tests/_data' ] ]; file_put_contents(__DIR__ . '/composer.json', json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); } } loadSessionSnapshot('login')) return; * * // logging in * $I->amOnPage('/login'); * $I->fillField('name', 'jon'); * $I->fillField('password', '123345'); * $I->click('Login'); * * // saving snapshot * $I->saveSessionSnapshot('login'); * } * ?> * ``` * * @param $name * @return mixed */ public function saveSessionSnapshot($name); /** * Loads cookies from a saved snapshot. * Allows to reuse same session across tests without additional login. * * See [saveSessionSnapshot](#saveSessionSnapshot) * * @param $name * @return mixed */ public function loadSessionSnapshot($name); /** * Deletes session snapshot. * * See [saveSessionSnapshot](#saveSessionSnapshot) * * @param $name * @return mixed */ public function deleteSessionSnapshot($name); } getModule('{{MODULE_NAME}}')->_saveScreenshot(codecept_output_dir().'screenshot_1.png'); * ``` * @api * @param $filename */ public function _saveScreenshot($filename); } :@ondemand.saucelabs.com' * port: 80 * browser: chrome * capabilities: * platform: 'Windows 10' * ``` * * ### BrowserStack * * 1. Create an account at [BrowserStack](https://www.browserstack.com/) to get your username and access key * 2. In the module configuration use the format `username`:`access_key`@hub.browserstack.com' for `host` * 3. Configure `os` and `os_version` under `capabilities` to define the operating System * 4. If your site is available only locally or via VPN you should use a tunnel app. In this case add `browserstack.local` capability and set it to true. * * ```yaml * modules: * enabled: * - WebDriver: * url: https://mysite.com * host: ':@hub.browserstack.com' * port: 80 * browser: chrome * capabilities: * os: Windows * os_version: 10 * browserstack.local: true # for local testing * ``` * * ### LambdaTest * * 1. Create an account at [LambdaTest](https://www.lambdatest.com/) to get your username and access key * 2. In the module configuration use the format `username`:`access key`@hub.lambdatest.com' for `host` * 3. Configure `os` and `os_version` under `capabilities` to define the operating System * 4. If your site is available only locally or via VPN you should use a tunnel app. In this case add capabilities.setCapability("tunnel",true);. * * ```yaml * modules: * enabled: * - WebDriver: * url: https://mysite.com * host: ':@hub.lambdatest.com' * build: * name: * port: 80 * browser: chrome * capabilities: * os: Windows * os_version: 10 * browser_version: 86 * resolution: 1366x768 * tunnel: true # for local testing * ``` * * ### TestingBot * * 1. Create an account at [TestingBot](https://testingbot.com/) to get your key and secret * 2. In the module configuration use the format `key`:`secret`@hub.testingbot.com' for `host` * 3. Configure `platform` under `capabilities` to define the [Operating System](https://testingbot.com/support/getting-started/browsers.html) * 4. Run [TestingBot Tunnel](https://testingbot.com/support/other/tunnel) if your site can't be accessed from Internet * * ```yaml * modules: * enabled: * - WebDriver: * url: https://mysite.com * host: ':@hub.testingbot.com' * port: 80 * browser: chrome * capabilities: * platform: Windows 10 * ``` * * ## Configuration * * * `url` *required* - Base URL for your app (amOnPage opens URLs relative to this setting). * * `browser` *required* - Browser to launch. * * `host` - Selenium server host (127.0.0.1 by default). * * `port` - Selenium server port (4444 by default). * * `restart` - Set to `false` (default) to use the same browser window for all tests, or set to `true` to create a new window for each test. In any case, when all tests are finished the browser window is closed. * * `start` - Autostart a browser for tests. Can be disabled if browser session is started with `_initializeSession` inside a Helper. * * `window_size` - Initial window size. Set to `maximize` or a dimension in the format `640x480`. * * `clear_cookies` - Set to false to keep cookies, or set to true (default) to delete all cookies between tests. * * `wait` (default: 0 seconds) - Whenever element is required and is not on page, wait for n seconds to find it before fail. * * `capabilities` - Sets Selenium [desired capabilities](https://github.com/SeleniumHQ/selenium/wiki/DesiredCapabilities). Should be a key-value array. * * `connection_timeout` - timeout for opening a connection to remote selenium server (30 seconds by default). * * `request_timeout` - timeout for a request to return something from remote selenium server (30 seconds by default). * * `pageload_timeout` - amount of time to wait for a page load to complete before throwing an error (default 0 seconds). * * `http_proxy` - sets http proxy server url for testing a remote server. * * `http_proxy_port` - sets http proxy server port * * `ssl_proxy` - sets ssl(https) proxy server url for testing a remote server. * * `ssl_proxy_port` - sets ssl(https) proxy server port * * `debug_log_entries` - how many selenium entries to print with `debugWebDriverLogs` or on fail (0 by default). * * `log_js_errors` - Set to true to include possible JavaScript to HTML report, or set to false (default) to deactivate. * * `webdriver_proxy` - sets http proxy to tunnel requests to the remote Selenium WebDriver through * * `webdriver_proxy_port` - sets http proxy server port to tunnel requests to the remote Selenium WebDriver through * * Example (`acceptance.suite.yml`) * * ```yaml * modules: * enabled: * - WebDriver: * url: 'https://localhost/' * browser: firefox * window_size: 1024x768 * capabilities: * unexpectedAlertBehaviour: 'accept' * firefox_profile: '~/firefox-profiles/codeception-profile.zip.b64' * ``` * * ## Loading Parts from other Modules * * While all Codeception modules are designed to work stand-alone, it's still possible to load *several* modules at once. To use e.g. the [Asserts module](https://codeception.com/docs/modules/Asserts) in your acceptance tests, just load it like this in your `acceptance.suite.yml`: * * ```yaml * modules: * enabled: * - WebDriver * - Asserts * ``` * * However, when loading a framework module (e.g. [Symfony](https://codeception.com/docs/modules/Symfony)) like this, it would lead to a conflict: When you call `$I->amOnPage()`, Codeception wouldn't know if you want to access the page using WebDriver's `amOnPage()`, or Symfony's `amOnPage()`. That's why possibly conflicting modules are separated into "parts". Here's how to load just the "services" part from e.g. Symfony: * ```yaml * modules: * enabled: * - WebDriver * - Symfony: * part: services * ``` * To find out which parts each module has, look at the "Parts" header on the module's page. * * ## Usage * * ### Locating Elements * * Most methods in this module that operate on a DOM element (e.g. `click`) accept a locator as the first argument, * which can be either a string or an array. * * If the locator is an array, it should have a single element, * with the key signifying the locator type (`id`, `name`, `css`, `xpath`, `link`, or `class`) * and the value being the locator itself. * This is called a "strict" locator. * Examples: * * * `['id' => 'foo']` matches `
` * * `['name' => 'foo']` matches `
` * * `['css' => 'input[type=input][value=foo]']` matches `` * * `['xpath' => "//input[@type='submit'][contains(@value, 'foo')]"]` matches `` * * `['link' => 'Click here']` matches `Click here` * * `['class' => 'foo']` matches `
` * * Writing good locators can be tricky. * The Mozilla team has written an excellent guide titled [Writing reliable locators for Selenium and WebDriver tests](https://blog.mozilla.org/webqa/2013/09/26/writing-reliable-locators-for-selenium-and-webdriver-tests/). * * If you prefer, you may also pass a string for the locator. This is called a "fuzzy" locator. * In this case, Codeception uses a a variety of heuristics (depending on the exact method called) to determine what element you're referring to. * For example, here's the heuristic used for the `submitForm` method: * * 1. Does the locator look like an ID selector (e.g. "#foo")? If so, try to find a form matching that ID. * 2. If nothing found, check if locator looks like a CSS selector. If so, run it. * 3. If nothing found, check if locator looks like an XPath expression. If so, run it. * 4. Throw an `ElementNotFound` exception. * * Be warned that fuzzy locators can be significantly slower than strict locators. * Especially if you use Selenium WebDriver with `wait` (aka implicit wait) option. * In the example above if you set `wait` to 5 seconds and use XPath string as fuzzy locator, * `submitForm` method will wait for 5 seconds at each step. * That means 5 seconds finding the form by ID, another 5 seconds finding by CSS * until it finally tries to find the form by XPath). * If speed is a concern, it's recommended you stick with explicitly specifying the locator type via the array syntax. * * ### Get Scenario Metadata * * You can inject `\Codeception\Scenario` into your test to get information about the current configuration: * ```php * use Codeception\Scenario * public function myTest(AcceptanceTester $I, Scenario $scenario) * { * if ('firefox' === $scenario->current('browser')) { * // ... * } * } * ``` * See [Get Scenario Metadata](https://codeception.com/docs/07-AdvancedUsage#Get-Scenario-Metadata) for more information on `$scenario`. * * ## Public Properties * * * `webDriver` - instance of `\Facebook\WebDriver\Remote\RemoteWebDriver`. Can be accessed from Helper classes for complex WebDriver interactions. * * ```php * // inside Helper class * $this->getModule('WebDriver')->webDriver->getKeyboard()->sendKeys('hello, webdriver'); * ``` * */ class WebDriver extends CodeceptionModule implements WebInterface, RemoteInterface, MultiSessionInterface, SessionSnapshot, ScreenshotSaver, PageSourceSaver, ElementLocator, ConflictsWithModule, RequiresPackage { protected $requiredFields = ['browser', 'url']; protected $config = [ 'protocol' => 'http', 'host' => '127.0.0.1', 'port' => '4444', 'path' => '/wd/hub', 'start' => true, 'restart' => false, 'wait' => 0, 'clear_cookies' => true, 'window_size' => false, 'capabilities' => [], 'connection_timeout' => null, 'request_timeout' => null, 'pageload_timeout' => null, 'http_proxy' => null, 'http_proxy_port' => null, 'ssl_proxy' => null, 'ssl_proxy_port' => null, 'debug_log_entries' => 0, 'log_js_errors' => false, 'webdriver_proxy' => null, 'webdriver_proxy_port' => null, ]; protected $wdHost; protected $capabilities; protected $connectionTimeoutInMs; protected $requestTimeoutInMs; protected $test; protected $sessions = []; protected $sessionSnapshots = []; protected $webdriverProxy; protected $webdriverProxyPort; /** * @var RemoteWebDriver */ public $webDriver; /** * @var RemoteWebElement */ protected $baseElement; public function _requires() { return ['Facebook\WebDriver\Remote\RemoteWebDriver' => '"php-webdriver/webdriver": "^1.0.1"']; } /** * @return RemoteWebElement * @throws ModuleException */ protected function getBaseElement() { if (!$this->baseElement) { throw new ModuleException($this, "Page not loaded. Use `\$I->amOnPage` (or hidden API methods `_request` and `_loadPage`) to open it"); } return $this->baseElement; } public function _initialize() { $this->wdHost = sprintf('%s://%s:%s%s', $this->config['protocol'], $this->config['host'], $this->config['port'], $this->config['path']); $this->capabilities = $this->config['capabilities']; $this->capabilities[WebDriverCapabilityType::BROWSER_NAME] = $this->config['browser']; if ($proxy = $this->getProxy()) { $this->capabilities[WebDriverCapabilityType::PROXY] = $proxy; } $this->connectionTimeoutInMs = $this->config['connection_timeout'] * 1000; $this->requestTimeoutInMs = $this->config['request_timeout'] * 1000; $this->webdriverProxy = $this->config['webdriver_proxy']; $this->webdriverProxyPort = $this->config['webdriver_proxy_port']; $this->loadFirefoxProfile(); } /** * Change capabilities of WebDriver. Should be executed before starting a new browser session. * This method expects a function to be passed which returns array or [WebDriver Desired Capabilities](https://github.com/php-webdriver/php-webdriver/blob/main/lib/Remote/DesiredCapabilities.php) object. * Additional [Chrome options](https://github.com/php-webdriver/php-webdriver/wiki/ChromeOptions) (like adding extensions) can be passed as well. * * ```php * getModule('WebDriver')->_capabilities(function($currentCapabilities) { * // or new \Facebook\WebDriver\Remote\DesiredCapabilities(); * return \Facebook\WebDriver\Remote\DesiredCapabilities::firefox(); * }); * } * ``` * * to make this work load `\Helper\Acceptance` before `WebDriver` in `acceptance.suite.yml`: * * ```yaml * modules: * enabled: * - \Helper\Acceptance * - WebDriver * ``` * * For instance, [**BrowserStack** cloud service](https://www.browserstack.com/automate/capabilities) may require a test name to be set in capabilities. * This is how it can be done via `_capabilities` method from `Helper\Acceptance`: * * ```php * getMetadata()->getName(); * $this->getModule('WebDriver')->_capabilities(function($currentCapabilities) use ($name) { * $currentCapabilities['name'] = $name; * return $currentCapabilities; * }); * } * ``` * In this case, please ensure that `\Helper\Acceptance` is loaded before WebDriver so new capabilities could be applied. * * @api * @param \Closure $capabilityFunction */ public function _capabilities(\Closure $capabilityFunction) { $this->capabilities = $capabilityFunction($this->capabilities); } public function _conflicts() { return 'Codeception\Lib\Interfaces\Web'; } public function _before(TestInterface $test) { if (!isset($this->webDriver) && $this->config['start']) { $this->_initializeSession(); } $this->setBaseElement(); $test->getMetadata()->setCurrent( [ 'browser' => $this->webDriver->getCapabilities()->getBrowserName(), 'capabilities' => $this->webDriver->getCapabilities()->toArray(), ] ); } /** * Restarts a web browser. * Can be used with `_reconfigure` to open browser with different configuration * * ```php * getModule('WebDriver')->_restart(); // just restart * $this->getModule('WebDriver')->_restart(['browser' => $browser]); // reconfigure + restart * ``` * * @param array $config * @api */ public function _restart($config = []) { $this->webDriver->quit(); if (!empty($config)) { $this->_reconfigure($config); } $this->_initializeSession(); } protected function onReconfigure() { $this->_initialize(); } protected function loadFirefoxProfile() { if (!array_key_exists('firefox_profile', $this->config['capabilities'])) { return; } $firefox_profile = $this->config['capabilities']['firefox_profile']; if (file_exists($firefox_profile) === false) { throw new ModuleConfigException( __CLASS__, "Firefox profile does not exist under given path " . $firefox_profile ); } // Set firefox profile as capability $this->capabilities['firefox_profile'] = file_get_contents($firefox_profile); } protected function initialWindowSize() { if ($this->config['window_size'] == 'maximize') { $this->maximizeWindow(); return; } $size = explode('x', $this->config['window_size']); if (count($size) == 2) { $this->resizeWindow(intval($size[0]), intval($size[1])); } } public function _after(TestInterface $test) { if ($this->config['restart']) { $this->stopAllSessions(); return; } if ($this->config['clear_cookies'] && isset($this->webDriver)) { try { $this->webDriver->manage()->deleteAllCookies(); } catch (\Exception $e) { // may cause fatal errors when not handled $this->debug("Error, can't clean cookies after a test: " . $e->getMessage()); } } } public function _failed(TestInterface $test, $fail) { $this->debugWebDriverLogs($test); $filename = preg_replace('~[^a-zA-Z0-9\x80-\xff]~', '.', Descriptor::getTestSignatureUnique($test)); $outputDir = codecept_output_dir(); $this->_saveScreenshot($report = $outputDir . mb_strcut($filename, 0, 245, 'utf-8') . '.fail.png'); $test->getMetadata()->addReport('png', $report); $this->_savePageSource($report = $outputDir . mb_strcut($filename, 0, 244, 'utf-8') . '.fail.html'); $test->getMetadata()->addReport('html', $report); $this->debug("Screenshot and page source were saved into '$outputDir' dir"); } /** * Print out latest Selenium Logs in debug mode * * @param \Codeception\TestInterface $test */ public function debugWebDriverLogs(TestInterface $test = null) { if (!isset($this->webDriver)) { $this->debug('WebDriver::debugWebDriverLogs method has been called when webDriver is not set'); return; } // don't show logs if log entries not set if (!$this->config['debug_log_entries']) { return; } try { // Dump out latest Selenium logs $logs = $this->webDriver->manage()->getAvailableLogTypes(); foreach ($logs as $logType) { $logEntries = array_slice( $this->webDriver->manage()->getLog($logType), -$this->config['debug_log_entries'] ); if (empty($logEntries)) { $this->debugSection("Selenium {$logType} Logs", " EMPTY "); continue; } $this->debugSection("Selenium {$logType} Logs", "\n" . $this->formatLogEntries($logEntries)); if ($logType === 'browser' && $this->config['log_js_errors'] && ($test instanceof ScenarioDriven) ) { $this->logJSErrors($test, $logEntries); } } } catch (\Exception $e) { $this->debug('Unable to retrieve Selenium logs : ' . $e->getMessage()); } } /** * Turns an array of log entries into a human-readable string. * Each log entry is an array with the keys "timestamp", "level", and "message". * See https://code.google.com/p/selenium/wiki/JsonWireProtocol#Log_Entry_JSON_Object * * @param array $logEntries * @return string */ protected function formatLogEntries(array $logEntries) { $formattedLogs = ''; foreach ($logEntries as $logEntry) { // Timestamp is in milliseconds, but date() requires seconds. $time = date('H:i:s', $logEntry['timestamp'] / 1000) . // Append the milliseconds to the end of the time string '.' . ($logEntry['timestamp'] % 1000); $formattedLogs .= "{$time} {$logEntry['level']} - {$logEntry['message']}\n"; } return $formattedLogs; } /** * Logs JavaScript errors as comments. * * @param ScenarioDriven $test * @param array $browserLogEntries */ protected function logJSErrors(ScenarioDriven $test, array $browserLogEntries) { foreach ($browserLogEntries as $logEntry) { if (true === isset($logEntry['level']) && true === isset($logEntry['message']) && $this->isJSError($logEntry['level'], $logEntry['message']) ) { // Timestamp is in milliseconds, but date() requires seconds. $time = date('H:i:s', $logEntry['timestamp'] / 1000) . // Append the milliseconds to the end of the time string '.' . ($logEntry['timestamp'] % 1000); $test->getScenario()->comment("{$time} {$logEntry['level']} - {$logEntry['message']}"); } } } /** * Determines if the log entry is an error. * The decision is made depending on browser and log-level. * * @param string $logEntryLevel * @param string $message * @return bool */ protected function isJSError($logEntryLevel, $message) { return ( ($this->isPhantom() && $logEntryLevel != 'INFO') // phantomjs logs errors as "WARNING" || $logEntryLevel === 'SEVERE' // other browsers log errors as "SEVERE" ) && strpos($message, 'ERR_PROXY_CONNECTION_FAILED') === false; // ignore blackhole proxy } public function _afterSuite() { // this is just to make sure webDriver is cleared after suite $this->stopAllSessions(); } protected function stopAllSessions() { foreach ($this->sessions as $session) { $this->_closeSession($session); } $this->webDriver = null; $this->baseElement = null; } public function amOnSubdomain($subdomain) { $url = $this->config['url']; $url = preg_replace('~(https?:\/\/)(.*\.)(.*\.)~', "$1$3", $url); // removing current subdomain $url = preg_replace('~(https?:\/\/)(.*)~', "$1$subdomain.$2", $url); // inserting new $this->_reconfigure(['url' => $url]); } /** * Returns URL of a host. * * @api * @return mixed * @throws ModuleConfigException */ public function _getUrl() { if (!isset($this->config['url'])) { throw new ModuleConfigException( __CLASS__, "Module connection failure. The URL for client can't bre retrieved" ); } return $this->config['url']; } protected function getProxy() { $proxyConfig = []; if ($this->config['http_proxy']) { $proxyConfig['httpProxy'] = $this->config['http_proxy']; if ($this->config['http_proxy_port']) { $proxyConfig['httpProxy'] .= ':' . $this->config['http_proxy_port']; } } if ($this->config['ssl_proxy']) { $proxyConfig['sslProxy'] = $this->config['ssl_proxy']; if ($this->config['ssl_proxy_port']) { $proxyConfig['sslProxy'] .= ':' . $this->config['ssl_proxy_port']; } } if (!empty($proxyConfig)) { $proxyConfig['proxyType'] = 'manual'; return $proxyConfig; } return null; } /** * Uri of currently opened page. * @return string * @api * @throws ModuleException */ public function _getCurrentUri() { $url = $this->webDriver->getCurrentURL(); if ($url == 'about:blank' || strpos($url, 'data:') === 0) { throw new ModuleException($this, 'Current url is blank, no page was opened'); } return Uri::retrieveUri($url); } public function _saveScreenshot($filename) { if (!isset($this->webDriver)) { $this->debug('WebDriver::_saveScreenshot method has been called when webDriver is not set'); return; } try { $this->webDriver->takeScreenshot($filename); } catch (\Exception $e) { $this->debug('Unable to retrieve screenshot from Selenium : ' . $e->getMessage()); return; } } public function _saveElementScreenshot($selector, $filename) { if (!isset($this->webDriver)) { $this->debug('WebDriver::_saveElementScreenshot method has been called when webDriver is not set'); return; } try { $this->matchFirstOrFail($this->webDriver, $selector)->takeElementScreenshot($filename); } catch (\Exception $e) { $this->debug('Unable to retrieve element screenshot from Selenium : ' . $e->getMessage()); return; } } public function _findElements($locator) { return $this->match($this->webDriver, $locator); } /** * Saves HTML source of a page to a file * @param $filename */ public function _savePageSource($filename) { if (!isset($this->webDriver)) { $this->debug('WebDriver::_savePageSource method has been called when webDriver is not set'); return; } try { file_put_contents($filename, $this->webDriver->getPageSource()); } catch (\Exception $e) { $this->debug('Unable to retrieve source page from Selenium : ' . $e->getMessage()); } } /** * Takes a screenshot of the current window and saves it to `tests/_output/debug`. * * ``` php * amOnPage('/user/edit'); * $I->makeScreenshot('edit_page'); * // saved to: tests/_output/debug/edit_page.png * $I->makeScreenshot(); * // saved to: tests/_output/debug/2017-05-26_14-24-11_4b3403665fea6.png * ``` * * @param $name */ public function makeScreenshot($name = null) { if (empty($name)) { $name = uniqid(date("Y-m-d_H-i-s_")); } $debugDir = codecept_log_dir() . 'debug'; if (!is_dir($debugDir)) { mkdir($debugDir, 0777); } $screenName = $debugDir . DIRECTORY_SEPARATOR . $name . '.png'; $this->_saveScreenshot($screenName); $this->debugSection('Screenshot Saved', "file://$screenName"); } /** * Takes a screenshot of an element of the current window and saves it to `tests/_output/debug`. * * ``` php * amOnPage('/user/edit'); * $I->makeElementScreenshot('#dialog', 'edit_page'); * // saved to: tests/_output/debug/edit_page.png * $I->makeElementScreenshot('#dialog'); * // saved to: tests/_output/debug/2017-05-26_14-24-11_4b3403665fea6.png * ``` * * @param $name */ public function makeElementScreenshot($selector, $name = null) { if (empty($name)) { $name = uniqid(date("Y-m-d_H-i-s_")); } $debugDir = codecept_log_dir() . 'debug'; if (!is_dir($debugDir)) { mkdir($debugDir, 0777); } $screenName = $debugDir . DIRECTORY_SEPARATOR . $name . '.png'; $this->_saveElementScreenshot($selector, $screenName); $this->debugSection('Screenshot Saved', "file://$screenName"); } public function makeHtmlSnapshot($name = null) { if (empty($name)) { $name = uniqid(date("Y-m-d_H-i-s_")); } $debugDir = codecept_output_dir() . 'debug'; if (!is_dir($debugDir)) { mkdir($debugDir, 0777); } $fileName = $debugDir . DIRECTORY_SEPARATOR . $name . '.html'; $this->_savePageSource($fileName); $this->debugSection('Snapshot Saved', "file://$fileName"); } /** * Resize the current window. * * ``` php * resizeWindow(800, 600); * * ``` * * @param int $width * @param int $height */ public function resizeWindow($width, $height) { $this->webDriver->manage()->window()->setSize(new WebDriverDimension($width, $height)); } private function debugCookies() { $result = []; $cookies = $this->webDriver->manage()->getCookies(); foreach ($cookies as $cookie) { $result[] = is_array($cookie) ? $cookie : $cookie->toArray(); } $this->debugSection('Cookies', json_encode($result)); } public function seeCookie($cookie, array $params = []) { $cookies = $this->filterCookies($this->webDriver->manage()->getCookies(), $params); $cookies = array_map( function ($c) { return $c['name']; }, $cookies ); $this->debugCookies(); $this->assertContains($cookie, $cookies); } public function dontSeeCookie($cookie, array $params = []) { $cookies = $this->filterCookies($this->webDriver->manage()->getCookies(), $params); $cookies = array_map( function ($c) { return $c['name']; }, $cookies ); $this->debugCookies(); $this->assertNotContains($cookie, $cookies); } public function setCookie($cookie, $value, array $params = [], $showDebug = true) { $params['name'] = $cookie; $params['value'] = $value; if (isset($params['expires'])) { // PhpBrowser compatibility $params['expiry'] = $params['expires']; } // #5401 Supply defaults, otherwise chromedriver 2.46 complains. $defaults = [ 'path' => '/', 'expiry' => time() + 86400, 'secure' => false, 'httpOnly' => false, ]; foreach ($defaults as $key => $default) { if (empty($params[$key])) { $params[$key] = $default; } } $this->webDriver->manage()->addCookie($params); if ($showDebug) { $this->debugCookies(); } } public function resetCookie($cookie, array $params = []) { $this->webDriver->manage()->deleteCookieNamed($cookie); $this->debugCookies(); } public function grabCookie($cookie, array $params = []) { $params['name'] = $cookie; $cookies = $this->filterCookies($this->webDriver->manage()->getCookies(), $params); if (empty($cookies)) { return null; } $cookie = reset($cookies); return $cookie['value']; } /** * Grabs current page source code. * * @throws ModuleException if no page was opened. * * @return string Current page source code. */ public function grabPageSource() { // Make sure that some page was opened. $this->_getCurrentUri(); return $this->webDriver->getPageSource(); } protected function filterCookies($cookies, $params = []) { foreach (['domain', 'path', 'name'] as $filter) { if (!isset($params[$filter])) { continue; } $cookies = array_filter( $cookies, function ($item) use ($filter, $params) { return $item[$filter] == $params[$filter]; } ); } return $cookies; } public function amOnUrl($url) { $host = Uri::retrieveHost($url); $this->_reconfigure(['url' => $host]); $this->debugSection('Host', $host); $this->webDriver->get($url); } public function amOnPage($page) { $url = Uri::appendPath($this->config['url'], $page); $this->debugSection('GET', $url); $this->webDriver->get($url); } public function see($text, $selector = null) { if (!$selector) { return $this->assertPageContains($text); } $this->enableImplicitWait(); $nodes = $this->matchVisible($selector); $this->disableImplicitWait(); $this->assertNodesContain($text, $nodes, $selector); } public function dontSee($text, $selector = null) { if (!$selector) { return $this->assertPageNotContains($text); } $nodes = $this->matchVisible($selector); $this->assertNodesNotContain($text, $nodes, $selector); } public function seeInSource($raw) { $this->assertPageSourceContains($raw); } public function dontSeeInSource($raw) { $this->assertPageSourceNotContains($raw); } /** * Checks that the page source contains the given string. * * ```php * seeInPageSource('assertThat( $this->webDriver->getPageSource(), new PageConstraint($text, $this->_getCurrentUri()), '' ); } /** * Checks that the page source doesn't contain the given string. * * @param $text */ public function dontSeeInPageSource($text) { $this->assertThatItsNot( $this->webDriver->getPageSource(), new PageConstraint($text, $this->_getCurrentUri()), '' ); } public function click($link, $context = null) { $page = $this->webDriver; if ($context) { $page = $this->matchFirstOrFail($this->webDriver, $context); } $el = $this->_findClickable($page, $link); if (!$el) { // check one more time if this was a CSS selector we didn't match try { $els = $this->match($page, $link); } catch (MalformedLocatorException $e) { throw new ElementNotFound("name=$link", "'$link' is invalid CSS and XPath selector and Link or Button"); } $el = reset($els); } if (!$el) { throw new ElementNotFound($link, 'Link or Button or CSS or XPath'); } $el->click(); } /** * Locates a clickable element. * * Use it in Helpers or GroupObject or Extension classes: * * ```php * getModule('WebDriver'); * $page = $module->webDriver; * * // search a link or button on a page * $el = $module->_findClickable($page, 'Click Me'); * * // search a link or button within an element * $topBar = $module->_findElements('.top-bar')[0]; * $el = $module->_findClickable($topBar, 'Click Me'); * * ``` * @api * @param RemoteWebDriver $page WebDriver instance or an element to search within * @param $link a link text or locator to click * @return WebDriverElement */ public function _findClickable($page, $link) { if (is_array($link) or ($link instanceof WebDriverBy)) { return $this->matchFirstOrFail($page, $link); } // try to match by strict locators, CSS Ids or XPath if (Locator::isPrecise($link)) { return $this->matchFirstOrFail($page, $link); } $locator = static::xpathLiteral(trim($link)); // narrow $xpath = Locator::combine( ".//a[normalize-space(.)=$locator]", ".//button[normalize-space(.)=$locator]", ".//a/img[normalize-space(@alt)=$locator]/ancestor::a", ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][normalize-space(@value)=$locator]" ); $els = $page->findElements(WebDriverBy::xpath($xpath)); if (count($els)) { return reset($els); } // wide $xpath = Locator::combine( ".//a[./@href][((contains(normalize-space(string(.)), $locator)) or contains(./@title, $locator) or .//img[contains(./@alt, $locator)])]", ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][contains(./@value, $locator)]", ".//input[./@type = 'image'][contains(./@alt, $locator)]", ".//button[contains(normalize-space(string(.)), $locator)]", ".//input[./@type = 'submit' or ./@type = 'image' or ./@type = 'button'][./@name = $locator or ./@title = $locator]", ".//button[./@name = $locator or ./@title = $locator]" ); $els = $page->findElements(WebDriverBy::xpath($xpath)); if (count($els)) { return reset($els); } return null; } /** * @param $selector * @return WebDriverElement[] * @throws \Codeception\Exception\ElementNotFound */ protected function findFields($selector) { if ($selector instanceof WebDriverElement) { return [$selector]; } if (is_array($selector) || ($selector instanceof WebDriverBy)) { $fields = $this->match($this->webDriver, $selector); if (empty($fields)) { throw new ElementNotFound($selector); } return $fields; } $locator = static::xpathLiteral(trim($selector)); // by text or label $xpath = Locator::combine( // @codingStandardsIgnoreStart ".//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')][(((./@name = $locator) or ./@id = //label[contains(normalize-space(string(.)), $locator)]/@for) or ./@placeholder = $locator)]", ".//label[contains(normalize-space(string(.)), $locator)]//.//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]" // @codingStandardsIgnoreEnd ); $fields = $this->webDriver->findElements(WebDriverBy::xpath($xpath)); if (!empty($fields)) { return $fields; } // by name $xpath = ".//*[self::input | self::textarea | self::select][@name = $locator]"; $fields = $this->webDriver->findElements(WebDriverBy::xpath($xpath)); if (!empty($fields)) { return $fields; } // try to match by CSS or XPath $fields = $this->match($this->webDriver, $selector, false); if (!empty($fields)) { return $fields; } throw new ElementNotFound($selector, "Field by name, label, CSS or XPath"); } /** * @param $selector * @return WebDriverElement * @throws \Codeception\Exception\ElementNotFound */ protected function findField($selector) { $arr = $this->findFields($selector); return reset($arr); } public function seeLink($text, $url = null) { $this->enableImplicitWait(); $nodes = $this->getBaseElement()->findElements(WebDriverBy::partialLinkText($text)); $this->disableImplicitWait(); $currentUri = $this->_getCurrentUri(); if (empty($nodes)) { $this->fail("No links containing text '$text' were found in page $currentUri"); } if ($url) { $nodes = $this->filterNodesByHref($url, $nodes); } $this->assertNotEmpty($nodes, "No links containing text '$text' and URL '$url' were found in page $currentUri"); } public function dontSeeLink($text, $url = null) { $nodes = $this->getBaseElement()->findElements(WebDriverBy::partialLinkText($text)); $currentUri = $this->_getCurrentUri(); if (!$url) { $this->assertEmpty($nodes, "Link containing text '$text' was found in page $currentUri"); } else { $nodes = $this->filterNodesByHref($url, $nodes); $this->assertEmpty($nodes, "Link containing text '$text' and URL '$url' was found in page $currentUri"); } } /** * @param string $url * @param $nodes * @return array */ private function filterNodesByHref($url, $nodes) { //current uri can be relative, merging it with configured base url gives absolute url $absoluteCurrentUrl = Uri::mergeUrls($this->_getUrl(), $this->_getCurrentUri()); $expectedUrl = Uri::mergeUrls($absoluteCurrentUrl, $url); $nodes = array_filter( $nodes, function (WebDriverElement $e) use ($expectedUrl, $absoluteCurrentUrl) { $elementHref = Uri::mergeUrls($absoluteCurrentUrl, $e->getAttribute('href')); return $elementHref === $expectedUrl; } ); return $nodes; } public function seeInCurrentUrl($uri) { $this->assertStringContainsString($uri, $this->_getCurrentUri()); } public function seeCurrentUrlEquals($uri) { $this->assertEquals($uri, $this->_getCurrentUri()); } public function seeCurrentUrlMatches($uri) { $this->assertRegExp($uri, $this->_getCurrentUri()); } public function dontSeeInCurrentUrl($uri) { $this->assertStringNotContainsString($uri, $this->_getCurrentUri()); } public function dontSeeCurrentUrlEquals($uri) { $this->assertNotEquals($uri, $this->_getCurrentUri()); } public function dontSeeCurrentUrlMatches($uri) { $this->assertNotRegExp($uri, $this->_getCurrentUri()); } public function grabFromCurrentUrl($uri = null) { if (!$uri) { return $this->_getCurrentUri(); } $matches = []; $res = preg_match($uri, $this->_getCurrentUri(), $matches); if (!$res) { $this->fail("Couldn't match $uri in " . $this->_getCurrentUri()); } if (!isset($matches[1])) { $this->fail("Nothing to grab. A regex parameter required. Ex: '/user/(\\d+)'"); } return $matches[1]; } public function seeCheckboxIsChecked($checkbox) { $this->assertTrue($this->findField($checkbox)->isSelected()); } public function dontSeeCheckboxIsChecked($checkbox) { $this->assertFalse($this->findField($checkbox)->isSelected()); } public function seeInField($field, $value) { $els = $this->findFields($field); $this->assert($this->proceedSeeInField($els, $value)); } public function dontSeeInField($field, $value) { $els = $this->findFields($field); $this->assertNot($this->proceedSeeInField($els, $value)); } public function seeInFormFields($formSelector, array $params) { $this->proceedSeeInFormFields($formSelector, $params, false); } public function dontSeeInFormFields($formSelector, array $params) { $this->proceedSeeInFormFields($formSelector, $params, true); } protected function proceedSeeInFormFields($formSelector, array $params, $assertNot) { $form = $this->match($this->getBaseElement(), $formSelector); if (empty($form)) { throw new ElementNotFound($formSelector, "Form via CSS or XPath"); } $form = reset($form); $els = []; foreach ($params as $name => $values) { $this->pushFormField($els, $form, $name, $values); } foreach ($els as $arrayElement) { list($el, $values) = $arrayElement; if (!is_array($values)) { $values = [$values]; } foreach ($values as $value) { $ret = $this->proceedSeeInField($el, $value); if ($assertNot) { $this->assertNot($ret); } else { $this->assert($ret); } } } } /** * Map an array element passed to seeInFormFields to its corresponding WebDriver element, * recursing through array values if the field is not found. * * @param array $els The previously found elements. * @param RemoteWebElement $form The form in which to search for fields. * @param string $name The field's name. * @param mixed $values * @return void */ protected function pushFormField(&$els, $form, $name, $values) { $el = $form->findElements(WebDriverBy::name($name)); if ($el) { $els[] = [$el, $values]; } elseif (is_array($values)) { foreach ($values as $key => $value) { $this->pushFormField($els, $form, "{$name}[$key]", $value); } } else { throw new ElementNotFound($name); } } /** * @param RemoteWebElement[] $elements * @param $value * @return array */ protected function proceedSeeInField(array $elements, $value) { $strField = reset($elements)->getAttribute('name'); if (reset($elements)->getTagName() === 'select') { $el = reset($elements); $elements = $el->findElements(WebDriverBy::xpath('.//option')); if (empty($value) && empty($elements)) { return ['True', true]; } } $currentValues = []; if (is_bool($value)) { $currentValues = [false]; } foreach ($elements as $el) { switch ($el->getTagName()) { case 'input': if ($el->getAttribute('type') === 'radio' || $el->getAttribute('type') === 'checkbox') { if ($el->getAttribute('checked')) { if (is_bool($value)) { $currentValues = [true]; break; } else { $currentValues[] = $el->getAttribute('value'); } } } else { $currentValues[] = $el->getAttribute('value'); } break; case 'option': // no break we need the trim text and the value also if (!$el->isSelected()) { break; } $currentValues[] = $el->getText(); case 'textarea': // we include trimmed and real value of textarea for check $currentValues[] = trim($el->getText()); default: $currentValues[] = $el->getAttribute('value'); // raw value break; } } return [ 'Contains', $value, $currentValues, "Failed testing for '$value' in $strField's value: '" . implode("', '", $currentValues) . "'" ]; } public function selectOption($select, $option) { $el = $this->findField($select); if ($el->getTagName() != 'select') { $els = $this->matchCheckables($select); $radio = null; foreach ($els as $el) { $radio = $this->findCheckable($el, $option, true); if ($radio) { break; } } if (!$radio) { throw new ElementNotFound($select, "Radiobutton with value or name '$option in"); } $radio->click(); return; } $wdSelect = new WebDriverSelect($el); if ($wdSelect->isMultiple()) { $wdSelect->deselectAll(); } if (!is_array($option)) { $option = [$option]; } $matched = false; if (key($option) !== 'value') { foreach ($option as $opt) { try { $wdSelect->selectByVisibleText($opt); $matched = true; } catch (NoSuchElementException $e) { } } } if ($matched) { return; } if (key($option) !== 'text') { foreach ($option as $opt) { try { $wdSelect->selectByValue($opt); $matched = true; } catch (NoSuchElementException $e) { } } } if ($matched) { return; } // partially matching foreach ($option as $opt) { try { $optElement = $el->findElement(WebDriverBy::xpath('.//option [contains (., "' . $opt . '")]')); $matched = true; if (!$optElement->isSelected()) { $optElement->click(); } } catch (NoSuchElementException $e) { // exception treated at the end } } if ($matched) { return; } throw new ElementNotFound(json_encode($option), "Option inside $select matched by name or value"); } /** * Manually starts a new browser session. * * ```php * getModule('WebDriver')->_initializeSession(); * ``` * * @api */ public function _initializeSession() { try { $this->sessions[] = $this->webDriver; $this->webDriver = RemoteWebDriver::create( $this->wdHost, $this->capabilities, $this->connectionTimeoutInMs, $this->requestTimeoutInMs, $this->webdriverProxy, $this->webdriverProxyPort ); if (!is_null($this->config['pageload_timeout'])) { $this->webDriver->manage()->timeouts()->pageLoadTimeout($this->config['pageload_timeout']); } $this->setBaseElement(); $this->initialWindowSize(); } catch (WebDriverCurlException $e) { codecept_debug('Curl error: ' . $e->getMessage()); throw new ConnectionException("Can't connect to WebDriver at {$this->wdHost}. Make sure that ChromeDriver, GeckoDriver or Selenium Server is running."); } } /** * Loads current RemoteWebDriver instance as a session * * @api * @param RemoteWebDriver $session */ public function _loadSession($session) { $this->webDriver = $session; $this->setBaseElement(); } /** * Returns current WebDriver session for saving * * @api * @return RemoteWebDriver */ public function _backupSession() { return $this->webDriver; } /** * Manually closes current WebDriver session. * * ```php * getModule('WebDriver')->_closeSession(); * * // close a specific session * $webDriver = $this->getModule('WebDriver')->webDriver; * $this->getModule('WebDriver')->_closeSession($webDriver); * ``` * * @api * @param $webDriver (optional) a specific webdriver session instance */ public function _closeSession($webDriver = null) { if (!$webDriver and $this->webDriver) { $webDriver = $this->webDriver; } if (!$webDriver) { return; } try { $webDriver->quit(); unset($webDriver); } catch (UnknownServerException $e) { // Session already closed so nothing to do } catch (UnknownErrorException $e) { // Session already closed so nothing to do } } /** * Unselect an option in the given select box. * * @param $select * @param $option */ public function unselectOption($select, $option) { $el = $this->findField($select); $wdSelect = new WebDriverSelect($el); if (!is_array($option)) { $option = [$option]; } $matched = false; foreach ($option as $opt) { try { $wdSelect->deselectByVisibleText($opt); $matched = true; } catch (NoSuchElementException $e) { // exception treated at the end } try { $wdSelect->deselectByValue($opt); $matched = true; } catch (NoSuchElementException $e) { // exception treated at the end } } if ($matched) { return; } throw new ElementNotFound(json_encode($option), "Option inside $select matched by name or value"); } /** * @param $context * @param $radioOrCheckbox * @param bool $byValue * @return mixed|null */ protected function findCheckable($context, $radioOrCheckbox, $byValue = false) { if ($radioOrCheckbox instanceof WebDriverElement) { return $radioOrCheckbox; } if (is_array($radioOrCheckbox) or ($radioOrCheckbox instanceof WebDriverBy)) { return $this->matchFirstOrFail($this->getBaseElement(), $radioOrCheckbox); } $locator = static::xpathLiteral($radioOrCheckbox); if ($context instanceof WebDriverElement && $context->getTagName() === 'input') { $contextType = $context->getAttribute('type'); if (!in_array($contextType, ['checkbox', 'radio'], true)) { return null; } $nameLiteral = static::xpathLiteral($context->getAttribute('name')); $typeLiteral = static::xpathLiteral($contextType); $inputLocatorFragment = "input[@type = $typeLiteral][@name = $nameLiteral]"; $xpath = Locator::combine( // @codingStandardsIgnoreStart "ancestor::form//{$inputLocatorFragment}[(@id = ancestor::form//label[contains(normalize-space(string(.)), $locator)]/@for) or @placeholder = $locator]", // @codingStandardsIgnoreEnd "ancestor::form//label[contains(normalize-space(string(.)), $locator)]//{$inputLocatorFragment}" ); if ($byValue) { $xpath = Locator::combine($xpath, "ancestor::form//{$inputLocatorFragment}[@value = $locator]"); } } else { $xpath = Locator::combine( // @codingStandardsIgnoreStart "//input[@type = 'checkbox' or @type = 'radio'][(@id = //label[contains(normalize-space(string(.)), $locator)]/@for) or @placeholder = $locator or @name = $locator]", // @codingStandardsIgnoreEnd "//label[contains(normalize-space(string(.)), $locator)]//input[@type = 'radio' or @type = 'checkbox']" ); if ($byValue) { $xpath = Locator::combine($xpath, "//input[@type = 'checkbox' or @type = 'radio'][@value = $locator]"); } } $els = $context->findElements(WebDriverBy::xpath($xpath)); if (count($els)) { return reset($els); } $els = $context->findElements(WebDriverBy::xpath(str_replace('ancestor::form', '', $xpath))); if (count($els)) { return reset($els); } $els = $this->match($context, $radioOrCheckbox); if (count($els)) { return reset($els); } return null; } protected function matchCheckables($selector) { $els = $this->match($this->webDriver, $selector); if (!count($els)) { throw new ElementNotFound($selector, "Element containing radio by CSS or XPath"); } return $els; } public function checkOption($option) { $field = $this->findCheckable($this->webDriver, $option); if (!$field) { throw new ElementNotFound($option, "Checkbox or Radio by Label or CSS or XPath"); } if ($field->isSelected()) { return; } $field->click(); } public function uncheckOption($option) { $field = $this->findCheckable($this->getBaseElement(), $option); if (!$field) { throw new ElementNotFound($option, "Checkbox by Label or CSS or XPath"); } if (!$field->isSelected()) { return; } $field->click(); } public function fillField($field, $value) { $el = $this->findField($field); $el->clear(); $el->sendKeys((string)$value); } /** * Clears given field which isn't empty. * * ``` php * clearField('#username'); * ``` * * @param $field */ public function clearField($field) { $el = $this->findField($field); $el->clear(); } /** * Type in characters on active element. * With a second parameter you can specify delay between key presses. * * ```php * click('#input'); * * // type text in active element * $I->type('Hello world'); * * // type text with a 1sec delay between chars * $I->type('Hello World', 1); * ``` * * This might be useful when you an input reacts to typing and you need to slow it down to emulate human behavior. * For instance, this is how Credit Card fields can be filled in. * * @param $text * @param $delay [sec] */ public function type($text, $delay = 0) { $keys = str_split($text); foreach ($keys as $key) { sleep($delay); $this->webDriver->getKeyboard()->pressKey($key); } sleep($delay); } public function attachFile($field, $filename) { $el = $this->findField($field); // in order to be compatible on different OS $filePath = codecept_data_dir() . $filename; if (!file_exists($filePath)) { throw new \InvalidArgumentException("File does not exist: $filePath"); } if (!is_readable($filePath)) { throw new \InvalidArgumentException("File is not readable: $filePath"); } // in order for remote upload to be enabled $el->setFileDetector(new LocalFileDetector()); // skip file detector for phantomjs if ($this->isPhantom()) { $el->setFileDetector(new UselessFileDetector()); } $el->sendKeys(realpath($filePath)); } /** * Grabs all visible text from the current page. * * @return string */ protected function getVisibleText() { if ($this->getBaseElement() instanceof RemoteWebElement) { return $this->getBaseElement()->getText(); } $els = $this->getBaseElement()->findElements(WebDriverBy::cssSelector('body')); if (isset($els[0])) { return $els[0]->getText(); } return ''; } public function grabTextFrom($cssOrXPathOrRegex) { $els = $this->match($this->getBaseElement(), $cssOrXPathOrRegex, false); if (count($els)) { return $els[0]->getText(); } if (@preg_match($cssOrXPathOrRegex, $this->webDriver->getPageSource(), $matches)) { return $matches[1]; } throw new ElementNotFound($cssOrXPathOrRegex, 'CSS or XPath or Regex'); } public function grabAttributeFrom($cssOrXpath, $attribute) { $el = $this->matchFirstOrFail($this->getBaseElement(), $cssOrXpath); return $el->getAttribute($attribute); } public function grabValueFrom($field) { $el = $this->findField($field); // value of multiple select is the value of the first selected option if ($el->getTagName() == 'select') { $select = new WebDriverSelect($el); return $select->getFirstSelectedOption()->getAttribute('value'); } return $el->getAttribute('value'); } public function grabMultiple($cssOrXpath, $attribute = null) { $els = $this->match($this->getBaseElement(), $cssOrXpath); return array_map( function (WebDriverElement $e) use ($attribute) { if ($attribute) { return $e->getAttribute($attribute); } return $e->getText(); }, $els ); } protected function filterByAttributes($els, array $attributes) { foreach ($attributes as $attr => $value) { $els = array_filter( $els, function (WebDriverElement $el) use ($attr, $value) { return $el->getAttribute($attr) == $value; } ); } return $els; } public function seeElement($selector, $attributes = []) { $this->enableImplicitWait(); $els = $this->matchVisible($selector); $this->disableImplicitWait(); $els = $this->filterByAttributes($els, $attributes); $this->assertNotEmpty($els); } public function dontSeeElement($selector, $attributes = []) { $els = $this->matchVisible($selector); $els = $this->filterByAttributes($els, $attributes); $this->assertEmpty($els); } /** * Checks that the given element exists on the page, even it is invisible. * * ``` php * seeElementInDOM('//form/input[type=hidden]'); * ?> * ``` * * @param $selector * @param array $attributes */ public function seeElementInDOM($selector, $attributes = []) { $this->enableImplicitWait(); $els = $this->match($this->getBaseElement(), $selector); $els = $this->filterByAttributes($els, $attributes); $this->disableImplicitWait(); $this->assertNotEmpty($els); } /** * Opposite of `seeElementInDOM`. * * @param $selector * @param array $attributes */ public function dontSeeElementInDOM($selector, $attributes = []) { $els = $this->match($this->getBaseElement(), $selector); $els = $this->filterByAttributes($els, $attributes); $this->assertEmpty($els); } public function seeNumberOfElements($selector, $expected) { $counted = count($this->matchVisible($selector)); if (is_array($expected)) { list($floor, $ceil) = $expected; $this->assertTrue( $floor <= $counted && $ceil >= $counted, 'Number of elements counted differs from expected range' ); } else { $this->assertEquals( $expected, $counted, 'Number of elements counted differs from expected number' ); } } public function seeNumberOfElementsInDOM($selector, $expected) { $counted = count($this->match($this->getBaseElement(), $selector)); if (is_array($expected)) { list($floor, $ceil) = $expected; $this->assertTrue( $floor <= $counted && $ceil >= $counted, 'Number of elements counted differs from expected range' ); } else { $this->assertEquals( $expected, $counted, 'Number of elements counted differs from expected number' ); } } public function seeOptionIsSelected($selector, $optionText) { $el = $this->findField($selector); if ($el->getTagName() !== 'select') { $els = $this->matchCheckables($selector); foreach ($els as $k => $el) { $els[$k] = $this->findCheckable($el, $optionText, true); } $this->assertNotEmpty( array_filter( $els, function ($e) { return $e && $e->isSelected(); } ) ); return; } $select = new WebDriverSelect($el); $this->assertNodesContain($optionText, $select->getAllSelectedOptions(), 'option'); } public function dontSeeOptionIsSelected($selector, $optionText) { $el = $this->findField($selector); if ($el->getTagName() !== 'select') { $els = $this->matchCheckables($selector); foreach ($els as $k => $el) { $els[$k] = $this->findCheckable($el, $optionText, true); } $this->assertEmpty( array_filter( $els, function ($e) { return $e && $e->isSelected(); } ) ); return; } $select = new WebDriverSelect($el); $this->assertNodesNotContain($optionText, $select->getAllSelectedOptions(), 'option'); } public function seeInTitle($title) { $this->assertStringContainsString($title, $this->webDriver->getTitle()); } public function dontSeeInTitle($title) { $this->assertStringNotContainsString($title, $this->webDriver->getTitle()); } /** * Accepts the active JavaScript native popup window, as created by `window.alert`|`window.confirm`|`window.prompt`. * Don't confuse popups with modal windows, * as created by [various libraries](https://jster.net/category/windows-modals-popups). */ public function acceptPopup() { if ($this->isPhantom()) { throw new ModuleException($this, 'PhantomJS does not support working with popups'); } $this->webDriver->switchTo()->alert()->accept(); } /** * Dismisses the active JavaScript popup, as created by `window.alert`, `window.confirm`, or `window.prompt`. */ public function cancelPopup() { if ($this->isPhantom()) { throw new ModuleException($this, 'PhantomJS does not support working with popups'); } $this->webDriver->switchTo()->alert()->dismiss(); } /** * Checks that the active JavaScript popup, * as created by `window.alert`|`window.confirm`|`window.prompt`, contains the given string. * * @param $text * * @throws \Codeception\Exception\ModuleException */ public function seeInPopup($text) { if ($this->isPhantom()) { throw new ModuleException($this, 'PhantomJS does not support working with popups'); } $alert = $this->webDriver->switchTo()->alert(); try { $this->assertStringContainsString($text, $alert->getText()); } catch (\PHPUnit\Framework\AssertionFailedError $e) { $alert->dismiss(); throw $e; } } /** * Checks that the active JavaScript popup, * as created by `window.alert`|`window.confirm`|`window.prompt`, does NOT contain the given string. * * @param $text * * @throws \Codeception\Exception\ModuleException */ public function dontSeeInPopup($text) { if ($this->isPhantom()) { throw new ModuleException($this, 'PhantomJS does not support working with popups'); } $alert = $this->webDriver->switchTo()->alert(); try { $this->assertStringNotContainsString($text, $alert->getText()); } catch (\PHPUnit\Framework\AssertionFailedError $e) { $alert->dismiss(); throw $e; } } /** * Enters text into a native JavaScript prompt popup, as created by `window.prompt`. * * @param $keys * * @throws \Codeception\Exception\ModuleException */ public function typeInPopup($keys) { if ($this->isPhantom()) { throw new ModuleException($this, 'PhantomJS does not support working with popups'); } $this->webDriver->switchTo()->alert()->sendKeys($keys); } /** * Reloads the current page. */ public function reloadPage() { $this->webDriver->navigate()->refresh(); } /** * Moves back in history. */ public function moveBack() { $this->webDriver->navigate()->back(); $this->debug($this->_getCurrentUri()); } /** * Moves forward in history. */ public function moveForward() { $this->webDriver->navigate()->forward(); $this->debug($this->_getCurrentUri()); } protected function getSubmissionFormFieldName($name) { if (substr($name, -2) === '[]') { return substr($name, 0, -2); } return $name; } /** * Submits the given form on the page, optionally with the given form * values. Give the form fields values as an array. Note that hidden fields * can't be accessed. * * Skipped fields will be filled by their values from the page. * You don't need to click the 'Submit' button afterwards. * This command itself triggers the request to form's action. * * You can optionally specify what button's value to include * in the request with the last parameter as an alternative to * explicitly setting its value in the second parameter, as * button values are not otherwise included in the request. * * Examples: * * ``` php * submitForm('#login', [ * 'login' => 'davert', * 'password' => '123456' * ]); * // or * $I->submitForm('#login', [ * 'login' => 'davert', * 'password' => '123456' * ], 'submitButtonName'); * * ``` * * For example, given this sample "Sign Up" form: * * ``` html *
* Login: *
* Password: *
* Do you agree to our terms? *
* Select pricing plan: * * *
* ``` * * You could write the following to submit it: * * ``` php * submitForm( * '#userForm', * [ * 'user[login]' => 'Davert', * 'user[password]' => '123456', * 'user[agree]' => true * ], * 'submitButton' * ); * ``` * Note that "2" will be the submitted value for the "plan" field, as it is * the selected option. * * Also note that this differs from PhpBrowser, in that * ```'user' => [ 'login' => 'Davert' ]``` is not supported at the moment. * Named array keys *must* be included in the name as above. * * Pair this with seeInFormFields for quick testing magic. * * ``` php * 'value', * 'field2' => 'another value', * 'checkbox1' => true, * // ... * ]; * $I->submitForm('//form[@id=my-form]', $form, 'submitButton'); * // $I->amOnPage('/path/to/form-page') may be needed * $I->seeInFormFields('//form[@id=my-form]', $form); * ?> * ``` * * Parameter values must be set to arrays for multiple input fields * of the same name, or multi-select combo boxes. For checkboxes, * either the string value can be used, or boolean values which will * be replaced by the checkbox's value in the DOM. * * ``` php * submitForm('#my-form', [ * 'field1' => 'value', * 'checkbox' => [ * 'value of first checkbox', * 'value of second checkbox', * ], * 'otherCheckboxes' => [ * true, * false, * false, * ], * 'multiselect' => [ * 'first option value', * 'second option value', * ] * ]); * ?> * ``` * * Mixing string and boolean values for a checkbox's value is not supported * and may produce unexpected results. * * Field names ending in "[]" must be passed without the trailing square * bracket characters, and must contain an array for its value. This allows * submitting multiple values with the same name, consider: * * ```php * $I->submitForm('#my-form', [ * 'field[]' => 'value', * 'field[]' => 'another value', // 'field[]' is already a defined key * ]); * ``` * * The solution is to pass an array value: * * ```php * // this way both values are submitted * $I->submitForm('#my-form', [ * 'field' => [ * 'value', * 'another value', * ] * ]); * ``` * * The `$button` parameter can be either a string, an array or an instance * of Facebook\WebDriver\WebDriverBy. When it is a string, the * button will be found by its "name" attribute. If $button is an * array then it will be treated as a strict selector and a WebDriverBy * will be used verbatim. * * For example, given the following HTML: * * ``` html * * ``` * * `$button` could be any one of the following: * - 'submitButton' * - ['name' => 'submitButton'] * - WebDriverBy::name('submitButton') * * @param $selector * @param $params * @param $button */ public function submitForm($selector, array $params, $button = null) { $form = $this->matchFirstOrFail($this->getBaseElement(), $selector); $fields = $form->findElements( WebDriverBy::cssSelector('input:enabled,textarea:enabled,select:enabled,input[type=hidden]') ); foreach ($fields as $field) { $fieldName = $this->getSubmissionFormFieldName($field->getAttribute('name')); if (!isset($params[$fieldName])) { continue; } $value = $params[$fieldName]; if (is_array($value) && $field->getTagName() !== 'select') { if ($field->getAttribute('type') === 'checkbox' || $field->getAttribute('type') === 'radio') { $found = false; foreach ($value as $index => $val) { if (!is_bool($val) && $val === $field->getAttribute('value')) { array_splice($params[$fieldName], $index, 1); $value = $val; $found = true; break; } } if (!$found && !empty($value) && is_bool(reset($value))) { $value = array_pop($params[$fieldName]); } } else { $value = array_pop($params[$fieldName]); } } if ($field->getAttribute('type') === 'checkbox' || $field->getAttribute('type') === 'radio') { if ($value === true || $value === $field->getAttribute('value')) { $this->checkOption($field); } else { $this->uncheckOption($field); } } elseif ($field->getAttribute('type') === 'button' || $field->getAttribute('type') === 'submit') { continue; } elseif ($field->getTagName() === 'select') { $this->selectOption($field, $value); } else { $this->fillField($field, $value); } } $this->debugSection( 'Uri', $form->getAttribute('action') ? $form->getAttribute('action') : $this->_getCurrentUri() ); $this->debugSection('Method', $form->getAttribute('method') ? $form->getAttribute('method') : 'GET'); $this->debugSection('Parameters', json_encode($params)); $submitted = false; if (!empty($button)) { if (is_array($button)) { $buttonSelector = $this->getStrictLocator($button); } elseif ($button instanceof WebDriverBy) { $buttonSelector = $button; } else { $buttonSelector = WebDriverBy::name($button); } $els = $form->findElements($buttonSelector); if (!empty($els)) { $el = reset($els); $el->click(); $submitted = true; } } if (!$submitted) { $form->submit(); } $this->debugSection('Page', $this->_getCurrentUri()); } /** * Waits up to $timeout seconds for the given element to change. * Element "change" is determined by a callback function which is called repeatedly * until the return value evaluates to true. * * ``` php * waitForElementChange('#menu', function(WebDriverElement $el) { * return $el->isDisplayed(); * }, 100); * ?> * ``` * * @param $element * @param \Closure $callback * @param int $timeout seconds * @throws \Codeception\Exception\ElementNotFound */ public function waitForElementChange($element, \Closure $callback, $timeout = 30) { $el = $this->matchFirstOrFail($this->getBaseElement(), $element); $checker = function () use ($el, $callback) { return $callback($el); }; $this->webDriver->wait($timeout)->until($checker); } /** * Waits up to $timeout seconds for an element to appear on the page. * If the element doesn't appear, a timeout exception is thrown. * * ``` php * waitForElement('#agree_button', 30); // secs * $I->click('#agree_button'); * ?> * ``` * * @param $element * @param int $timeout seconds * @throws \Exception */ public function waitForElement($element, $timeout = 10) { $condition = WebDriverExpectedCondition::presenceOfElementLocated($this->getLocator($element)); $this->webDriver->wait($timeout)->until($condition); } /** * Waits up to $timeout seconds for the given element to be visible on the page. * If element doesn't appear, a timeout exception is thrown. * * ``` php * waitForElementVisible('#agree_button', 30); // secs * $I->click('#agree_button'); * ?> * ``` * * @param $element * @param int $timeout seconds * @throws \Exception */ public function waitForElementVisible($element, $timeout = 10) { $condition = WebDriverExpectedCondition::visibilityOfElementLocated($this->getLocator($element)); $this->webDriver->wait($timeout)->until($condition); } /** * Waits up to $timeout seconds for the given element to become invisible. * If element stays visible, a timeout exception is thrown. * * ``` php * waitForElementNotVisible('#agree_button', 30); // secs * ?> * ``` * * @param $element * @param int $timeout seconds * @throws \Exception */ public function waitForElementNotVisible($element, $timeout = 10) { $condition = WebDriverExpectedCondition::invisibilityOfElementLocated($this->getLocator($element)); $this->webDriver->wait($timeout)->until($condition); } /** * Waits up to $timeout seconds for the given element to be clickable. * If element doesn't become clickable, a timeout exception is thrown. * * ``` php * waitForElementClickable('#agree_button', 30); // secs * $I->click('#agree_button'); * ?> * ``` * * @param $element * @param int $timeout seconds * @throws \Exception */ public function waitForElementClickable($element, $timeout = 10) { $condition = WebDriverExpectedCondition::elementToBeClickable($this->getLocator($element)); $this->webDriver->wait($timeout)->until($condition); } /** * Waits up to $timeout seconds for the given string to appear on the page. * * Can also be passed a selector to search in, be as specific as possible when using selectors. * waitForText() will only watch the first instance of the matching selector / text provided. * If the given text doesn't appear, a timeout exception is thrown. * * ``` php * waitForText('foo', 30); // secs * $I->waitForText('foo', 30, '.title'); // secs * ?> * ``` * * @param string $text * @param int $timeout seconds * @param string $selector optional * @throws \Exception */ public function waitForText($text, $timeout = 10, $selector = null) { $message = sprintf( 'Waited for %d secs but text %s still not found', $timeout, Locator::humanReadableString($text) ); if (!$selector) { $condition = WebDriverExpectedCondition::textToBePresentInElement(WebDriverBy::xpath('//body'), $text); $this->webDriver->wait($timeout)->until($condition, $message); return; } $condition = WebDriverExpectedCondition::textToBePresentInElement($this->getLocator($selector), $text); $this->webDriver->wait($timeout)->until($condition, $message); } /** * Wait for $timeout seconds. * * @param int|float $timeout secs * @throws \Codeception\Exception\TestRuntimeException */ public function wait($timeout) { if ($timeout >= 1000) { throw new TestRuntimeException( " Waiting for more then 1000 seconds: 16.6667 mins\n Please note that wait method accepts number of seconds as parameter." ); } usleep($timeout * 1000000); } /** * Low-level API method. * If Codeception commands are not enough, this allows you to use Selenium WebDriver methods directly: * * ``` php * $I->executeInSelenium(function(\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) { * $webdriver->get('https://google.com'); * }); * ``` * * This runs in the context of the * [RemoteWebDriver class](https://github.com/php-webdriver/php-webdriver/blob/master/lib/remote/RemoteWebDriver.php). * Try not to use this command on a regular basis. * If Codeception lacks a feature you need, please implement it and submit a patch. * * @param callable $function */ public function executeInSelenium(\Closure $function) { return $function($this->webDriver); } /** * Switch to another window identified by name. * * The window can only be identified by name. If the $name parameter is blank, the parent window will be used. * * Example: * ``` html * * ``` * * ``` php * click("Open window"); * # switch to another window * $I->switchToWindow("another_window"); * # switch to parent window * $I->switchToWindow(); * ?> * ``` * * If the window has no name, match it by switching to next active tab using `switchToNextTab` method. * * Or use native Selenium functions to get access to all opened windows: * * ``` php * executeInSelenium(function (\Facebook\WebDriver\Remote\RemoteWebDriver $webdriver) { * $handles=$webdriver->getWindowHandles(); * $last_window = end($handles); * $webdriver->switchTo()->window($last_window); * }); * ?> * ``` * * @param string|null $name */ public function switchToWindow($name = null) { $this->webDriver->switchTo()->window($name); } /** * Switch to another iframe on the page. * * Example: * ``` html *