JSDOC/Packer.vala
[gnome.introspection-doc-generator] / JSDOC / Packer.vala
1  
2 /**
3  * @namespace JSDOC
4  * @class  Packer
5  * Create a new packer
6  * 
7  * Use with pack.js 
8  * 
9  * 
10  * Usage:
11  * <code>
12  *
13  
14 var x = new  JSON.Packer(target, debugTarget);
15
16 x.files = an array of files
17 x.srcfiles = array of files (that list other files...) << not supported?
18 x.target = "output.pathname.js"
19 x.debugTarget = "output.pathname.debug.js"
20
21
22 x.debugTranslateTarget : "/tmp/output.translate.js" << this used to be the single vs double quotes.. we may not use it in future..
23 x.translateJSON: "/tmp/translate.json",
24     
25 x.packAll();  // writes files  etc..
26     
27  *</code> 
28  *
29  * Notes for improving compacting:
30  *  if you add a jsdoc comment 
31  * <code>
32  * /**
33  *   eval:var:avarname
34  *   eval:var:bvarname
35  *   ....
36  * </code>
37  * directly before an eval statement, it will compress all the code around the eval, 
38  * and not rename the variables 'avarname'
39  * 
40  * Dont try running this on a merged uncompressed large file - it's used to be horrifically slow. not sure about now..
41  * Best to use lot's of small classes, and use it to merge, as it will cache the compaction
42  * 
43  * 
44  * 
45  * Notes for translation
46  *  - translation relies on you using double quotes for strings if they need translating
47  *  - single quoted strings are ignored.
48  * 
49  * Generation of indexFiles
50  *   - translateIndex = the indexfile
51  * 
52  * 
53  * 
54  * 
55
56  */
57 namespace JSDOC 
58 {
59
60
61         public class Packer : Object 
62         {
63                 /**
64                 * @cfg {String} target to write files to - must be full path.
65                 */
66                 string target;
67                 /**
68                  * @cfg {String} debugTarget target to write files debug version to (uncompacted)- must be full path.
69                  */
70                 string debugTarget;
71         
72                 /**
73                  * @cfg {String} tmpDir  (optional) where to put the temporary files. 
74                  *      if you set this, then files will not be cleaned up
75                  */
76                 public string tmpDir = "/tmp";  // FIXME??? in ctor?
77         
78         
79                   
80                 /**
81                  * @cfg {Boolean} cleanup  (optional) clean up temp files after done - 
82                  *    Defaults to false if you set tmpDir, otherwise true.
83                  */
84                 public bool cleanup =  true;
85                 
86                 
87                 /**
88                  * @cfg {Boolean} keepWhite (optional) do not remove white space in output.
89                  *    usefull for debugging compressed files.
90                  */
91                 
92                 public bool keepWhite =  true;
93                 
94                 
95                 // list of files to compile...
96                 Gee.ArrayList<string> files;
97                 
98                 public  string out = ""; // if no target is specified - then this will contain the result
99     
100                 public Packer(string target, string debugTarget)
101                 {
102                         this.target = target;
103                         this.debugTarget  = debugTarget;
104                 
105                 }
106                 
107                 public void loadSourceIndexes(Gee.ArrayList<string> indexes)
108                 {
109                         foreach(var f in indexes) {
110                                 this.loadSourceIndex(f);
111                         }
112                 }
113                 
114                 public void loadFiles(Gee.ArrayList<string> fs)
115                 {
116                         foreach(var f in fs) {
117                                 this.files.add(f); //?? easier way?
118                         }
119                 }
120                 
121                 public void pack()
122                 {
123                     if (!this.files) {
124                                 throw new Packer.ArgumentError("No Files loaded before pack() called");
125                         }
126                         this.packAll();
127                 }
128                 
129   
130                 
131                 
132    
133                 
134  
135            
136                 /**
137                  * load a dependancy list -f option
138                  * @param {String} srcfile sourcefile to parse
139                  * 
140                  */
141                 
142                 public void loadSourceIndex(string srcfile)
143                 {
144                     string str;
145                     FileUtils.get_contents(srcfile,out str);
146                     
147                     var lines = str.split("\n");
148                     for(var i =0; i < lines.length;i++) {
149  
150                             var f = lines[i].strip();
151                         if (Regex.match_simple ("^\s*\/", f) ||
152                                 !Regex.match_simple ("[a-zA-Z]+", f) 
153                         ){
154                                 continue; // blank or not starting with a-z
155                         }
156                         
157                         if (Regex.match_simple ("\.js$", f)) {
158                             this.files.add( f);
159                             // js file..
160                             continue;
161                         }
162                         
163                                 // this maps Roo.bootstrap.XXX to Roo/bootstrap/xxx.js
164                                 
165                                 
166                         var add = f.replace(".", "/").replace(/\s+/g,'')+'.js';
167                         if (_this.files.indexOf(f) > -1) {
168                             return;
169                         }
170                         _this.files.push( add );
171                         
172                     })
173                 },
174     
175     
176     packAll : function()  // do the packing (run from constructor)
177     {
178         
179         //this.transOrigFile= bpath + '/../lang.en.js'; // needs better naming...
180         //File.write(this.transfile, "");
181         if (this.target) {
182             File.write(this.target, "");
183         }
184         
185         if (this.debugTarget) {
186             File.write(this.debugTarget, "");
187         }
188         if (this.debugTranslateTarget) {
189             File.write(this.debugTarget, "");
190         }
191         
192         for(var i=0; i < this.files.length; i++)  {
193             var file = this.files[i];
194             
195             print("reading " +file );
196             if (!File.isFile(file)) {
197                 print("SKIP (is not a file) " + file);
198                 continue;
199             }
200            
201             // debug Target
202             
203             if (this.debugTarget) {
204                 File.append(this.debugTarget, File.read(file));
205             }
206             // it's a good idea to check with 0 compression to see if the code can parse!!
207             
208             // debug file..
209             //File.append(dout, str +"\n"); 
210             
211        
212             
213             var minfile = this.tmpDir + '/' +file.replace(/\//g, '.');
214             
215             
216             // let's see if we have a min file already?
217             // this might happen if tmpDir is set .. 
218             if (true && File.exists(minfile)) {
219                 var mt = File.mtime(minfile);
220                 var ot = File.mtime(file);
221                 print("compare : " + mt + "=>" + ot);
222                 if (mt >= ot) {
223                     continue;
224                     
225                 }
226                 
227             }
228              
229             print("COMPRESSING ");
230             //var codeComp = pack(str, 10, 0, 0);
231             if (File.exists(minfile)) {
232                 File.remove(minfile);
233             }
234             var str = File.read(file);
235             var str = this.pack(str, file, minfile);
236              
237           
238         }
239         
240         
241         
242         // if we are translating, write the translations strings at the top
243         // of the file..
244         
245         if (this.translateJSON) {
246             
247                
248             print("MERGING LANGUAGE");
249             var out = "if (typeof(_T) == 'undefined') { _T={};}\n"
250             if (this.target) {
251                 File.write(this.target, out);
252             } else {
253                 this.out += out;
254             }
255              
256             File.write(this.translateJSON, "");
257             for(var i=0; i < this.files.length; i++)  {
258                 var file = this.files[i];
259                 var transfile= this.tmpDir + '/' +file.replace(/\//g, '.') +'.lang.trans';
260                 var transmd5 = this.tmpDir  + '/' +file.replace(/\//g, '.') +'.lang';
261                 if (File.exists(transmd5)) {
262                     var str = File.read(transmd5);
263                     if (str.length) {
264                         if (this.target) {
265                             File.append(this.target, str + "\n");
266                         } else {
267                             this.out += str + "\n";
268                         }
269                         
270                     }
271                     if (this.cleanup) {
272                         File.remove(transmd5);
273                     }
274                 }
275                 if (File.exists(transfile)) {
276                     var str = File.read(transfile);
277                     if (str.length) {
278                         File.append(this.translateJSON, str);
279                     }
280                     if (this.cleanup) {
281                         File.remove(transfile);
282                     }
283                 }
284                 
285                
286             }
287         }
288         
289         print("MERGING SOURCE");
290         
291         for(var i=0; i < this.files.length; i++)  {
292             var file = this.files[i];
293             var minfile = this.tmpDir + '/' + file.replace(/\//g, '.');
294             
295             
296             if (!File.exists(minfile)) {
297                 continue;
298             }
299             var str = File.read(minfile);
300             print("using MIN FILE  "+ minfile);
301             if (str.length) {
302                 if (this.target) {
303                     File.append(this.target, '//' + file + "\n");   
304                     File.append(this.target, str + "\n");   
305                 } else {
306                     this.out += '//' + file + "\n";
307                     this.out += str + "\n";
308                 }
309                 
310             }
311             if (this.cleanup) {
312                 File.remove(minfile);
313             }
314             
315         }
316         print("Output file: " + this.target);
317         if (this.debugTarget) print("Output debug file: " + this.debugTarget);
318         
319          
320     
321     
322     },
323     /**
324      * Core packing routine  for a file
325      * 
326      * @param str - str source text..
327      * @param fn - filename (for reference?)
328      * @param minfile - min file location...
329      * 
330      */
331     
332     pack : function (str,fn,minfile)
333     {
334     
335         var tr = new  TokenReader(  { 
336             keepDocs :true, 
337             keepWhite : true,  
338             keepComments : true, 
339             sepIdents : true,
340             collapseWhite : false,
341             filename : fn
342         });
343         this.timerPrint("START" + fn);
344         
345         // we can load translation map here...
346         
347         var toks = tr.tokenize(new TextStream(str)); // dont merge xxx + . + yyyy etc.
348         
349         // at this point we can write a language file...
350         if (this.translateJSON) {
351             
352             this.writeTranslateFile(fn, minfile, toks);
353         }
354         
355         this.activeFile = fn;
356         
357         // and replace if we are generating a different language..
358         
359         this.timerPrint("Tokenized");
360         //var ts = new TokenStream(toks);
361         //print(JSON.stringify(toks, null,4 )); Seed.quit();
362         var ts = new Collapse(toks);
363        // print(JSON.stringify(ts.tokens, null,4 )); Seed.quit();
364         //return;//
365         var sp = new ScopeParser(ts);
366         this.timerPrint("Converted to Parser");
367         sp.packer = this;
368         sp.buildSymbolTree();
369         this.timerPrint("Built Sym tree");
370         sp.mungeSymboltree();
371         this.timerPrint("Munged Sym tree");
372         print(sp.warnings.join("\n"));
373         this.timerPrint("Compressed");
374         
375         var out = CompressWhite(new TokenStream(toks), this, this.keepWhite); // do not kill whitespace..
376         
377         
378         this.timerPrint("Compressed");
379         
380          if (out.length) {
381             File.write(minfile, out);
382             this.timerPrint("Write (" + out.length + "bytes) " + minfile);
383         }
384         
385         return out;
386         
387         
388          
389     },
390     
391     timerPrint: function (str) {
392         var ntime = new Date() * 1;
393         var tdif =  ntime -this.timer;
394         this.timer = ntime;
395         print('['+tdif+']'+str);
396     },
397     
398     /**
399      * 
400      * Translation concept...
401      * -> replace text strings with _T....
402      * -> this file will need inserting at the start of the application....
403      * -> we need to generate 2 files, 
404      * -> a reference used to do the translation, and the _T file..
405      *
406      *
407      * We store the trsum on the token...
408      * 
409      */
410     
411     writeTranslateFile : function(fn, minfile, toks) 
412     {
413         
414         var map = {};  // 'string=> md5sum'
415         var _this = this;
416         var t, last, next;
417         
418         
419         var tokfind =  function (j,dir) {
420             while (1) {
421                 if ((dir < 0) && (j < 0)) {
422                     return false;
423                 }
424                 if ((dir > 0) && (j >= toks.length)) {
425                     return false;
426                 }
427                 j += dir;
428                 if (toks[j].type != 'WHIT') {
429                     return toks[j];
430                 }
431             }
432             return false;
433             
434         }
435         
436         
437         for (var i=0;i<toks.length;i++) {
438             
439             t = toks[i];
440             if (t.type != 'STRN') {
441                 continue;
442             }
443             if (t.name != 'DOUBLE_QUOTE') {
444                 continue;
445             }
446             
447             last = tokfind(i,-1);
448             next = tokfind(i,+1);
449             
450             // we have to ignore key values on objects
451             
452             // defined by
453             // last == '{' or ',' and
454             // next == ':'
455             
456             if (next &&
457                 next.type == 'PUNC' &&
458                 next.data == ':' && 
459                 last && 
460                 last.type == 'PUNC' &&
461                 (last.data == ',' || last.data == '{')
462             ){
463                 continue; // found object key... - we can not translate these
464             }
465                 
466             var sval = t.data.substring(1,t.data.length-1);
467             var ffn = fn.substring(_this.prefix.length);
468             
469             t.trsum = _this.md5(ffn + '-' + sval);
470             map[sval] = t.trsum;
471             
472             
473             
474         }
475         
476         
477         var transfile = minfile + '.lang.trans';
478         var transmd5 = minfile + '.lang';
479         print("writeTranslateFile "  + transfile);
480         var i = 0;
481         var v = '';
482         if (File.exists(transfile)) {
483             File.remove(transfile);
484         }
485         if (File.exists(transmd5)) {
486             File.remove(transmd5);
487         }
488         for(v in map) { i++; break };
489         if (!i ) {
490             return; // no strings in file...
491         }
492         var ffn = fn.substring(this.prefix.length);
493          
494          
495         File.write(transfile, "\n'" + ffn  + "' : {");
496         var l = '';
497         var _tout = {}
498          
499         File.write(transmd5, '');
500         for(v in map) {
501             if (!v.length) {
502                 continue;
503             }
504             File.append(transfile, l + "\n\t\"" + v  + "\" : \"" + v +"\"");
505             l = ',';
506             // strings are raw... - as the where encoded to start with!!!
507             // so we should not need to encode them again.. - just wrap with "
508             File.append(transmd5, '_T["' + this.md5(ffn + '-' + v) + '"]="'+v+"\";\n");
509         }
510         File.append(transfile, "\n},"); // always one trailing..
511         
512          
513     },
514     md5 : function (string)
515     {
516         
517         return GLib.compute_checksum_for_string(GLib.ChecksumType.MD5, string, string.length);
518         
519     },
520     stringHandler : function(tok)
521     {
522         //print("STRING HANDLER");
523        // callback when outputing compressed file, 
524        var data = tok.data;
525         if (!this.translateJSON) {
526          //   print("TURNED OFF");
527             return data;
528         }
529         if (tok.name == 'SINGLE_QUOTE') {
530             return data;
531         }
532         
533         if (typeof(tok.trsum) == 'undefined') {
534             return data;
535         }
536         
537         return '_T["' + tok.trsum + '"]';
538         
539         var sval = data.substring(1,data.length-1);
540         // we do not clean up... quoting here!??!!?!?!?!?
541         
542         
543         // blank with tabs or spaces..
544         //if (!sval.replace(new RegExp("(\\\\n|\\\\t| )+",'g'), '').length) {
545        //     return tok.outData;
546        // }
547         
548         var sval = tok.data.substring(1,data.length-1);
549         var fn = this.activeFile.substring(this.prefix.length);
550         
551         
552         return '_T["' + this.md5(fn + '-' + sval) + '"]';
553         
554         
555     }
556     
557     
558 };