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