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