text: #! /usr/bin/env node
  // -*- js -*-
  
  global.sys = require(/^v0\.[012]/.test(process.version) ? "sys" : "util");
  var fs = require("fs");
  var uglify = require("uglify-js"), // symlink ~/.node_libraries/uglify-js.js to ../uglify-js.js
      jsp = uglify.parser,
      pro = uglify.uglify;
  
  var options = {
          ast: false,
          mangle: true,
          mangle_toplevel: false,
          squeeze: true,
          make_seqs: true,
          dead_code: true,
          verbose: false,
          show_copyright: true,
          out_same_file: false,
          max_line_length: 32 * 1024,
          unsafe: false,
          reserved_names: null,
          defines: { },
          codegen_options: {
                  ascii_only: false,
                  beautify: false,
                  indent_level: 4,
                  indent_start: 0,
                  quote_keys: false,
                  space_colon: false,
                  inline_script: false
          },
          make: false,
          output: true            // stdout
  };
  
  var args = jsp.slice(process.argv, 2);
  var filename;
  
  out: while (args.length > 0) {
          var v = args.shift();
          switch (v) {
              case "-b":
              case "--beautify":
                  options.codegen_options.beautify = true;
                  break;
              case "-i":
              case "--indent":
                  options.codegen_options.indent_level = args.shift();
                  break;
              case "-q":
              case "--quote-keys":
                  options.codegen_options.quote_keys = true;
                  break;
              case "-mt":
              case "--mangle-toplevel":
                  options.mangle_toplevel = true;
                  break;
              case "--no-mangle":
              case "-nm":
                  options.mangle = false;
                  break;
              case "--no-squeeze":
              case "-ns":
                  options.squeeze = false;
                  break;
              case "--no-seqs":
                  options.make_seqs = false;
                  break;
              case "--no-dead-code":
                  options.dead_code = false;
                  break;
              case "--no-copyright":
              case "-nc":
                  options.show_copyright = false;
                  break;
              case "-o":
              case "--output":
                  options.output = args.shift();
                  break;
              case "--overwrite":
                  options.out_same_file = true;
                  break;
              case "-v":
              case "--verbose":
                  options.verbose = true;
                  break;
              case "--ast":
                  options.ast = true;
                  break;
              case "--unsafe":
                  options.unsafe = true;
                  break;
              case "--max-line-len":
                  options.max_line_length = parseInt(args.shift(), 10);
                  break;
              case "--reserved-names":
                  options.reserved_names = args.shift().split(",");
                  break;
              case "-d":
              case "--define":
                   var defarg = args.shift();
                   try {
                       var defsym = function(sym) {
                               // KEYWORDS_ATOM doesn't include NaN or Infinity - should we check
                               // for them too ?? We don't check reserved words and the like as the
                               // define values are only substituted AFTER parsing
                               if (jsp.KEYWORDS_ATOM.hasOwnProperty(sym)) {
                                   throw "Don't define values for inbuilt constant '"+sym+"'";
                               }
                               return sym;
                           },
                           defval = function(v) {
                               if (v.match(/^"(.*)"$/) || v.match(/^'(.*)'$/)) {
                                   return [ "string", RegExp.$1 ];
                               }
                               else if (!isNaN(parseFloat(v))) {
                                   return [ "num", parseFloat(v) ];
                               }
                               else if (v.match(/^[a-z\$_][a-z\$_0-9]*$/i)) {
                                   return [ "name", v ];
                               }
                               else if (!v.match(/"/)) {
                                   return [ "string", v ];
                               }
                               else if (!v.match(/'/)) {
                                   return [ "string", v ];
                               }
                               throw "Can't understand the specified value: "+v;
                           };
                       if (defarg.match(/^([a-z_\$][a-z_\$0-9]*)(=(.*))?$/i)) {
                           var sym = defsym(RegExp.$1),
                               val = RegExp.$2 ? defval(RegExp.$2.substr(1)) : [ 'name', 'true' ];
                           options.defines[sym] = val;
                       }
                       else {
                           throw "The --define option expects SYMBOL[=value]";
                       }
                   } catch(ex) {
                       sys.print("ERROR: In option --define "+defarg+"\n"+ex+"\n");
                       process.exit(1);
                   }
                   break;
              case "--define-from-module":
                  var defmodarg = args.shift(),
                      defmodule = require(defmodarg),
                      sym,
                      val;
                  for (sym in defmodule) {
                      if (defmodule.hasOwnProperty(sym)) {
                          options.defines[sym] = function(val) {
                              if (typeof val == "string")
                                  return [ "string", val ];
                              if (typeof val == "number")
                                  return [ "num", val ];
                              if (val === true)
                                  return [ 'name', 'true' ];
                              if (val === false)
                                  return [ 'name', 'false' ];
                              if (val === null)
                                  return [ 'name', 'null' ];
                              if (val === undefined)
                                  return [ 'name', 'undefined' ];
                              sys.print("ERROR: In option --define-from-module "+defmodarg+"\n");
                              sys.print("ERROR: Unknown object type for: "+sym+"="+val+"\n");
                              process.exit(1);
                              return null;
                          }(defmodule[sym]);
                      }
                  }
                  break;
              case "--ascii":
                  options.codegen_options.ascii_only = true;
                  break;
              case "--make":
                  options.make = true;
                  break;
              case "--inline-script":
                  options.codegen_options.inline_script = true;
                  break;
              default:
                  filename = v;
                  break out;
          }
  }
  
  if (options.verbose) {
          pro.set_logger(function(msg){
                  sys.debug(msg);
          });
  }
  
  jsp.set_logger(function(msg){
          sys.debug(msg);
  });
  
  if (options.make) {
          options.out_same_file = false; // doesn't make sense in this case
          var makefile = JSON.parse(fs.readFileSync(filename || "Makefile.uglify.js").toString());
          output(makefile.files.map(function(file){
                  var code = fs.readFileSync(file.name);
                  if (file.module) {
                          code = "!function(exports, global){global = this;\n" + code + "\n;this." + file.module + " = exports;}({})";
                  }
                  else if (file.hide) {
                          code = "(function(){" + code + "}());";
                  }
                  return squeeze_it(code);
          }).join("\n"));
  }
  else if (filename) {
          fs.readFile(filename, "utf8", function(err, text){
                  if (err) throw err;
                  output(squeeze_it(text));
          });
  }
  else {
          var stdin = process.openStdin();
          stdin.setEncoding("utf8");
          var text = "";
          stdin.on("data", function(chunk){
                  text += chunk;
          });
          stdin.on("end", function() {
                  output(squeeze_it(text));
          });
  }
  
  function output(text) {
          var out;
          if (options.out_same_file && filename)
                  options.output = filename;
          if (options.output === true) {
                  out = process.stdout;
          } else {
                  out = fs.createWriteStream(options.output, {
                          flags: "w",
                          encoding: "utf8",
                          mode: 0644
                  });
          }
          out.write(text);
          if (options.output !== true) {
                  out.end();
          }
  };
  
  // --------- main ends here.
  
  function show_copyright(comments) {
          var ret = "";
          for (var i = 0; i < comments.length; ++i) {
                  var c = comments[i];
                  if (c.type == "comment1") {
                          ret += "//" + c.value + "\n";
                  } else {
                          ret += "/*" + c.value + "*/";
                  }
          }
          return ret;
  };
  
  function squeeze_it(code) {
          var result = "";
          if (options.show_copyright) {
                  var tok = jsp.tokenizer(code), c;
                  c = tok();
                  result += show_copyright(c.comments_before);
          }
          try {
                  var ast = time_it("parse", function(){ return jsp.parse(code); });
                  if (options.mangle) ast = time_it("mangle", function(){
                          return pro.ast_mangle(ast, {
                                  toplevel: options.mangle_toplevel,
                                  defines: options.defines,
                                  except: options.reserved_names
                          });
                  });
                  if (options.squeeze) ast = time_it("squeeze", function(){
                          ast = pro.ast_squeeze(ast, {
                                  make_seqs  : options.make_seqs,
                                  dead_code  : options.dead_code,
                                  keep_comps : !options.unsafe
                          });
                          if (options.unsafe)
                                  ast = pro.ast_squeeze_more(ast);
                          return ast;
                  });
                  if (options.ast)
                          return sys.inspect(ast, null, null);
                  result += time_it("generate", function(){ return pro.gen_code(ast, options.codegen_options) });
                  if (!options.codegen_options.beautify && options.max_line_length) {
                          result = time_it("split", function(){ return pro.split_lines(result, options.max_line_length) });
                  }
                  return result;
          } catch(ex) {
                  sys.debug(ex.stack);
                  sys.debug(sys.inspect(ex));
                  sys.debug(JSON.stringify(ex));
          }
  };
  
  function time_it(name, cont) {
          if (!options.verbose)
                  return cont();
          var t1 = new Date().getTime();
          try { return cont(); }
          finally { sys.debug("// " + name + ": " + ((new Date().getTime() - t1) / 1000).toFixed(3) + " sec."); }
  };
  