").text(datas[i].caption) : null
33 | ),
34 | $.roundDiv(datas[i].bytes, 1024).toFixed(3) + '',
35 | $.roundDiv(controller.getStack().getFunctions()[id].getMemory(), 1024).toFixed(3) + '',
36 | controller.getStack().getFunctions()[id].calls
37 | ])
38 | .attr('data-ref', datas[i].i)
39 | .bind(
40 | 'click',
41 | function() {
42 | forp.getController()
43 | .getConsole()
44 | .showInSidebar(
45 | (new forp.Backtrace(
46 | $(this).attr("data-ref"),
47 | forp.getController().getStack().stack
48 | )).$
49 | )
50 | .scrollBottom();
51 | }
52 | ).bind(
53 | 'mouseover',
54 | function(){
55 | controller
56 | .getMemoryHistogram()
57 | .highlight(
58 | controller
59 | .getStack()
60 | .stack[$(this).attr('data-ref')].leaf
61 | );
62 | }
63 | ).bind(
64 | 'mouseout',
65 | function(){
66 | controller.memoryHistogram.restore();
67 | }
68 | );
69 | }
70 |
71 | $table.appendTo(controller.getConsole().$);
72 | },
73 | 'close': function() {
74 | forp.getController().getLayout().reduce();
75 | }
76 | }
77 | };
78 |
79 | })(forp, jMicro);
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Introduction #
2 |
3 | forp-ui is a GUI utility that allows you to view and explore profiles dump.
4 |
5 | It is very easy to integrate forp-ui into an HTML page, it is written in JavaScript.
6 |
7 | # Basic features #
8 |
9 | - search engine
10 | - tree representation of the stack
11 | - top 20 duration
12 | - top 20 memory
13 | - top 20 calls
14 | - grouping of functions
15 | - metrics and quality grades
16 | - "called from" view
17 | - "backtrace" view
18 | - inspector
19 |
20 | # Integration into an HTML page and example #
21 |
22 | Download the minified version from the release branch of forp-ui on Github.
23 | Put it in the js directory of your project, then run forp-ui as in the example below.
24 |
25 |
26 | ```
27 |
28 |
61 | ```
62 |
63 | # Build #
64 |
65 | Use src/build.php to build src/built/forp.min.js file.
66 |
67 | ```
68 | $ git submodule init
69 | $ git submodule update
70 | $ cd src
71 | $ php build.php
72 | ```
73 |
74 | Options:
75 |
76 | --skin name : gstyle, consolas (default)
77 |
78 |
79 | # Communication with forp PHP profiler #
80 |
81 | forp-ui is the perfect tool to treat the forp PHP profiles dump (https://github.com/aterrien/forp).
82 | forp extension gives us PHP profiling datas, forp-ui helps you to refine it clientside.
83 |
84 | # Screenshots (example : Yii PHP framework) #
85 |
86 | ### tree representation of the stack ###
87 |
88 | 
89 |
90 | ### top 20 duration ###
91 |
92 | 
93 |
94 | Click on a stack entry displays backtrace in sidebar :
95 |
96 | 
97 |
98 | ### top 20 memory ###
99 |
100 | 
101 |
102 | ### top 20 Calls ###
103 |
104 | 
105 |
106 | ### grouping of functions ###
107 |
108 | This is the result of forp @ProfileGroup annotation.
109 |
110 | 
111 |
112 | Click on a group entry displays "called from" block :
113 |
114 | 
115 |
116 | ### search engine ###
117 |
118 | 
119 |
120 |
121 | ### metrics and quality grades
122 |
123 | # Samples #
124 |
125 | Samples are in the "samples" directory :
126 | - "free" : simple example free of programming language.
127 | - php : simple example of PHP profiling with forp PHP profiler.
128 |
--------------------------------------------------------------------------------
/src/build.php:
--------------------------------------------------------------------------------
1 | array(
15 | 's:', 'skin:'
16 | ),
17 | 'nomin' => array(
18 | 'n', 'nomin'
19 | )
20 | );
21 | $php_var = array(
22 | '@PHP-VAR-topCalls@' => 20,
23 | '@PHP-VAR-topMemory@' => 20,
24 | '@PHP-VAR-topCpu@' => 20,
25 | '@PHP-VAR-maxStack@' => 30000 /*the max number of elements in the forp stack that forp ui will allow before displaying an error.*/
26 | );
27 |
28 | $shortOpts = '';
29 | $longOpts = array();
30 | foreach($opts as $opt) {
31 | $shortOpts .= $opt[0];
32 | $longOpts[] = $opt[1];
33 | }
34 | $options = getopt($shortOpts, $longOpts);
35 | foreach($options as $k=>$v) {
36 | switch($k) {
37 | case 's' : case 'skin' :
38 | $skin = $v;
39 | break;
40 | case 'n' : case 'nomin' :
41 | $nomin = true;
42 | break;
43 | default :
44 | $skin = 'gstyle';
45 | $nomin = false;
46 | }
47 | }
48 |
49 | // Files
50 | $files = array(
51 | 'js' => array(
52 | 'js/submodules/jmicro/jmicro', //'dom',
53 | 'js/main',
54 | 'js/lib/view/utils',
55 |
56 | // Contoller
57 | 'js/lib/controller/controller',
58 |
59 | // Datas
60 | 'js/lib/model/stack',
61 |
62 | // Helpers
63 | 'js/lib/helpers/grader',
64 | 'js/lib/helpers/tagrandcolor',
65 |
66 | // UI
67 | 'js/lib/view/control/togglebutton',
68 | 'js/lib/view/indicator/graph',
69 | 'js/lib/view/indicator/gauge',
70 | 'js/lib/view/indicator/barchart',
71 | 'js/lib/view/indicator/histogram',
72 | 'js/lib/view/layout/layout',
73 | 'js/lib/view/layout/mainpanel',
74 | 'js/lib/view/layout/console',
75 | 'js/lib/view/layout/sidebar',
76 | 'js/lib/view/stack/backtrace',
77 | 'js/lib/view/stack/tree',
78 |
79 | // Plugins
80 | 'js/plugins/inspector',
81 | 'js/plugins/metrics',
82 | 'js/plugins/stack',
83 | 'js/plugins/duration',
84 | 'js/plugins/memory',
85 | 'js/plugins/calls',
86 | 'js/plugins/groups',
87 | 'js/plugins/files',
88 | 'js/plugins/searchengine',
89 | ),
90 | 'css' => array(
91 | 'css/default',
92 | 'css/' . $skin
93 | )
94 | );
95 |
96 |
97 | $path = dirname(__FILE__) . '/built/forp.min.js';
98 | $target = fopen($path, 'w+');
99 | try {
100 | $js = $css = '';
101 |
102 | foreach($files['js'] as $file) {
103 | $js .= file_get_contents(dirname(__FILE__) . '/' . $file . '.js');
104 | }
105 |
106 | foreach($files['css'] as $file) {
107 | $css .= file_get_contents(dirname(__FILE__) . '/' . $file . '.css');
108 | }
109 |
110 | $js = str_replace(array_keys($php_var), array_values($php_var), $js );
111 | $css = str_replace(array_keys($php_var), array_values($php_var), $css );
112 |
113 | // Inject CSS
114 | fwrite(
115 | $target,
116 | str_replace(
117 | '%forp.css%',
118 | CssMin::minify($css),
119 | '/** forp-ui (c) 2013 Anthony Terrien **/' .
120 | ($nomin ? $js : JSMin::minify($js))
121 | )
122 | );
123 |
124 | echo "File " . $path . " built\n";
125 | } catch(Exception $ex ) {
126 | echo "Fatal error : " . $ex->getMessage() . "\n\n";
127 | echo $ex->getTraceAsString();
128 |
129 | }
130 | fclose($target);
131 |
--------------------------------------------------------------------------------
/src/js/plugins/groups.js:
--------------------------------------------------------------------------------
1 | (function(forp, $) {
2 |
3 | forp.plugins.groups = {
4 | 'nav': {
5 | 'label': function() {
6 | return "groups (" + forp.getController().getStack().groupsCount + ")";
7 | },
8 | 'display': function() {
9 | return forp.getController().getStack().groupsCount > 0;
10 | },
11 | 'enabled': false,
12 | 'open': function(e) {
13 | var self = forp.getController(),
14 | datas = self.getStack()
15 | .getGroups();
16 |
17 | self.getConsole().empty().open();
18 |
19 | self.getGroupsBarChart()
20 | .appendTo(self.getConsole());
21 |
22 | var $table = $.table(
23 | ["group", "calls", "ms", "Kb"],
24 | ["group", "calls", "ms", "Kb"]
25 | );
26 |
27 | for(var i in datas) {
28 | $table
29 | .append(
30 | $("
")
31 | .append(
32 | $("| ")
33 | .attr("colspan", 4)
34 | .attr("style", "padding: 0px; height: 4px; background:"
35 | + forp.TagRandColor.provideFor(i))
36 | )
37 | )
38 | .line([
39 |
40 | datas[i].calls,
41 | $.roundDiv(datas[i].usec, 1000).toFixed(3) + '',
42 | $.roundDiv(datas[i].bytes, 1024).toFixed(3) + ''
43 | ])
44 | .prepend(
45 | $(" | ")
46 | .append(
47 | forp.TagRandColor.provideElementFor(i)
48 | )
49 | .append(
50 | $("")
51 | .append(
52 | $("").class('strong').text(i)
53 | )
54 | .append(
55 | $("").text(' (' + datas[i].refs.length + ' ' + (datas[i].refs.length>1 ? "entries" : "entry") + ')')
56 | )
57 | )
58 | );
59 |
60 | for(var j in datas[i].refs) {
61 | $table.line([
62 | datas[i].refs[j].id,
63 | $.Gauge(
64 | self.getStack().getFunctions()[datas[i].refs[j].id].calls,
65 | datas[i].calls
66 | ),
67 | $.Gauge(
68 | self.getStack().getFunctions()[datas[i].refs[j].id].getDuration(),
69 | datas[i].usec,
70 | 1000,
71 | 'ms'
72 | ),
73 | $.Gauge(
74 | self.getStack().getFunctions()[datas[i].refs[j].id].getMemory(),
75 | datas[i].bytes,
76 | 1024,
77 | 'Kb'
78 | )
79 | ])
80 | .attr("data-ref", datas[i].refs[j].id)
81 | .bind("click", self.toggleDetails);
82 | }
83 | }
84 |
85 | self.getConsole().show($table);
86 | },
87 | 'close': function() {
88 | forp.getController().getLayout().reduce();
89 | }
90 | }
91 | };
92 |
93 | })(forp, jMicro);
--------------------------------------------------------------------------------
/samples/php/basic/common.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 | ';
34 |
35 | /**
36 | * @ProfileGroup("Test")
37 | * @ProfileCaption("Hello #1 !")
38 | */
39 | function test($i = 0){ echo 'User function with "Hello ' . $i . ' !" profile caption. '; };
40 | /**
41 | * @ProfileGroup("Foo Group")
42 | * @ProfileCaption("User function that calls another one")
43 | */
44 | function test1(){test();};
45 | // user class
46 | class Foo {
47 |
48 | public $mybool = true;
49 |
50 | public $myintvar = 27;
51 |
52 | public $mystringvar = "test";
53 |
54 | public $myarray = array("assoc" => "test");
55 |
56 | public $myobject = null;
57 |
58 | public function __construct() {
59 | $this->myobject = new stdClass();
60 | }
61 |
62 | /**
63 | * @ProfileGroup("Foo","data")
64 | * @ProfileCaption("Caption of bar.")
65 | */
66 | function bar() { test1(); }
67 |
68 | /**
69 | * @ProfileGroup("Foo Group")
70 | * @ProfileCaption("Caption of bar2 #1 #2.")
71 | */
72 | function bar2($lambda, $object) { return test1(); }
73 | }
74 | // closure
75 | $lambda =
76 | /**
77 | * @ProfileGroup("Foo group")
78 | * @ProfileCaption("Closure")
79 | * @ProfileHighlight("1")
80 | * @ProfileAlias("HighlightTestClosure")
81 | */
82 | function() {
83 | echo 'User function with "Foo group" group, HighlightTestClosure" alias, "Closure" caption and highlight. ';
84 | test();
85 | };
86 |
87 | forp_inspect('lambda', $lambda);
88 |
89 | // calls
90 | for($i = 0; $i<1000; $i++) {
91 | test($i);
92 | // inspect stress
93 | // forp_inspect('i' . $i, $i);
94 | }
95 |
96 | for($i=0;$i<5;$i++){ test1(); }
97 | $lambda();
98 | $lambda();
99 | $foo = new Foo();
100 | //sleep(1);
101 | $foo->bar();
102 | $foo->bar2($lambda, $foo);
103 | forp_inspect('foo', $foo);
104 |
105 | /**
106 | * @ProfileAlias("Alloc")
107 | */
108 | $alloc = function(){
109 | $stdObject = new stdClass();
110 | $stdObject->arr = array();
111 | for($i = 0; $i<100; $i++) {
112 | $stdObject->arr[$i] = "test";
113 | }
114 | return $stdObject;
115 | };
116 | /**
117 | * @ProfileAlias("test alloc")
118 | */
119 | $allocTest = function(){
120 | global $alloc;
121 | $o = $alloc();
122 | };
123 | $allocTest();
124 |
125 | // fibo
126 | $br = (php_sapi_name() == "cli")? "\n":" \n";
127 |
128 | /**
129 | * @ProfileGroup("Fibo Group")
130 | * @ProfileCaption("Caption of fibo, value #1 #1 #1 #1")
131 | * @param type $x
132 | * @return int
133 | */
134 | function fibo( $x ) {
135 | if ( $x < 2) {
136 | return 1;
137 | } else {
138 | return fibo($x - 1) + fibo($x - 2);
139 | }
140 | }
141 |
142 | for( $i = 1; $i < 10; $i++) {
143 | printf(
144 | 'fibo(%1$s) = %2$s'.$br,
145 | $i, fibo($i)
146 | );
147 | }
148 | ?>
149 |
150 |
--------------------------------------------------------------------------------
/src/js/lib/view/layout/layout.js:
--------------------------------------------------------------------------------
1 | (function(forp, $){
2 |
3 | /**
4 | * Layout
5 | *
6 | * - Layout #forp
7 | * - Navbar nav
8 | * - MainPanel .mainpanel
9 | * - Console .console
10 | * - Sidebar .sidebar
11 | */
12 | forp.Layout = function($container, viewMode)
13 | {
14 | var self = this;
15 | forp.Decorator.call(this, $container);
16 | $container.attr("id", "forp");
17 |
18 | this.mainpanel = null;
19 | this.nav = null;
20 | this.viewMode = viewMode; // fixed, embedded
21 |
22 | this.conf = {
23 | fixed : {
24 | size : function() {
25 | self.$.attr("style", "");
26 | self.getConsole().$
27 | .attr(
28 | "style",
29 | "height: " + (self.$.height()-45) + "px"
30 | );
31 |
32 | if( self.getConsole()
33 | .hasSidebar()
34 | ) {
35 | self.getConsole()
36 | .getSidebar().$
37 | .attr(
38 | "style",
39 | "height: " + (self.$.height()-45) + "px"
40 | );
41 | }
42 | if(window.onresize == null) {
43 | window.onresize = function(e) {
44 | self.size();
45 | }
46 | }
47 | },
48 | reduce : function() {
49 | self.$.attr(
50 | "style",
51 | "height: 45px"
52 | );
53 | }
54 | }
55 | , embedded : {
56 | size : function() {
57 | self.$.attr(
58 | "style",
59 | "height: 100%"
60 | );
61 | self.getConsole().$
62 | .attr(
63 | "style",
64 | "height: " + (self.$.height()-45) + "px"
65 | );
66 | if( self.getConsole()
67 | .hasSidebar()
68 | ) {
69 | self.getConsole()
70 | .getSidebar().$
71 | .attr(
72 | "style",
73 | "height: " + (self.$.height()-45) + "px"
74 | );
75 | }
76 | },
77 | reduce : function() { return false }
78 | }
79 | };
80 |
81 | this.setViewMode = function(viewMode)
82 | {
83 | this.viewMode = viewMode;
84 | return this;
85 | };
86 |
87 | this.getMainPanel = function()
88 | {
89 | if(!this.mainpanel) {
90 | this.mainpanel = (new forp.MainPanel(this)).appendTo(this);
91 | }
92 | return this.mainpanel;
93 | };
94 |
95 | this.getNav = function()
96 | {
97 | if(!this.nav) {
98 | this.nav = (new forp.Nav()).appendTo(this);
99 | }
100 | return this.nav;
101 | };
102 |
103 | this.getConsole = function()
104 | {
105 | return this.getMainPanel().getConsole();
106 | };
107 |
108 | this.open = function()
109 | {
110 | this.$.class("forp-" + this.viewMode);
111 | return this;
112 | };
113 |
114 | this.size = function() {
115 | return (this.conf[this.viewMode].size() !== false);
116 | };
117 |
118 | this.reduce = function() {
119 | return (self.conf[self.viewMode].reduce() !== false);
120 | };
121 |
122 | this.compact = function(callback)
123 | {
124 | this.$
125 | .attr("style", "")
126 | .empty()
127 | .class("forp-" + this.viewMode + "-compact");
128 |
129 | this.nav = null;
130 | this.mainpanel = null;
131 |
132 | callback();
133 |
134 | return this;
135 | };
136 | };
137 | })(forp, jMicro);
--------------------------------------------------------------------------------
/src/css/consolas.css:
--------------------------------------------------------------------------------
1 | #forp {
2 | background: none;
3 | }
4 |
5 | #forp *{
6 | font-family: Consolas, monaco, monospace;
7 | font-size : 11px;
8 | color: #eee;
9 | }
10 |
11 | /* backtrace */
12 | #forp div.backtrace-item{
13 | word-wrap: break-word;
14 | max-width: 90%;
15 | background: #777;
16 | padding: 4px 10px 5px 10px;
17 | }
18 | #forp div.backtrace div.highlight{
19 | background: #333 !important;
20 | }
21 | #forp div.backtrace div.arrow {
22 | color: #ddd;
23 | }
24 |
25 | /* close */
26 | #forp div.close:hover{
27 | color: #eee;
28 | }
29 |
30 | /* console */
31 | #forp div.console{}
32 |
33 | /* forp */
34 | #forp.forp-fixed {
35 | height: 100%;
36 | background: #FFF;
37 | min-width: 1000px;
38 | top: 0px;
39 | left: 0px;
40 | }
41 | #forp.forp-fixed-compact {
42 | margin: 0px;
43 | position: fixed;
44 | top: 0px;
45 | right: 0px;
46 | bottom: auto;
47 | border-radius: 0;
48 | -moz-box-shadow: none;
49 | -webkit-box-shadow: none;
50 | box-shadow: none;
51 | width: 130px;
52 | }
53 | #forp.forp-fixed-compact nav{
54 | height: 35px;
55 | text-align: right;
56 | padding: 5px 10px;
57 | background: none;
58 | background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0) 7%, #333 0%);
59 | background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0) 7%, #333 0%);
60 | background-image: -ms-linear-gradient(45deg, rgba(255, 255, 255, 0) 7%, #333 0%);
61 | background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0) 7%, #333 0%);
62 | background-image: linear-gradient(45deg, rgba(255, 255, 255, 0) 7%, #333 0%);
63 | border-radius: 0;
64 | }
65 | #forp.forp-fixed-compact div.summary > div {
66 | color: #fff !important;
67 | }
68 | #forp.forp-fixed-compact.grade-A {
69 | border-right: 4px solid #9E5
70 | }
71 | #forp.forp-fixed-compact.grade-B {
72 | border-right: 4px solid #EE5
73 | }
74 | #forp.forp-fixed-compact.grade-C {
75 | border-right: 4px solid #EB5
76 | }
77 | #forp.forp-fixed-compact.grade-D {
78 | border-right: 4px solid #E95
79 | }
80 | #forp.forp-fixed-compact.grade-E {
81 | border-right: 4px solid #E55
82 | }
83 |
84 | /* gauge */
85 | #forp div.gauge{
86 | background: #777
87 | }
88 | #forp div.gauge div.text{}
89 | #forp div.gauge div.bar{}
90 |
91 | /* input */
92 | #forp input[type=text]{
93 | border: 1px solid #333;
94 | background: #333;
95 | margin: 10px 5px;
96 | }
97 | #forp input[type=text]:focus{
98 | background: #555;
99 | }
100 |
101 | /* inset */
102 | #forp div.inset {
103 | -webkit-box-shadow: none;
104 | -moz-box-shadow: none;
105 | box-shadow: none;
106 | }
107 |
108 | /* mainpanel */
109 | #forp div.mainpanel {
110 | background: #666;
111 | }
112 |
113 | /* nav */
114 | #forp nav {
115 | background: #222;
116 | height: 45px;
117 | padding: 0px;
118 | }
119 | #forp.forp-fixed nav, #forp.forp-embedded nav{
120 | height: 45px;
121 | }
122 | #forp div.toggleBar{
123 | min-width: 1100px;
124 | }
125 | #forp div.toggleOn, #forp div.toggleOff{
126 | height: 44px;
127 | padding: 15px 10px 5px 10px;
128 | min-width: 100px;
129 | background: none;
130 | border-radius: 0;
131 | margin: 0px;
132 | border-bottom: 4px solid #CCC;
133 | margin-right: 1px;
134 | }
135 | #forp div.toggleOn:hover, #forp div.toggleOff:hover{
136 | border-bottom: 4px solid #FFF;
137 | }
138 | #forp div.toggleOn{
139 | color: #EEE;
140 | border-bottom: 4px solid #9E5;
141 | background-image: -webkit-linear-gradient(-135deg, rgba(255, 255, 255, 0) 7%, #555 0%);
142 | background-image: -moz-linear-gradient(-135deg, rgba(255, 255, 255, 0) 7%, #555 0%);
143 | background-image: -ms-linear-gradient(-135deg, rgba(255, 255, 255, 0) 7%, #555 0%);
144 | background-image: -o-linear-gradient(-135deg, rgba(255, 255, 255, 0) 7%, #555 0%);
145 | background-image: linear-gradient(-135deg, rgba(255, 255, 255, 0) 7%, #555 0%);
146 | }
147 | #forp div.toggleOff{
148 | color: #EEE;
149 | background-image: -webkit-linear-gradient(-135deg, rgba(255, 255, 255, 0) 5%, #333 0%);
150 | background-image: -moz-linear-gradient(-135deg, rgba(255, 255, 255, 0) 5%, #333 0%);
151 | background-image: -ms-linear-gradient(-135deg, rgba(255, 255, 255, 0) 5%, #333 0%);
152 | background-image: -o-linear-gradient(-135deg, rgba(255, 255, 255, 0) 5%, #333 0%);
153 | background-image: linear-gradient(-135deg, rgba(255, 255, 255, 0) 5%, #333 0%);
154 | }
155 |
156 | /* table */
157 | #forp tr:nth-child(even) {background: #333}
158 | #forp tr:nth-child(odd) {background: #444}
159 | #forp tr:hover{
160 | background: #777;
161 | }
162 | #forp th, #forp td {
163 | padding: 7px;
164 | border: none;
165 | }
166 | #forp th{
167 | background: #999;
168 | }
169 | #forp th.reorder {
170 | cursor:pointer;
171 | }
--------------------------------------------------------------------------------
/src/js/plugins/inspector.js:
--------------------------------------------------------------------------------
1 | (function(forp, $) {
2 |
3 | forp.plugins.inspector = {
4 | 'nav': {
5 | 'label': 'inspector',
6 | 'display': function() {
7 | return (forp.getController().getStack().inspect != null);
8 | },
9 | 'enabled': function() {
10 | return (forp.getController().getStack().inspect != null);
11 | },
12 | 'open': function(e) {
13 |
14 | var self = forp.getController(),
15 | $table = $.table(["var", "type"], ["var", "type"]),
16 | ivars = self.getStack().inspect;
17 |
18 | for(var ivar in ivars) {
19 | var info;
20 | if(
21 | typeof(ivars[ivar]) === "object"
22 | && ivars[ivar].properties
23 | ) {
24 | info = $('');
25 | for(var prop in ivars[ivar].properties) {
26 | info.append($(' ').text(prop));
27 | }
28 | } else {
29 | info = $.Utils.htmlEntities(ivars[ivar].value);
30 | }
31 |
32 | $table
33 | .line(
34 | [
35 | ivar,
36 | ivars[ivar].type
37 | ]
38 | )
39 | .attr('data-ref', ivar)
40 | .bind(
41 | 'click',
42 | function() {
43 |
44 | var $ul = $("").class("inspect"),
45 | ivar = $(this).attr('data-ref'),
46 | list = function(v, ul) {
47 | for(var entry in v) {
48 | var el = $("- ");
49 | if(typeof(v[entry]) == 'object') {
50 | ul.append(
51 | el.text(entry + ":")
52 | );
53 | var sul = $("
").appendTo(el);
54 |
55 | if(v[entry].type) {
56 | switch(v[entry].type) {
57 | case "string" :
58 | case "int" :
59 | case "bool" :
60 | ul.append(
61 | el.text(
62 | entry + ": ("
63 | + v[entry].type
64 | + ") "
65 | + $.Utils.htmlEntities(v[entry].value)
66 | )
67 | );
68 | break;
69 | default:
70 | list(v[entry], sul);
71 | }
72 |
73 | } else {
74 | list(v[entry], sul);
75 | }
76 | } else {
77 | ul.append(
78 | el.text(entry + ": " + $.Utils.htmlEntities(v[entry]))
79 | );
80 | }
81 | }
82 | };
83 |
84 | list(
85 | forp.getController().getStack().inspect[ivar],
86 | $ul
87 | );
88 |
89 | forp.getController()
90 | .getConsole()
91 | .showInSidebar($ul);
92 | }
93 | );
94 | }
95 |
96 | self.getConsole().show($table);
97 | },
98 | 'close': function() {
99 | forp.getController().getLayout().reduce();
100 | }
101 | }
102 | };
103 |
104 | })(forp, jMicro);
--------------------------------------------------------------------------------
/src/js/lib/view/stack/tree.js:
--------------------------------------------------------------------------------
1 | (function(forp, $){
2 |
3 | /**
4 | * Stack Tree Class
5 | * @param Object stack Call stack array
6 | */
7 | $.tree = function(stack)
8 | {
9 | var self = this;
10 |
11 | /**
12 | * Generates a tree representation (UL) of the stack
13 | *
14 | * @param array entry Root entry
15 | * @param boolean recursive Says if we have to fetch it recursively
16 | * @return Object Wrapped UL
17 | */
18 | this.treeList = function(entry, recursive)
19 | {
20 | var ul = $("").addClass("l" + entry.level)
21 | , ex = $("")
22 | .html(" ")
23 | .addClass("left expander")
24 | , gd = ($.Gauge(
25 | entry.usec,
26 | stack[entry.parent] ? stack[entry.parent].usec : entry.usec,
27 | 1000,
28 | 'ms'
29 | )).addClass("left")
30 | , gb = ($.Gauge(
31 | entry.bytes,
32 | stack[entry.parent] ? stack[entry.parent].bytes : entry.bytes,
33 | 1024,
34 | 'Kb'
35 | )).addClass("left")
36 | , li = $(" - ").text(entry.id);
37 |
38 |
39 | if(entry.groups) {
40 | for(var g in entry.groups) {
41 | li.append(forp.TagRandColor.provideElementFor(entry.groups[g]));
42 | }
43 | }
44 | if(entry.caption) li.append($("").text(entry.caption));
45 |
46 | li.append(ex).append(gd).append(gb).appendTo(ul);
47 |
48 | if(entry.childrenRefs) {
49 |
50 | li.removeClass("expanded").addClass("collapsed");
51 |
52 | ex.bind(
53 | 'click'
54 | , function() {
55 | if(li.hasClass("expanded")) {
56 | li.removeClass("expanded").addClass("collapsed");
57 | } else {
58 | li.removeClass("collapsed").addClass("expanded");
59 | if(!li.attr("data-tree")) {
60 | for(var i in entry.childrenRefs) {
61 | self
62 | .treeList(stack[entry.childrenRefs[i]], true)
63 | .appendTo(li);
64 | }
65 | li.attr("data-tree", 1);
66 | }
67 | }
68 | }
69 | );
70 |
71 | if(parseInt(entry.level) < 2) {
72 | li.removeClass("collapsed").addClass("expanded");
73 | if(!li.attr("data-tree")) {
74 | li.attr("data-tree", 1);
75 | var loopCounter,
76 | lastId,
77 | loopMaxIter = 100;
78 |
79 | for(var i in entry.childrenRefs) {
80 |
81 | if(stack[entry.childrenRefs[i]].id == lastId) {
82 | loopCounter++;
83 | } else {
84 | loopCounter = 0;
85 | }
86 | lastId = stack[entry.childrenRefs[i]].id;
87 |
88 | // loop detected, max item reached
89 | if(loopCounter == loopMaxIter) {
90 | $("
")
91 | .append(
92 | $("- ")
93 | .append(ex)
94 | .append(
95 | $("
")
96 | .text(lastId + " : too many items to display (>" + loopMaxIter + ")")
97 | )
98 | )
99 | .appendTo(li);
100 | continue;
101 | }
102 |
103 | if(loopCounter < loopMaxIter) {
104 | this.treeList(stack[entry.childrenRefs[i]])
105 | .appendTo(li);
106 | }
107 | }
108 | }
109 | } else {
110 | li.removeClass("expanded").addClass("collapsed");
111 | }
112 | }
113 |
114 | return ul;
115 | };
116 |
117 | return this.treeList(stack[0], true);
118 | };
119 | })(forp, jMicro);
--------------------------------------------------------------------------------
/samples/php/full/vendor/forp/forp/Forp.php:
--------------------------------------------------------------------------------
1 | false);
20 |
21 | /**
22 | * @param type $opts
23 | * @return \forp\Response
24 | */
25 | public function setConf($opts)
26 | {
27 | $this->conf = array_merge($this->conf, $opts);
28 | return $this;
29 | }
30 |
31 | /**
32 | * @return \forp\ResponseCli|\forp\ResponseHttpHeaders|\forp\ResponseInlineXForpStack|\forp\ResponseInlineAsync|\forp\ResponseInline
33 | */
34 | public function getResponse()
35 | {
36 | if(php_sapi_name() === "cli") {
37 | return new ResponseCli();
38 | }
39 |
40 | // priority to send forp datas in HTTP headers
41 | // forp client is a browser extension
42 | if(isset($_SERVER)
43 | && isset($_SERVER['HTTP_X_FORP_VERSION'])
44 | ) {
45 | if(!empty($_SERVER['HTTP_ACCEPT'])
46 | && strpos(strtolower($_SERVER['HTTP_ACCEPT']), 'json')
47 | ) {
48 | // send forp datas in HTTP headers
49 | // XHR communication case
50 | return new ResponseHttpHeaders();
51 | } else {
52 | // headers size limitation on Chrome
53 | return new ResponseInlineXForpStack();
54 | }
55 | }
56 |
57 | // asynchrone JavaScript loading + inline JavaScript
58 | if($this->conf['async']) {
59 | return new ResponseInlineAsync();
60 | }
61 |
62 | // JavaScript loading + inline JavaScript
63 | return new ResponseInline();
64 | }
65 |
66 | /**
67 | * @return \forp\Response
68 | */
69 | public function send()
70 | {
71 | $this->getResponse()
72 | ->send(forp_dump());
73 |
74 | return $this;
75 | }
76 | }
77 |
78 | /**
79 | * IResponse
80 | */
81 | interface IResponse
82 | {
83 | public function send(array $datas);
84 | }
85 |
86 | /**
87 | * ResponseCli
88 | */
89 | class ResponseCli
90 | implements IResponse
91 | {
92 | /**
93 | * @param array $datas
94 | */
95 | public function send(array $datas)
96 | {
97 | forp_print();
98 | }
99 | }
100 |
101 | /**
102 | * ResponseInline
103 | */
104 | class ResponseInline
105 | implements IResponse
106 | {
107 | /**
108 | * @param array $datas
109 | */
110 | public function send(array $datas)
111 | {
112 | ?>
113 |
114 |
115 |
123 |
139 |
140 |
141 |
145 |
160 |
176 | ";
179 | }
180 | }
181 |
182 | /**
183 | * ResponseHttpHeaders
184 | */
185 | class ResponseHttpHeaders
186 | implements IResponse
187 | {
188 | /**
189 | * @param array $datas
190 | * @throws \Exception
191 | */
192 | public function send(array $datas)
193 | {
194 | $parts = explode(
195 | "\n",
196 | chunk_split(json_encode($datas), 5000, "\n")
197 | );
198 | foreach($parts as $i=>$part) {
199 | header("X-Forp-Stack_" . $i . ":" . $part);
200 | if ($i > 99999) {
201 | throw new \Exception('Can\t exceed 99999 chunks.');
202 | }
203 | }
204 | }
205 | }
--------------------------------------------------------------------------------
/src/js/lib/helpers/grader.js:
--------------------------------------------------------------------------------
1 | (function(forp, $) {
2 |
3 | "use strict";
4 |
5 | /**
6 | * Grader Class
7 | *
8 | * Provides grades, quality metrics
9 | */
10 | forp.Grader = function()
11 | {
12 | this.grades = {
13 | time : {
14 | A : {
15 | min : 0, max : 100, tip : ["Very good job !", "The planet will reward you !", "You'll be the king at the coffee machine.", "Your servers thanks you."]
16 | },
17 | B : {
18 | min : 100, max : 300, tip : ["Good job !"]
19 | },
20 | C : {
21 | min : 300, max : 600, tip : ["You are close to job performance."]
22 | },
23 | D : {
24 | min : 600, max : 1000, tip : ["You are under one second.", "Think cache."]
25 | },
26 | E : {
27 | min : 1000, max : 2000, tip : ["At your own risk !"]
28 | }
29 | },
30 | memory : {
31 | A : {
32 | min : 0, max : 2000, tip : ["Very good job !"]
33 | },
34 | B : {
35 | min : 2000, max : 4000, tip : ["Good job !"]
36 | },
37 | C : {
38 | min : 4000, max : 8000, tip : ["Respectable"]
39 | },
40 | D : {
41 | min : 8000, max : 12000, tip : ["It seems that you load too much data."]
42 | },
43 | E : {
44 | min : 12000, max : 20000, tip : ["It seems that you load too much data."]
45 | }
46 | },
47 | includes : {
48 | A : {
49 | min : 0, max : 5, tip : ["Very good job !"]
50 | },
51 | B : {
52 | min : 5, max : 20, tip : ["Good job !"]
53 | },
54 | C : {
55 | min : 30, max : 60, tip : ["A builder script could do the rest."]
56 | },
57 | D : {
58 | min : 60, max : 120, tip : ["A builder script could be your best friend on this."]
59 | },
60 | E : {
61 | min : 120, max : 240, tip : ["At your own risk !", "A builder script could be your best friend on this."]
62 | }
63 | },
64 | calls : {
65 | A : {
66 | min : 0, max : 2000, tip : ["Very good job !", "This is the 'Hello world' script ?"]
67 | },
68 | B : {
69 | min : 2000, max : 4000, tip : ["Very good job !"]
70 | },
71 | C : {
72 | min : 4000, max : 8000, tip : ["Respectable"]
73 | },
74 | D : {
75 | min : 8000, max : 16000, tip : ["Has a bad impact on performance."]
76 | },
77 | E : {
78 | min : 32000, max : 64000, tip : ["It's a joke ?", "At your own risk !", "Too many instructions."]
79 | }
80 | },
81 | nesting : {
82 | E : {
83 | min : 0, max : 5, tip : ["This is the 'Hello world' script ?"]
84 | },
85 | A : {
86 | min : 5, max : 10, tip : ["Good job !"]
87 | },
88 | B : {
89 | min : 10, max : 15, tip : ["Respectable"]
90 | },
91 | C : {
92 | min : 15, max : 20, tip : ["Respectable"]
93 | },
94 | D : {
95 | min : 20, max : 30, tip : ["Perhaps, are you currently refactoring ?"]
96 | }
97 | }
98 | };
99 |
100 | this.getGrade = function(gradeName, mesure) {
101 | for(var grade in this.grades[gradeName]) {
102 | if( mesure >= this.grades[gradeName][grade]['min']
103 | && mesure <= this.grades[gradeName][grade]['max']
104 | ) {
105 | return grade;
106 | }
107 | }
108 | return grade;
109 | };
110 |
111 | this.getTip = function(gradeName, grade) {
112 | var i = Math.floor((Math.random() * this.grades[gradeName][grade]['tip'].length));
113 | return this.grades[gradeName][grade]['tip'][i];
114 | };
115 |
116 | this.getClass = function(grade) {
117 | return "grade-" + grade;
118 | };
119 |
120 | this.getGradeWithTip = function(gradeName, mesure) {
121 | var grade = this.getGrade(gradeName, mesure);
122 | return $(' ')
123 | .append(
124 | $(' ')
125 | .class(this.getClass(grade))
126 | .text(grade)
127 | )
128 | .append(
129 | $(' ')
130 | .text(this.getTip(gradeName, grade))
131 | );
132 | };
133 | };
134 | })(forp, jMicro);
--------------------------------------------------------------------------------
/src/css/default.css:
--------------------------------------------------------------------------------
1 | /* reset CSS */
2 | #forp *{
3 | margin: 0;
4 | padding: 0;
5 | border: 0;
6 | font-family: sans-serif;
7 | font-size: 1em;
8 | font-weight: normal;
9 | font-style: normal;
10 | text-decoration: none;
11 | width: auto;
12 | height: auto;
13 | color: #222;
14 | line-height: 1.3;
15 | }
16 | #forp {
17 | position: relative;
18 | font-weight: 300;
19 | max-width: 300px;
20 | background: #fff;
21 | font-size : 12px;
22 | }
23 | #forp div.inset {
24 | height: 10px; position: absolute;
25 | top: 0px; left: 0px; right: 0px;
26 | -webkit-box-shadow: inset 0 3px 5px -2px #aaa;
27 | -moz-box-shadow: inset 0 3px 5px -2px #aaa;
28 | box-shadow: inset 0 3px 5px -2px #aaa;
29 | }
30 | #forp.forp-fixed {
31 | height: 70%;
32 | position: fixed;
33 | bottom: 0px;
34 | right: 0px;
35 | z-index: 2147483647;
36 | }
37 | #forp.forp-fixed-compact {
38 | position: fixed;
39 | bottom: 0px;
40 | right: 0px;
41 | z-index: 2147483647;
42 | width: 150px;
43 | opacity: .6;
44 | cursor: pointer;
45 | margin: 15px;
46 | border-radius: 8px;
47 | -moz-box-shadow: 0 0 8px #ccc;
48 | -webkit-box-shadow: 0 0 8px #ccc;
49 | box-shadow: 0 0 8px #ccc;
50 | }
51 | #forp.forp-fixed-compact:hover {
52 | opacity: 1;
53 | }
54 | #forp.forp-fixed-compact nav{
55 | height: auto;
56 | padding: 10px;
57 | border-radius: 8px;
58 | }
59 | #forp.forp-fixed-compact nav div{
60 | margin: 3px 0px;
61 | }
62 | #forp.forp-fixed, #forp.forp-embedded {
63 | min-width: 1000px;
64 | max-width: 100%;
65 | left: 0px;
66 | }
67 | #forp.forp-fixed a, #forp.forp-embedded a{
68 | display: inline;
69 | }
70 | #forp.forp-fixed nav div, #forp.forp-embedded nav div{
71 | float: left;
72 | }
73 | #forp.forp-fixed div.summary, #forp.forp-embedded div.summary{
74 | display: none
75 | }
76 |
77 | /* backtrace */
78 | #forp div.backtrace{
79 | text-align: center;
80 | color: #aaa;
81 | margin: 30px auto;
82 | }
83 | #forp div.backtrace-item{
84 | color: #333;
85 | margin: 0px 5px;
86 | padding: 4px 5px 5px 5px;
87 | border-radius: 8px;
88 | padding: 5px;
89 | display: inline-block;
90 | }
91 | #forp div.backtrace div.arrow{
92 | margin: 5px;
93 | color: #999;
94 | }
95 | #forp div.backtrace div.highlight{
96 | background: #4D90FE !important;
97 | color: #FFF !important;
98 | }
99 | #forp div.backtrace div.highlight span{
100 | color: #FFF !important;
101 | }
102 |
103 | /* close */
104 | #forp div.close{}
105 | #forp div.close:before{
106 | content: "x";
107 | }
108 | #forp div.close{
109 | color: #ccc;
110 | position: absolute;
111 | top: 14px;
112 | right: 30px;
113 | }
114 | #forp div.close:hover{
115 | cursor: pointer;
116 | }
117 |
118 | /* console */
119 | #forp div.console{
120 | position: relative;
121 | float: left;
122 | width: 100%;
123 | }
124 |
125 | /* gauge */
126 | #forp div.gauge{
127 | position: relative;
128 | background: #eee;
129 | color: #333;
130 | margin: 1px;
131 | width: 110px;
132 | text-align: right;
133 | }
134 | #forp div.gauge div.bar{
135 | background: #59f;
136 | height: 18px;
137 | }
138 | #forp div.gauge div.text{
139 | position: absolute;
140 | top: 0px;
141 | right: 4px;
142 | line-height: 1.8;
143 | font-size: .9em
144 | }
145 | #forp td div.gauge{
146 | margin: 0px;
147 | }
148 |
149 | /* grade */
150 | #forp div.grade, #forp div.grade-A, #forp div.grade-B,
151 | #forp div.grade-C, #forp div.grade-D, #forp div.grade-E {
152 | padding: 2px 5px;
153 | display: inline-block;
154 | border-radius: 3px;
155 | font-size: 0.8em;
156 | color: #FFF;
157 | margin-right: 10px
158 | }
159 | #forp div.grade-A {
160 | background-color: #9E5
161 | }
162 | #forp div.grade-B {
163 | background-color: #EE5
164 | }
165 | #forp div.grade-C {
166 | background-color: #EB5
167 | }
168 | #forp div.grade-D {
169 | background-color: #E95
170 | }
171 | #forp div.grade-E {
172 | background-color: #E55
173 | }
174 |
175 | /* inspector */
176 | #forp ul.inspect{
177 | padding: 10px 0px 0px 0px;
178 | }
179 |
180 | /* input */
181 | #forp input[type=text]{
182 | padding: 4px;
183 | border: 1px solid #777;
184 | border-radius: 3px;
185 | background: #fff;
186 | }
187 |
188 | /* nav */
189 | #forp nav{
190 | background: #fcfcfc;
191 | padding: 14px 0px 8px 4px;
192 | }
193 | #forp.forp-fixed nav, #forp.forp-embedded nav{
194 | height: 44px;
195 | }
196 | #forp a{
197 | white-space: nowrap;
198 | color: #FFF;
199 | }
200 | #forp div.toggleOn, #forp div.toggleOff{
201 | margin: 0px 5px;
202 | padding: 4px;
203 | float: left;
204 | border-radius: 2px;
205 | }
206 | #forp div.toggleOn:hover, #forp div.toggleOff:hover{
207 | cursor: pointer;
208 | }
209 | #forp div.toggleOn{
210 | color: #FFF;
211 | background: #4D90FE;
212 | }
213 | #forp div.toggleOff{
214 | color: #FFF;
215 | background: #555;
216 | }
217 |
218 | /* sidebar */
219 | #forp div.sidebar{
220 | float: right;
221 | }
222 | #forp div.w2of3 {
223 | width: 66% !important;
224 | }
225 | #forp div.w1of3 {
226 | width: 33% !important;
227 | }
228 |
229 | /* stack */
230 | #forp ul{
231 | list-style: none;
232 | padding-left: 20px;
233 | }
234 | #forp ul.l0{
235 | padding-left: 10px;
236 | }
237 | #forp li{
238 | line-height: 1.8 !important;
239 | margin: 2px
240 | }
241 | #forp li div.numeric{
242 | min-width: 60px;
243 | margin: 0px 10px
244 | }
245 | #forp .indent{
246 | padding-left: 30px
247 | }
248 | #forp li div.expander{
249 | text-align:center;
250 | width: 20px;
251 | cursor: pointer;
252 | }
253 | #forp li.expanded div.expander:before{
254 | content: "-";
255 | }
256 | #forp li.collapsed div.expander:before{
257 | content: "+";
258 | }
259 | #forp li.collapsed ul{
260 | display: none;
261 | }
262 | #forp .left{
263 | float: left;
264 | }
265 | #forp .right{
266 | float: right;
267 | }
268 |
269 | /* table */
270 | #forp table{
271 | font-weight: 300;
272 | width: 100%;
273 | border-collapse: collapse;
274 | }
275 | #forp th, #forp td{
276 | padding: 5px;
277 | }
278 | #forp th{
279 | text-align: center;
280 | font-weight: bold;
281 | color: #333;
282 | border: 1px solid #ddd;
283 | background: #eee;
284 | }
285 | #forp .w100{
286 | width: 110px;
287 | }
288 | #forp td{
289 | text-align: left;
290 | text-overflow: ellipsis;
291 | word-space: nowrap;
292 | overflow: hidden;
293 | vertical-align: top;
294 | }
295 | #forp tr{
296 | color: #333;
297 | background: #fff;
298 | }
299 | #forp tr.sub{
300 | background: #eee;
301 | }
302 | #forp tr:hover{
303 | background: #EDF4FF;
304 | }
305 | #forp tr[data-ref]{
306 | cursor: pointer;
307 | }
308 | #forp .numeric{
309 | text-align: right;
310 | }
311 |
312 | /* tags */
313 | #forp a.tag{
314 | background: #EE0;
315 | color: #fff;
316 | font-size : 0.8em;
317 | padding: 2px 5px;
318 | margin: 0px 5px;
319 | }
320 | #forp a.tag, #forp a{
321 | border-radius: 3px;
322 | }
323 | #forp div.panel {
324 | overflow: auto;
325 | overflow-x: hidden;
326 | }
--------------------------------------------------------------------------------
/src/js/lib/view/utils.js:
--------------------------------------------------------------------------------
1 | (function(forp, $) {
2 |
3 | "use strict";
4 |
5 | /**
6 | * ScrollBottom jMicro Plugin
7 | */
8 | $.fn.scrollBottom = function() {
9 | return this.each(function() {
10 | this.scrollTop = $(this.firstChild).height();
11 | });
12 | };
13 |
14 | /**
15 | * jMicro table() helper
16 | */
17 | $.table = function(headers, reorder) {
18 | var $table = $('');
19 | if(headers) {
20 | var $header = $("");
21 | for(var i in headers) {
22 | if (reorder && ~reorder.indexOf(headers[i])) {
23 | $("| ").text(headers[i])
24 | .addClass("reorder")
25 | .on("click", $.fn.tableOrder)
26 | .appendTo($header);
27 | } else {
28 | $(" | ").text(headers[i]).appendTo($header);
29 | }
30 | }
31 | $header.appendTo($table);
32 | }
33 | return $table;
34 | };
35 |
36 | /**
37 | * jMicro tableOrder callback
38 | */
39 | $.fn.tableOrder = function(e) {
40 | $(e.srcElement).data("order", $(e.srcElement).data("order") ? 0 : 1);
41 | var $tbl = $(e.srcElement).parent().parent(),
42 | index = e.srcElement.cellIndex,
43 | trs = [],
44 | order = $(e.srcElement).data("order");
45 |
46 | for (var i = 1; i < $tbl[0].childNodes.length; i++) {
47 | trs.push($tbl[0].childNodes[i]);
48 | }
49 |
50 | trs.sort(function(a, b) {
51 | var aval = a.childNodes[index].innerHTML.replace(/<.*?>/g, ''),
52 | bval = b.childNodes[index].innerHTML.replace(/<.*?>/g, ''),
53 | regex = /^[\d\.]+$/;
54 |
55 | if (regex.test(aval) && regex.test(bval)) {
56 | return order ? (parseFloat(aval) - parseFloat(bval)): (parseFloat(bval) - parseFloat(aval));
57 | } else if (aval < bval) {
58 | return order ? -1 : 1;
59 | } else if (aval > bval) {
60 | return order ? 1 : -1;
61 | }
62 | return 0;
63 | });
64 |
65 | for (var i = 0; i < trs.length; i++) {
66 | $tbl.append(trs[i]);
67 | }
68 | };
69 |
70 | /**
71 | * jMicro line() helper
72 | */
73 | $.fn.line = function(cols) {
74 | var $tr = $(' | ');
75 | for(var i in cols) {
76 | if(typeof cols[i] === "object") {
77 | $tr.append(
78 | $("| ")
79 | .append(cols[i])
80 | );
81 | } else if(isNaN(cols[i])) {
82 | $tr.append(
83 | $(" | ")
84 | .html(cols[i])
85 | );
86 | } else {
87 | $tr.append(
88 | $(" | ")
89 | .addClass("numeric w100")
90 | .html(cols[i])
91 | );
92 | }
93 | }
94 | $(this).append($tr);
95 | return $tr;
96 | };
97 |
98 | /**
99 | * Utils, helpers
100 | */
101 | $.Utils = {
102 | /**
103 | * TODO depth
104 | * @param path File path
105 | */
106 | trimPath : function(path)
107 | {
108 | var pathSplit = path.split('/');
109 | if(pathSplit.length > 3) {
110 | pathSplit = [pathSplit[pathSplit.length - 4], pathSplit[pathSplit.length - 3], pathSplit[pathSplit.length - 2], pathSplit[pathSplit.length - 1]];
111 | path = pathSplit.join('/');
112 | }
113 | return path;
114 | },
115 | htmlEntities : function(str) {
116 | return String(str)
117 | .replace(/&/g, '&')
118 | .replace(//g, '>')
120 | .replace(/"/g, '"');
121 | }
122 | };
123 |
124 | /**
125 | * Sorted Fixed Array Class
126 | * @param callback compare
127 | * @param int size
128 | */
129 | $.SortedFixedArray = function(compare, size) {
130 | this.stack = [];
131 | this.size = size;
132 | /**
133 | * Internal method insert
134 | * @param mixed entry
135 | * @param int i
136 | */
137 | this.insert = function(entry, i) {
138 | for(var j = Math.min(this.size - 1, this.stack.length); j > i; j--) {
139 | this.stack[j] = this.stack[j - 1];
140 | }
141 | this.stack[i] = entry;
142 | }
143 | /**
144 | * Evaluate and put a new entry in the stack
145 | * @param mixed entry
146 | */
147 | this.put = function(entry) {
148 | if(this.stack.length) {
149 | // break if end entry is greatest
150 | if(this.stack.length == this.size) {
151 | if(!compare.call(this, entry, this.stack[this.size-1])) {
152 | return;
153 | }
154 | }
155 | // insert entry at the right place
156 | for(var i = 0; i < this.stack.length; i++) {
157 | if(compare.call(this, entry, this.stack[i])) {
158 | this.insert(entry, i);
159 | break;
160 | }
161 | }
162 | if(
163 | (i == this.stack.length)
164 | && (this.stack.length < this.size)
165 | ) {
166 | this.insert(entry, i);
167 | }
168 | } else {
169 | this.insert(entry, 0);
170 | }
171 | };
172 | };
173 |
174 | /**
175 | * @param string v
176 | * @return int
177 | */
178 | $.round = function(v)
179 | {
180 | return (~~ (0.5 + (v * 1000))) / 1000;
181 | };
182 |
183 | /**
184 | * @param string v
185 | * @param int d
186 | * @return int
187 | */
188 | $.roundDiv = function(v, d)
189 | {
190 | return this.round(v / d);
191 | };
192 |
193 | /**
194 | * inArray
195 | * @param needle
196 | * @param haystack
197 | */
198 | $.inArray = function(needle, haystack) {
199 | var length = haystack.length;
200 | for(var i = 0; i < length; i++) {
201 | if(haystack[i] == needle) return true;
202 | }
203 | return false;
204 | };
205 |
206 | /**
207 | * Helper for old jMicro portability
208 | */
209 | forp.Decorator = function($container)
210 | {
211 | var self = this;
212 | this.$ = $container;
213 | this.element = $container[0];
214 |
215 | this.unbind = function(event, fn)
216 | {
217 | self.$.unbind(event, fn);
218 | return this;
219 | };
220 |
221 | this.appendTo = function(container)
222 | {
223 | this.$.appendTo(container.$);
224 | return this;
225 | };
226 |
227 |
228 | this.append = function(element)
229 | {
230 | this.$.append(element.$);
231 | return this;
232 | };
233 |
234 | this.empty = function()
235 | {
236 | this.$.empty();
237 | return this;
238 | };
239 |
240 | this.attr = function(k, v)
241 | {
242 | this.$.attr(k, v);
243 | return this;
244 | };
245 |
246 | /*this.table = function(headers) {
247 | return (new forp.Table(headers)).appendTo(this);
248 | };*/
249 | };
250 |
251 | /**
252 | * Nav
253 | */
254 | forp.Nav = function()
255 | {
256 | forp.Decorator.call(this, $(" | |