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 Packer(string target, string debugTarget)
99                 {
100                         this.target = target;
101                         this.debugTarget  = debugTarget;
102                 
103                 }
104                 
105                 public void loadSourceIndexes(Gee.ArrayList<string> indexes)
106                 {
107                         foreach(var f in indexes) {
108                                 this.loadSourceIndex(f);
109                         }
110                 }
111                 
112                 public void loadFiles(Gee.ArrayList<string> fs)
113                 {
114                         foreach(var f in fs) {
115                                 this.files.add(f); //?? easier way?
116                         }
117                 }
118                 
119                 public void pack()
120                 {
121                     if (!this.files) {
122                                 throw new Packer.ArgumentError("No Files loaded before pack() called");
123                         }
124                         this.packAll();
125                 }
126                 
127   
128                 
129                 
130    
131                 
132     
133     /**
134      * @cfg {String} prefix (optional) prefix of directory to be stripped of when
135      *    Calculating md5 of filename 
136      */
137     prefix : '',  
138     out : '', // if no target is specified - then this will contain the result
139     
140     /**
141      * load a dependancy list -f option
142      * @param {String} srcfile sourcefile to parse
143      * 
144      */
145     
146     loadSourceIndexes : function(srcfile)
147     {
148         var lines = File.read(srcfile).split("\n");
149         var _this = this;
150         lines.forEach(function(f) {
151             
152             if (/^\s*\//.test(f) || !/[a-z]+/i.test(f)) { // skip comments..
153                 return;
154             }
155             if (/\.js$/.test(f)) {
156                 _this.files.push( f);
157                 // js file..
158                 return;
159             }
160             
161             //println("ADD"+ f.replace(/\./g, '/'));
162             var add = f.replace(/\./g, '/').replace(/\s+/g,'')+'.js';
163             if (_this.files.indexOf(f) > -1) {
164                 return;
165             }
166             _this.files.push( add );
167             
168         })
169     },
170     
171     
172     packAll : function()  // do the packing (run from constructor)
173     {
174         
175         //this.transOrigFile= bpath + '/../lang.en.js'; // needs better naming...
176         //File.write(this.transfile, "");
177         if (this.target) {
178             File.write(this.target, "");
179         }
180         
181         if (this.debugTarget) {
182             File.write(this.debugTarget, "");
183         }
184         if (this.debugTranslateTarget) {
185             File.write(this.debugTarget, "");
186         }
187         
188         for(var i=0; i < this.files.length; i++)  {
189             var file = this.files[i];
190             
191             print("reading " +file );
192             if (!File.isFile(file)) {
193                 print("SKIP (is not a file) " + file);
194                 continue;
195             }
196            
197             // debug Target
198             
199             if (this.debugTarget) {
200                 File.append(this.debugTarget, File.read(file));
201             }
202             // it's a good idea to check with 0 compression to see if the code can parse!!
203             
204             // debug file..
205             //File.append(dout, str +"\n"); 
206             
207        
208             
209             var minfile = this.tmpDir + '/' +file.replace(/\//g, '.');
210             
211             
212             // let's see if we have a min file already?
213             // this might happen if tmpDir is set .. 
214             if (true && File.exists(minfile)) {
215                 var mt = File.mtime(minfile);
216                 var ot = File.mtime(file);
217                 print("compare : " + mt + "=>" + ot);
218                 if (mt >= ot) {
219                     continue;
220                     
221                 }
222                 
223             }
224              
225             print("COMPRESSING ");
226             //var codeComp = pack(str, 10, 0, 0);
227             if (File.exists(minfile)) {
228                 File.remove(minfile);
229             }
230             var str = File.read(file);
231             var str = this.pack(str, file, minfile);
232              
233           
234         }
235         
236         
237         
238         // if we are translating, write the translations strings at the top
239         // of the file..
240         
241         if (this.translateJSON) {
242             
243                
244             print("MERGING LANGUAGE");
245             var out = "if (typeof(_T) == 'undefined') { _T={};}\n"
246             if (this.target) {
247                 File.write(this.target, out);
248             } else {
249                 this.out += out;
250             }
251              
252             File.write(this.translateJSON, "");
253             for(var i=0; i < this.files.length; i++)  {
254                 var file = this.files[i];
255                 var transfile= this.tmpDir + '/' +file.replace(/\//g, '.') +'.lang.trans';
256                 var transmd5 = this.tmpDir  + '/' +file.replace(/\//g, '.') +'.lang';
257                 if (File.exists(transmd5)) {
258                     var str = File.read(transmd5);
259                     if (str.length) {
260                         if (this.target) {
261                             File.append(this.target, str + "\n");
262                         } else {
263                             this.out += str + "\n";
264                         }
265                         
266                     }
267                     if (this.cleanup) {
268                         File.remove(transmd5);
269                     }
270                 }
271                 if (File.exists(transfile)) {
272                     var str = File.read(transfile);
273                     if (str.length) {
274                         File.append(this.translateJSON, str);
275                     }
276                     if (this.cleanup) {
277                         File.remove(transfile);
278                     }
279                 }
280                 
281                
282             }
283         }
284         
285         print("MERGING SOURCE");
286         
287         for(var i=0; i < this.files.length; i++)  {
288             var file = this.files[i];
289             var minfile = this.tmpDir + '/' + file.replace(/\//g, '.');
290             
291             
292             if (!File.exists(minfile)) {
293                 continue;
294             }
295             var str = File.read(minfile);
296             print("using MIN FILE  "+ minfile);
297             if (str.length) {
298                 if (this.target) {
299                     File.append(this.target, '//' + file + "\n");   
300                     File.append(this.target, str + "\n");   
301                 } else {
302                     this.out += '//' + file + "\n";
303                     this.out += str + "\n";
304                 }
305                 
306             }
307             if (this.cleanup) {
308                 File.remove(minfile);
309             }
310             
311         }
312         print("Output file: " + this.target);
313         if (this.debugTarget) print("Output debug file: " + this.debugTarget);
314         
315          
316     
317     
318     },
319     /**
320      * Core packing routine  for a file
321      * 
322      * @param str - str source text..
323      * @param fn - filename (for reference?)
324      * @param minfile - min file location...
325      * 
326      */
327     
328     pack : function (str,fn,minfile)
329     {
330     
331         var tr = new  TokenReader(  { 
332             keepDocs :true, 
333             keepWhite : true,  
334             keepComments : true, 
335             sepIdents : true,
336             collapseWhite : false,
337             filename : fn
338         });
339         this.timerPrint("START" + fn);
340         
341         // we can load translation map here...
342         
343         var toks = tr.tokenize(new TextStream(str)); // dont merge xxx + . + yyyy etc.
344         
345         // at this point we can write a language file...
346         if (this.translateJSON) {
347             
348             this.writeTranslateFile(fn, minfile, toks);
349         }
350         
351         this.activeFile = fn;
352         
353         // and replace if we are generating a different language..
354         
355         this.timerPrint("Tokenized");
356         //var ts = new TokenStream(toks);
357         //print(JSON.stringify(toks, null,4 )); Seed.quit();
358         var ts = new Collapse(toks);
359        // print(JSON.stringify(ts.tokens, null,4 )); Seed.quit();
360         //return;//
361         var sp = new ScopeParser(ts);
362         this.timerPrint("Converted to Parser");
363         sp.packer = this;
364         sp.buildSymbolTree();
365         this.timerPrint("Built Sym tree");
366         sp.mungeSymboltree();
367         this.timerPrint("Munged Sym tree");
368         print(sp.warnings.join("\n"));
369         this.timerPrint("Compressed");
370         
371         var out = CompressWhite(new TokenStream(toks), this, this.keepWhite); // do not kill whitespace..
372         
373         
374         this.timerPrint("Compressed");
375         
376          if (out.length) {
377             File.write(minfile, out);
378             this.timerPrint("Write (" + out.length + "bytes) " + minfile);
379         }
380         
381         return out;
382         
383         
384          
385     },
386     
387     timerPrint: function (str) {
388         var ntime = new Date() * 1;
389         var tdif =  ntime -this.timer;
390         this.timer = ntime;
391         print('['+tdif+']'+str);
392     },
393     
394     /**
395      * 
396      * Translation concept...
397      * -> replace text strings with _T....
398      * -> this file will need inserting at the start of the application....
399      * -> we need to generate 2 files, 
400      * -> a reference used to do the translation, and the _T file..
401      *
402      *
403      * We store the trsum on the token...
404      * 
405      */
406     
407     writeTranslateFile : function(fn, minfile, toks) 
408     {
409         
410         var map = {};  // 'string=> md5sum'
411         var _this = this;
412         var t, last, next;
413         
414         
415         var tokfind =  function (j,dir) {
416             while (1) {
417                 if ((dir < 0) && (j < 0)) {
418                     return false;
419                 }
420                 if ((dir > 0) && (j >= toks.length)) {
421                     return false;
422                 }
423                 j += dir;
424                 if (toks[j].type != 'WHIT') {
425                     return toks[j];
426                 }
427             }
428             return false;
429             
430         }
431         
432         
433         for (var i=0;i<toks.length;i++) {
434             
435             t = toks[i];
436             if (t.type != 'STRN') {
437                 continue;
438             }
439             if (t.name != 'DOUBLE_QUOTE') {
440                 continue;
441             }
442             
443             last = tokfind(i,-1);
444             next = tokfind(i,+1);
445             
446             // we have to ignore key values on objects
447             
448             // defined by
449             // last == '{' or ',' and
450             // next == ':'
451             
452             if (next &&
453                 next.type == 'PUNC' &&
454                 next.data == ':' && 
455                 last && 
456                 last.type == 'PUNC' &&
457                 (last.data == ',' || last.data == '{')
458             ){
459                 continue; // found object key... - we can not translate these
460             }
461                 
462             var sval = t.data.substring(1,t.data.length-1);
463             var ffn = fn.substring(_this.prefix.length);
464             
465             t.trsum = _this.md5(ffn + '-' + sval);
466             map[sval] = t.trsum;
467             
468             
469             
470         }
471         
472         
473         var transfile = minfile + '.lang.trans';
474         var transmd5 = minfile + '.lang';
475         print("writeTranslateFile "  + transfile);
476         var i = 0;
477         var v = '';
478         if (File.exists(transfile)) {
479             File.remove(transfile);
480         }
481         if (File.exists(transmd5)) {
482             File.remove(transmd5);
483         }
484         for(v in map) { i++; break };
485         if (!i ) {
486             return; // no strings in file...
487         }
488         var ffn = fn.substring(this.prefix.length);
489          
490          
491         File.write(transfile, "\n'" + ffn  + "' : {");
492         var l = '';
493         var _tout = {}
494          
495         File.write(transmd5, '');
496         for(v in map) {
497             if (!v.length) {
498                 continue;
499             }
500             File.append(transfile, l + "\n\t\"" + v  + "\" : \"" + v +"\"");
501             l = ',';
502             // strings are raw... - as the where encoded to start with!!!
503             // so we should not need to encode them again.. - just wrap with "
504             File.append(transmd5, '_T["' + this.md5(ffn + '-' + v) + '"]="'+v+"\";\n");
505         }
506         File.append(transfile, "\n},"); // always one trailing..
507         
508          
509     },
510     md5 : function (string)
511     {
512         
513         return GLib.compute_checksum_for_string(GLib.ChecksumType.MD5, string, string.length);
514         
515     },
516     stringHandler : function(tok)
517     {
518         //print("STRING HANDLER");
519        // callback when outputing compressed file, 
520        var data = tok.data;
521         if (!this.translateJSON) {
522          //   print("TURNED OFF");
523             return data;
524         }
525         if (tok.name == 'SINGLE_QUOTE') {
526             return data;
527         }
528         
529         if (typeof(tok.trsum) == 'undefined') {
530             return data;
531         }
532         
533         return '_T["' + tok.trsum + '"]';
534         
535         var sval = data.substring(1,data.length-1);
536         // we do not clean up... quoting here!??!!?!?!?!?
537         
538         
539         // blank with tabs or spaces..
540         //if (!sval.replace(new RegExp("(\\\\n|\\\\t| )+",'g'), '').length) {
541        //     return tok.outData;
542        // }
543         
544         var sval = tok.data.substring(1,data.length-1);
545         var fn = this.activeFile.substring(this.prefix.length);
546         
547         
548         return '_T["' + this.md5(fn + '-' + sval) + '"]';
549         
550         
551     }
552     
553     
554 };