src/jsdoc/Packer.vala
[roojspacker] / src / 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     
23 x.pack();  // writes files  etc..
24     
25  *</code> 
26  *
27  * Notes for improving compacting:
28  *  if you add a jsdoc comment 
29  * <code>
30  * /**
31  *   eval:var:avarname
32  *   eval:var:bvarname
33  *   ....
34  * </code>
35  * directly before an eval statement, it will compress all the code around the eval, 
36  * and not rename the variables 'avarname'
37  * 
38  * Dont try running this on a merged uncompressed large file - it's used to be horrifically slow. not sure about now..
39  * Best to use lot's of small classes, and use it to merge, as it will cache the compaction
40  * 
41  * 
42  * 
43  * Notes for translation
44  *  - translation relies on you using double quotes for strings if they need translating
45  *  - single quoted strings are ignored.
46  * 
47  * Generation of indexFiles
48  *   - translateIndex = the indexfile
49  * 
50  * 
51  * 
52  * 
53
54  */
55 namespace JSDOC 
56 {
57         public errordomain PackerError {
58             ArgumentError
59     }
60     
61     
62         public class Packer : Object 
63         {
64                 /**
65                 * @cfg {String} target to write files to - must be full path.
66                 */
67                 string target = "";
68                 GLib.FileOutputStream targetStream = null;
69                 /**
70                  * @cfg {String} debugTarget target to write files debug version to (uncompacted)- must be full path.
71                  */
72                 string targetDebug = "";
73                 
74
75                 GLib.FileOutputStream targetDebugStream  = null;
76                 /**
77                  * @cfg {String} tmpDir  (optional) where to put the temporary files. 
78                  *      if you set this, then files will not be cleaned up
79                  *  
80                  *  at present we need tmpfiles - as we compile multiple files into one.
81                  *  we could do this in memory now, as I suspect vala will not be as bad as javascript for leakage...
82                  *
83                  */
84                 //public string tmpDir = "/tmp";  // FIXME??? in ctor?
85         
86         
87                   
88                 /**
89                  * @cfg {Boolean} cleanup  (optional) clean up temp files after done - 
90                  *    Defaults to false if you set tmpDir, otherwise true.
91                  */
92                 public bool cleanup =  false;
93                 
94                  
95                 // list of files to compile...
96                 Gee.ArrayList<string> files;
97                 
98                 /**
99                 * @cfg activeFile ??? used???
100                 */
101                  
102                 public string activeFile = "";
103                 
104                         
105                 /**
106                 * @cfg baseDir -- prefix the files listed in indexfiles with this.
107                 */
108                  
109                 public string baseDir = "";
110                 
111                 
112                 public  string outstr = ""; // if no target is specified - then this will contain the result
113                 
114                 
115                 
116                 
117                 public Packer()
118                 {
119 #if HAVE_JSON_GLIB
120                         this.result = new Json.Object();
121 #else
122                         this.result_count = new  Gee.HashMap <string,int>();
123                 
124                         this.result =  new Gee.HashMap<
125                                 string /* errtype*/ , Gee.HashMap<string /*fn*/,     Gee.HashMap<int /*line*/, Gee.ArrayList<string>>>
126                         >();
127         
128 #endif                  
129                         this.files = new Gee.ArrayList<string>();
130                         
131                         new Lang_Class(); ///initilizaze lang..
132                         
133                         //this.tmp = Glib.get_tmp_dir(); // do we have to delete this?
134                         
135                          
136                 }
137                 
138                 
139                 // this could be another class really..
140                 
141                 public enum ResultType { 
142                         err , 
143                         warn;
144                         public string to_string() { 
145                                 switch(this) {
146                                         case err: return "ERR";
147                                         case warn: return "WARN";
148                                         default: assert_not_reached();
149                                 }
150                         
151                           }
152                   }
153                 /**
154                 *  result of complication - a JSON object containing warnings / errors etc..
155                 *  FORMAT:
156                 *     warn-TOTAL : X  (number of warnings.
157                 *     err-TOTAL: X  (number of errors) << this indicates failure...
158                 *     warn : {
159                 *            FILENAME : {
160                 *                  line : [ Errors,Errors,.... ]
161                 *     err : {
162                 *           .. sane format..
163                 *
164                 */
165                 
166 #if HAVE_JSON_GLIB
167                 
168                 public Json.Object result;   // output - what's the complication result
169
170                 public void  logError(ResultType type, string filename, int line, string message) {
171                          
172                          if (!this.result.has_member(type.to_string()+"-TOTAL")) {
173                                  this.result.set_int_member(type.to_string()+"-TOTAL", 1);
174                          } else {
175                                 this.result.set_int_member(type.to_string()+"-TOTAL", 
176                                         this.result.get_int_member(type.to_string()+"-TOTAL") +1 
177                                 );
178                          }
179                          
180                          
181                          if (!this.result.has_member(type.to_string())) {
182                                  this.result.set_object_member(type.to_string(), new Json.Object());
183                          }
184                          var t = this.result.get_object_member(type.to_string());
185                          if (!t.has_member(filename)) {
186                                  t.set_object_member(filename, new Json.Object());
187                          }
188                          var tt = t.get_object_member(filename);
189                          if (!tt.has_member(line.to_string())) {
190                                  tt.set_array_member(line.to_string(), new Json.Array());
191                          }
192                          var tl = tt.get_array_member(line.to_string());
193                          tl.add_string_element(message);
194                          
195                 }
196                 
197                 public bool hasErrors(string fn)
198                 {
199                          if (!this.result.has_member(ResultType.err.to_string())) {
200                                  return false;
201                          }
202                          
203                          if (fn.length < 1) {
204                                 return true;
205                          }
206                          var t = this.result.get_object_member(ResultType.err.to_string());
207                          
208                          if (t.has_member(fn)) {
209                                  return true;
210                          }
211                          return false;
212                 }
213                 public void dumpErrors(ResultType type)
214                 {
215                          if (!this.result.has_member(type.to_string())) {
216                                  return;
217                          }
218                         var t = this.result.get_object_member(type.to_string());
219                         t.foreach_member((obj, filename, node) => {
220                                         var linelist = node.dup_object();
221                                         linelist.foreach_member((linelistobj, linestr, nodear) => {
222                                                 var errors=  nodear.dup_array();
223                                                 errors.foreach_element((errorar, ignore, nodestr) => {
224                                                         print("%s: %s:%s %s\n", type.to_string(), filename, linestr, nodestr.get_string());
225                                                 });
226                                         });
227                         
228                         });
229                 }
230 #else
231                 public Gee.HashMap <string,int> result_count;   // output - what's the complication result
232                 
233                 public Gee.HashMap<
234                                 string /* errtype*/ , Gee.HashMap<string /*fn*/,     Gee.HashMap<int /*line*/, Gee.ArrayList<string>>>
235                 > result;
236
237                 public void  logError(ResultType type, string filename, int line, string message) {
238                          
239                          
240                          if (!this.result_count.has_key(type.to_string()+"-TOTAL")) {
241                                  this.result_count.set(type.to_string()+"-TOTAL", 1);
242                          } else {
243                                 this.result_count.set(type.to_string()+"-TOTAL",                                 
244                                         this.result_count.get(type.to_string()+"-TOTAL") +1
245                                 );
246                          }
247                          
248                          
249                          
250                          if (!this.result.has_key(type.to_string())) {
251                                  this.result.set(type.to_string(),
252                                          new Gee.HashMap<string /*fn*/,     Gee.HashMap<int /*line*/, Gee.ArrayList<string>>>()
253                                  );
254                          }
255                          var t = this.result.get(type.to_string());
256                          if (!t.has_key(filename)) {
257                                  t.set(filename, new  Gee.HashMap<int /*line*/, Gee.ArrayList<string>>());
258                          }
259                          var tt = t.get(filename);
260                          if (!tt.has_key(line)) {
261                                  tt.set(line, new Gee.ArrayList<string>());
262                          }
263                          var tl = tt.get(line);
264                          tl.add(message);
265                          
266                 }
267                 
268                 public bool hasErrors(string fn)
269                 {
270                          if (!this.result.has_key(ResultType.err.to_string())) {
271                                  return false;
272                          }
273                          
274                          if (fn.length < 1) {
275                                 return true;
276                          }
277                          var t = this.result.get(ResultType.err.to_string());
278                          
279                          if (t.has_key(fn)) {
280                                  return true;
281                          }
282                          return false;
283                 }
284                 public void dumpErrors(ResultType type)
285                 {
286                          if (!this.result.has_key(type.to_string())) {
287                                  return;
288                          }
289                         var t = this.result.get(type.to_string());
290                         foreach(string filename in t.keys) {
291                                 var node = t.get(filename);
292                                 foreach(int line in node.keys) {
293                                         var errors = node.get(line);
294                                         foreach(string errstr in errors) {
295                                                         print("%s: %s:%d %s\n", type.to_string(), filename, line, errstr);
296                                         }
297                                 }
298                         
299                         }
300                 }
301
302
303 #endif
304                 
305                 
306                 
307                 public void loadSourceIndexes(Gee.ArrayList<string> indexes)
308                 {
309                         foreach(var f in indexes) {
310                                 this.loadSourceIndex(f);
311                         }
312                 }
313                 
314                 public void loadFiles(string[] fs)
315                 {
316                         // fixme -- prefix baseDir?
317                         foreach(var f in fs) {
318                             GLib.debug("add File: %s", f);
319                                 this.files.add(f); //?? easier way?
320                         }
321                 }
322                 public void loadFile(string f)
323                 {
324                     // fixme -- prefix baseDir?
325                     GLib.debug("add File: %s", f);
326                         this.files.add(f); 
327                         GLib.debug("FILE LEN: %d", this.files.size);
328                 }
329                  
330                 
331                 public string pack(string target, string targetDebug = "") throws PackerError 
332                 {
333                     this.target = target;
334                         this.targetDebug  = targetDebug;
335                     
336                     if (this.files.size < 1) {
337                                 throw new PackerError.ArgumentError("No Files loaded before pack() called");
338                         }
339                         if (this.target.length > 0 ) {
340                                 this.targetStream = File.new_for_path(this.target).replace(null, false,FileCreateFlags.NONE);
341                         }
342                         if (this.targetDebug.length > 0 ) {
343                                 this.targetDebugStream = File.new_for_path(this.targetDebug).replace(null, false,FileCreateFlags.NONE);
344                         }
345                         return this.packAll();
346                 }
347                 
348   
349                  
350  
351            
352                 /**
353                  * load a dependancy list -f option
354                  * @param {String} srcfile sourcefile to parse
355                  * 
356                  */
357                 
358                 public void loadSourceIndex(string in_srcfile)
359                 {
360                     
361                     var srcfile = in_srcfile;
362                     if (srcfile[0] != '/') {
363                                 srcfile = this.baseDir + in_srcfile;
364                         }
365                     string str;
366                     FileUtils.get_contents(srcfile,out str);
367                     
368                     var lines = str.split("\n");
369                     for(var i =0; i < lines.length;i++) {
370  
371                             var f = lines[i].strip();
372                         if (f.length < 1 ||
373                                 Regex.match_simple ("^/", f) ||
374                                 !Regex.match_simple ("[a-zA-Z]+", f) 
375                         ){
376                                 continue; // blank comment or not starting with a-z
377                         }
378                         
379                         if (Regex.match_simple ("\\.js$", f)) {
380                             this.files.add( f);
381                             // js file..
382                             continue;
383                         }
384                         
385                                 // this maps Roo.bootstrap.XXX to Roo/bootstrap/xxx.js
386                                 // should we prefix? =- or should this be done elsewhere?
387                                 
388                         var add = f.replace(".", "/") + ".js";
389                         
390                         if (add[0] != '/') {
391                                         add = this.baseDir + add;
392                                 }
393                         
394                         if (this.files.contains(add)) {
395                             continue;
396                         }
397                         
398                         
399                         
400                         this.files.add( add );
401                         
402                     }
403                 }
404                 
405     
406                 private string packAll()   // do the packing (run from constructor)
407                 {
408                     
409                     //this.transOrigFile= bpath + '/../lang.en.js'; // needs better naming...
410                     //File.write(this.transfile, "");
411                     if (this.target.length > 0) {
412                         this.targetStream.write("".data);
413                     }
414                     
415                     if (this.targetDebugStream != null) {
416                             this.targetDebugStream.write("".data);
417                     }
418                     
419                     
420                     var tmpDir = GLib.DirUtils.make_tmp("roojspacker_XXXXXX");
421                     
422                     foreach(var file in this.files) {
423                         
424                         print("reading %s\n",file );
425                         
426                         if (!FileUtils.test (file, FileTest.EXISTS) || FileUtils.test (file, FileTest.IS_DIR)) {
427                             print("SKIP (is not a file) %s\n ", file);
428                             continue;
429                         }
430                        
431                                 var loaded_string = false;
432                                 string file_contents = "";
433                         // debug Target
434                         
435                         if (this.targetDebugStream !=null) {
436                                 
437                                 FileUtils.get_contents(file,out file_contents);
438                             this.targetDebugStream.write(file_contents.data);
439                             loaded_string = false;
440                         }
441                         // it's a good idea to check with 0 compression to see if the code can parse!!
442                         
443                         // debug file..
444                         //File.append(dout, str +"\n"); 
445                         
446                    
447                         
448                         var minfile = tmpDir + "/" + file.replace("/", ".");
449                         
450                         
451                         // let's see if we have a min file already?
452                         // this might happen if tmpDir is set .. 
453
454                         
455                         if ( FileUtils.test (minfile, FileTest.EXISTS)) {
456                                  
457                                 var otv = File.new_for_path(file).query_info (FileAttribute.TIME_MODIFIED, 0).get_modification_time();
458                                 var mtv = File.new_for_path(minfile).query_info (FileAttribute.TIME_MODIFIED, 0).get_modification_time();
459                                         
460                                          
461                            // print("%s : compare : Cache file  %s to Orignal Time %s\n", file, mtv.to_iso8601(), otv.to_iso8601());
462                             if (mtv.tv_usec > otv.tv_usec) {
463                                 continue; // file is newer or the same time..
464                                 
465                             }
466                             
467                         }
468                          
469                         print("COMPRESSING to %s\n", minfile);
470                         //var codeComp = pack(str, 10, 0, 0);
471                         if (this.cleanup && FileUtils.test (minfile, FileTest.EXISTS)) {
472                             FileUtils.remove(minfile);
473                         }
474                         if (!loaded_string) {
475                                 FileUtils.get_contents(file,out file_contents);
476                         }
477
478                          this.packFile(file_contents, file, minfile);
479                          
480                       
481                     }
482                     
483                     // at this point if we have errors, we should stop..
484
485                                             
486                         this.dumpErrors(ResultType.warn);
487                         this.dumpErrors(ResultType.err); // since they are fatal - display them last...
488                         
489                         
490                         
491                         
492                         if (PackerRun.opt_dump_tokens || this.hasErrors("")) {
493                                  
494                                 GLib.Process.exit(0);
495                         }
496                     print("MERGING SOURCE\n");
497                     
498                     for(var i=0; i < this.files.size; i++)  {
499                         var file = this.files[i];
500                         var minfile = tmpDir + "/" + file.replace("/", ".");
501                         
502                         
503                         if ( !FileUtils.test(minfile, FileTest.EXISTS)) {
504                                 print("skipping source %s - does not exist\n", minfile);
505                             continue;
506                         }
507                         string str;
508                         FileUtils.get_contents(minfile, out str);
509                         print("using MIN FILE  %s\n", minfile);
510                         if (str.length > 0) {
511                             if (this.targetStream != null) {
512                                         this.targetStream.write(("// " + 
513                                                 ( (file.length > this.baseDir.length) ? file.substring(this.baseDir.length)  : file ) + 
514                                                 "\n").data); 
515                                         this.targetStream.write((str + "\n").data); 
516
517                             } else {
518                                 this.outstr += "//" + 
519                                         ( (file.length > this.baseDir.length) ? file.substring(this.baseDir.length)  : file ) +  "\n";
520                                 this.outstr += str + "\n";
521                             }
522                             
523                         }
524                         if (this.cleanup) {
525                             FileUtils.remove(minfile);
526                         }
527                         
528                     }
529                     if (this.cleanup) {
530                                 FileUtils.remove(tmpDir);
531                         }
532                     
533                     if (this.target.length > 0 ) {
534                             print("Output file: " + this.target);
535                     }
536                     if (this.targetDebug.length > 0) {
537                                  print("Output debug file: %s\n" , this.targetDebug);
538                         }
539             
540
541             
542                         // OUTPUT should be handled by PackerRun (so that this can be used as a library...)
543                         if (this.outstr.length > 0 ) {
544                 return this.outstr;
545                         //      stdout.printf ("%s", this.outstr);
546                         }
547                     return "";
548                 
549                 
550                 }
551                 /**
552                  * Core packing routine  for a file
553                  * 
554                  * @param str - str source text..
555                  * @param fn - filename (for reference?)
556                  * @param minfile - min file location...
557                  * 
558                  */
559
560                 public  string packFile  (string str,string fn, string minfile)  
561                 {
562
563                         var tr = new  TokenReader(this);
564                         tr.keepDocs =true;
565                         tr.keepWhite = true;
566                         tr.keepComments = true;
567                         tr.sepIdents = true;
568                         tr.collapseWhite = false;
569                         tr.filename = fn;
570  
571                         // we can load translation map here...
572                 
573                         TokenArray toks = tr.tokenize(new TextStream(str)); // dont merge xxx + . + yyyy etc.
574                 
575                         if (PackerRun.opt_dump_tokens) {
576                                 toks.dump();
577                                 return "";
578                                 //GLib.Process.exit(0);
579                         }
580                 
581                         this.activeFile = fn;
582                 
583                         // and replace if we are generating a different language..
584                 
585
586                         //var ts = new TokenStream(toks);
587                         //print(JSON.stringify(toks, null,4 )); Seed.quit();
588                         var ts = new Collapse(toks.tokens, this, fn);
589                         
590                         //ts.dumpAll("");                       print("Done collaps"); Process.exit(1);
591                         
592                    // print(JSON.stringify(ts.tokens, null,4 )); Seed.quit();
593                         //return;//
594                         if (!PackerRun.opt_skip_scope) {
595                                 var sp = new ScopeParser(ts, this, fn);
596  
597                                 //sp.packer = this;
598                                 sp.buildSymbolTree();
599                                 sp.mungeSymboltree();
600                         
601                         
602                                 sp.printWarnings();
603                         }
604                         
605                         
606                         //print(sp.warnings.join("\n"));
607                         //(new TokenStream(toks.tokens)).dumpAll(""); GLib.Process.exit(1);
608                         // compress works on the original array - in theory the replacements have already been done by now 
609                         var outf = CompressWhite(new TokenStream(toks.tokens), this, PackerRun.opt_keep_whitespace); // do not kill whitespace..
610                 
611                         
612                         debug("RESULT: \n %s\n", outf);
613                         
614                         
615                         
616                         if (outf.length > 0 && minfile.length > 0 && !this.hasErrors(fn)) {
617                                 FileUtils.set_contents(minfile, outf);
618                                  
619                         }  
620
621                 
622                         return outf;
623                 
624                 
625                          
626                 }
627                  
628
629                 public string md5(string str)
630                 {
631                 
632                         return GLib.Checksum.compute_for_string(GLib.ChecksumType.MD5, str);
633                 
634                 }
635     
636          //stringHandler : function(tok) -- not used...
637     }
638     
639 }