1 // ==========================================================================
  2 // Project:   SproutCore Costello - Property Observing Library
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 /*globals CoreTest module */
  9 
 10 /**
 11  * jsDump
 12  * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
 13  * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php)
 14  * Date: 5/15/2008
 15  * @projectDescription Advanced and extensible data dumping for Javascript.
 16  * @version 1.0.0
 17  * @author Ariel Flesler
 18  * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
 19  */
 20 (function(){
 21   var reName, jsDump;
 22   
 23   function quote( str ){
 24     return '"' + str.toString().replace(/"/g, '\\"') + '"';
 25   }
 26   
 27   function literal( o ){
 28     return o + '';  
 29   }
 30   
 31   function join( pre, arr, post ){
 32     var s     = jsDump.separator(),
 33         base  = jsDump.indent(),
 34         inner = jsDump.indent(1);
 35         
 36     if( arr.join )  arr = arr.join( ',' + s + inner );
 37     if( !arr ) return pre + post;
 38     
 39     return [ pre, inner + arr, base + post ].join(s);
 40   }
 41   
 42   function array( arr ){
 43     var i = arr.length, ret = new Array(i);         
 44     this.up();
 45     while( i-- ) ret[i] = this._parse( arr[i] );        
 46     this.down();
 47     return join( '[', ret, ']' );
 48   }
 49   
 50   reName = /^function (\w+)/;
 51   
 52   jsDump = CoreTest.jsDump = {
 53 
 54     parse: function(obj, type) {
 55       if (obj && obj.toString) {
 56         var toString = obj.toString;
 57         if ((toString !== Object.prototype.toString) && (toString !== Array.toString)) return obj.toString();
 58       }
 59       if (obj && obj.inspect) return obj.inspect();
 60       
 61       this.seen = [];
 62       var ret = this._parse(obj, type);
 63       this.seen = null;
 64       return ret ;
 65     },
 66     
 67     //type is used mostly internally, you can fix a (custom)type in advance
 68     _parse: function( obj, type ) {
 69       
 70       
 71       var parser = this.parsers[ type || this.typeOf(obj) ];
 72       type = typeof parser;     
 73 
 74       // avoid recursive loops
 75       if ((parser === this.parsers.object) && (this.seen.indexOf(obj)>=0)) {
 76         return '(recursive)';
 77       }
 78       this.seen.push(obj);
 79       
 80       return type == 'function' ? parser.call( this, obj ) :
 81            type == 'string' ? parser :
 82            this.parsers.error;
 83     },
 84     typeOf:function( obj ){
 85       var type = typeof obj,
 86         f = 'function';//we'll use it 3 times, save it
 87         
 88       if (obj && (obj.isObject || obj.isClass)) return 'scobj';
 89       return type != 'object' && type != f ? type :
 90         !obj ? 'null' :
 91         obj.exec ? 'regexp' :// some browsers (FF) consider regexps functions
 92         obj.getHours ? 'date' :
 93         obj.scrollBy ?  'window' :
 94         obj.nodeName == '#document' ? 'document' :
 95         obj.nodeName ? 'node' :
 96         obj.item ? 'nodelist' : // Safari reports nodelists as functions
 97         obj.callee ? 'arguments' :
 98         obj.call || obj.constructor != Array && //an array would also fall on this hack
 99           (obj+'').indexOf(f) != -1 ? f : //IE reports functions like alert, as objects
100         'length' in obj ? 'array' :
101         type;
102     },
103     separator:function(){
104       return this.multiline ? this.HTML ? '<br />' : '\n' : this.HTML ? ' ' : ' ';
105     },
106     indent:function( extra ){// extra can be a number, shortcut for increasing-calling-decreasing
107       if( !this.multiline ) return '';
108       
109       var chr = this.indentChar;
110       if( this.HTML ) chr = chr.replace(/\t/g,'   ').replace(/ /g,' ');
111       return (new Array( this._depth_ + (extra||0) )).join(chr);
112     },
113     up:function( a ){
114       this._depth_ += a || 1;
115     },
116     down:function( a ){
117       this._depth_ -= a || 1;
118     },
119     setParser:function( name, parser ){
120       this.parsers[name] = parser;
121     },
122     // The next 3 are exposed so you can use them
123     quote:quote, 
124     literal:literal,
125     join:join,
126     //
127     _depth_: 1,
128     // This is the list of parsers, to modify them, use jsDump.setParser
129     parsers:{
130       window: '[Window]',
131       document: '[Document]',
132       error:'[ERROR]', //when no parser is found, shouldn't happen
133       unknown: '[Unknown]',
134       'null':'null',
135       'undefined':'undefined',
136       'function':function( fn ){
137         var ret = 'function',
138           name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE
139         if( name ) ret += ' ' + name;
140         ret += '(';
141         
142         ret = [ ret, this._parse( fn, 'functionArgs' ), '){'].join('');
143         return join( ret, this._parse(fn,'functionCode'), '}' );
144       },
145       array: array,
146       nodelist: array,
147       'arguments': array,
148       scobj: function(obj) { return obj.toString(); },
149       object:function( map ){
150         
151         var ret = [ ];
152         this.up();
153         for( var key in map ) {
154           ret.push( this._parse(key,'key') + ': ' + this._parse(map[key]) );
155         }
156         this.down();
157         return join( '{', ret, '}' );
158       },
159       node:function( node ){
160         var open = this.HTML ? '<' : '<',
161           close = this.HTML ? '>' : '>';
162           
163         var tag = node.nodeName.toLowerCase(),
164           ret = open + tag;
165           
166         for( var a in this.DOMAttrs ){
167           var val = node[this.DOMAttrs[a]];
168           if( val ) {
169             ret += ' ' + a + '=' + this._parse( val, 'attribute' );
170           }
171         }
172         return ret + close + open + '/' + tag + close;
173       },
174       functionArgs:function( fn ){//function calls it internally, it's the arguments part of the function
175         var l = fn.length;
176         if( !l ) return '';       
177         
178         var args = new Array(l);
179         while( l-- ) args[l] = String.fromCharCode(97+l);//97 is 'a'
180         return ' ' + args.join(', ') + ' ';
181       },
182       key:quote, //object calls it internally, the key part of an item in a map
183       functionCode:'[code]', //function calls it internally, it's the content of the function
184       attribute:quote, //node calls it internally, it's an html attribute value
185       string:quote,
186       date:quote,
187       regexp:literal, //regex
188       number:literal,
189       'boolean':literal
190     },
191     DOMAttrs:{//attributes to dump from nodes, name=>realName
192       id:'id',
193       name:'name',
194       'class':'className'
195     },
196     HTML:true,//if true, entities are escaped ( <, >, \t, space and \n )
197     indentChar:'   ',//indentation unit
198     multiline:true //if true, items in a collection, are separated by a \n, else just a space.
199   };
200   
201   CoreTest.dump = function dump(obj,type) {
202     return CoreTest.jsDump.parse(obj, type);
203   };
204 
205 })();
206