<commit_before>var _ = require("underscore"), chalk = require('chalk');

function ArgParser() {
   this.commands = {};  // expected commands
   this.specs = {};     // option specifications
}

ArgParser.prototype = {
  /* Add a command to the expected commands */
  command : function(name) {
    var command;
    if (name) {
      command = this.commands[name] = {
        name: name,
        specs: {}
      };
    }
    else {
      command = this.fallback = {
        specs: {}
      };
    }

    // facilitates command('name').options().cb().help()
    var chain = {
      options : function(specs) {
        command.specs = specs;
        return chain;
      },
      opts : function(specs) {
        // old API
        return this.options(specs);
      },
      option : function(name, spec) {
        command.specs[name] = spec;
        return chain;
      },
      callback : function(cb) {
        command.cb = cb;
        return chain;
      },
      help : function(help) {
        command.help = help;
        return chain;
      },
      usage : function(usage) {
        command._usage = usage;
        return chain;
      }
    };
    return chain;
  },

  nocommand : function() {
    return this.command();
  },

  options : function(specs) {
    this.specs = specs;
    return this;
  },

  opts : function(specs) {
    // old API
    return this.options(specs);
  },

  globalOpts : function(specs) {
    // old API
    return this.options(specs);
  },

  option : function(name, spec) {
    this.specs[name] = spec;
    return this;
  },

  usage : function(usage) {
    this._usage = usage;
    return this;
  },

  printer : function(print) {
    this.print = print;
    return this;
  },

  script : function(script) {
    this._script = script;
    return this;
  },

  scriptName : function(script) {
    // old API
    return this.script(script);
  },

  help : function(help) {
    this._help = help;
    return this;
  },

  colors: function() {
    // deprecated - colors are on by default now
    return this;
  },

  nocolors : function() {
    this._nocolors = true;
    return this;
  },

  parseArgs : function(argv) {
    // old API
    return this.parse(argv);
  },

  nom : function(argv) {
    return this.parse(argv);
  },

  parse : function(argv) {
    this.print = this.print || function(str, code) {
      console.log(str);
      process.exit(code || 0);
    };
    this._help = this._help || "";
    this._script = this._script || process.argv[0] + " "
          + require('path').basename(process.argv[1]);
    this.specs = this.specs || {};

    argv = argv || process.argv.slice(2);

    var arg = Arg(argv[0]).isValue && argv[0],
        command = arg && this.commands[arg],
        commandExpected = !_(this.commands).isEmpty();

    if (commandExpected) {
       if (command) {
          _(this.specs).extend(command.specs);
          this._script += " " + command.name;
          if (command.help) {
            this._help = command.help;
          }
          this.command = command;
       }
       else if (arg) {
          return this.print(this._script + ": no such command '" + arg + "'", 1);
       }
       else {
          // no command but command expected e.g. 'git -v'
          var helpStringBuilder = {
            list : function() {
               return 'one of: ' + _(this.commands).keys().join(", ");
            },
            twoColumn : function() {
              // find the longest command name to ensure horizontal alignment
              var maxLength = _(this.commands).max(function (cmd) {
                return cmd.name.length;
              }).name.length;

              // create the two column text strings
              var cmdHelp = _.map(this.commands, function(cmd, name) {
                var diff = maxLength - name.length;
                var pad = new Array(diff + 4).join(" ");
                return "  " + [ name, pad, cmd.help ].join(" ");
              });
              return "\n" + cmdHelp.join("\n");
            }
          };

          // if there are a small number of commands and all have help strings,
          // display them in a two column table; otherwise use the brief version.
          // The arbitrary choice of "20" comes from the number commands git
          // displays as "common commands"
          var helpType = 'list';
          if (_(this.commands).size() <= 20) {
            if (_(this.commands).every(function (cmd) { return cmd.help; })) {
                helpType = 'twoColumn';
            }
          }

          this.specs.command = {
            position: 0,
            help: helpStringBuilder[helpType].call(this)
          };

          if (this.fallback) {
            _(this.specs).extend(this.fallback.specs);
            this._help = this.fallback.help;
          } else {
            this.specs.command.required = true;
          }
       }
    }

    if (this.specs.length === undefined) {
      // specs is a hash not an array
      this.specs = _(this.specs).map(function(opt, name) {
        opt.name = name;
        return opt;
      });
    }
    this.specs = this.specs.map(function(opt) {
      return Opt(opt);
    });

    if (argv.indexOf("--help") >= 0 || argv.indexOf("-h") >= 0) {
      return this.print(this.getUsage());
    }

    var options = {};
    var args = argv.map(function(arg) {
      return Arg(arg);
    })
    .concat(Arg());

    var positionals = [];

    /* parse the args */
    var that = this;
    args.reduce(function(arg, val) {
      /* positional */
      if (arg.isValue) {
        positionals.push(arg.value);
      }
      else if (arg.chars) {
        var last = arg.chars.pop();

        /* -cfv */
        (arg.chars).forEach(function(ch) {
          that.setOption(options, ch, true);
        });

        /* -v key */
        if (!that.opt(last).flag) {
           if (val.isValue)  {
              that.setOption(options, last, val.value);
              return Arg(); // skip next turn - swallow arg
           }
           else {
              that.print("'-" + (that.opt(last).name || last) + "'"
                + " expects a value\n\n" + that.getUsage(), 1);
           }
        }
        else {
          /* -v */
          that.setOption(options, last, true);
        }
      }
      else if (arg.full) {
        var value = arg.value;

        /* --key */
        if (value === undefined) {
          /* --key value */
          if (!that.opt(arg.full).flag) {
            if (val.isValue) {
              that.setOption(options, arg.full, val.value);
              return Arg();
            }
            else {
              that.print("'--" + (that.opt(arg.full).name || arg.full) + "'"
                + " expects a value\n\n" + that.getUsage(), 1);
            }
          }
          else {
            /* --flag */
            value = true;
          }
        }
        that.setOption(options, arg.full, value);
      }
      return val;
    });

    positionals.forEach(function(pos, index) {
      this.setOption(options, index, pos);
    }, this);

    options._ = positionals;

    this.specs.forEach(function(opt) {
      if (opt.default !== undefined && options[opt.name] === undefined) {
        options[opt.name] = opt.default;
      }
    }, this);

    // exit if required arg isn't present
    this.specs.forEach(function(opt) {
      if (opt.required && options[opt.name] === undefined) {
         var msg = opt.name + " argument is required";
         msg = this._nocolors ? msg : chalk.red(msg);

         this.print("\n" + msg + "\n" + this.getUsage(), 1);
      }
    }, this);

    if (command && command.cb) {
      command.cb(options);
    }
    else if (this.fallback && this.fallback.cb) {
      this.fallback.cb(options);
    }

    return options;
  },

  getUsage : function() {
    if (this.command && this.command._usage) {
      return this.command._usage;
    }
    else if (this.fallback && this.fallback._usage) {
      return this.fallback._usage;
    }
    if (this._usage) {
      return this._usage;
    }

    // todo: use a template
    var str = "\n"
    if (!this._nocolors) {
      str += chalk.bold("Usage:");
    }
    else {
      str += "Usage:";
    }
    str += " " + this._script;

    var positionals = _(this.specs).select(function(opt) {
      return opt.position != undefined;
    })
    positionals = _(positionals).sortBy(function(opt) {
      return opt.position;
    });
    var options = _(this.specs).select(function(opt) {
      return opt.position === undefined;
    });

    // assume there are no gaps in the specified pos. args
    positionals.forEach(function(pos) {
      str += " ";
      var posStr = pos.string;
      if (!posStr) {
        posStr = pos.name || "arg" + pos.position;
        if (pos.required) {
            posStr = "<" + posStr + ">";
        } else {
            posStr = "[" + posStr + "]";
        }
        if (pos.list) {
          posStr += "...";
        }
      }
      str += posStr;
    });

    if (options.length) {
      if (!this._nocolors) {
        // must be a better way to do this
        str += chalk.blue(" [options]");
      }
      else {
        str += " [options]";
      }
    }

    if (options.length || positionals.length) {
      str += "\n\n";
    }

    function spaces(length) {
      var spaces = "";
      for (var i = 0; i < length; i++) {
        spaces += " ";
      }
      return spaces;
    }
    var longest = positionals.reduce(function(max, pos) {
      return pos.name.length > max ? pos.name.length : max;
    }, 0);

    positionals.forEach(function(pos) {
      var posStr = pos.string || pos.name;
      str += posStr + spaces(longest - posStr.length) + "     ";
      if (!this._nocolors) {
        str += chalk.grey(pos.help || "")
      }
      else {
        str += (pos.help || "")
      }
      str += "\n";
    }, this);
    if (positionals.length && options.length) {
      str += "\n";
    }

    if (options.length) {
      if (!this._nocolors) {
        str += chalk.blue("Options:");
      }
      else {
        str += "Options:";
      }
      str += "\n"

      var longest = options.reduce(function(max, opt) {
        return opt.string.length > max && !opt.hidden ? opt.string.length : max;
      }, 0);

      options.forEach(function(opt) {
        if (!opt.hidden) {
          str += "   " + opt.string + spaces(longest - opt.string.length) + "   ";

          var defaults = (opt.default != null ? "  [" + opt.default + "]" : "");
          var help = opt.help ? opt.help + defaults : "";
          str += this._nocolors ? help: chalk.grey(help);

          str += "\n";
        }
      }, this);
    }

    if (this._help) {
      str += "\n" + this._help;
    }
    return str;
  }
};

ArgParser.prototype.opt = function(arg) {
  // get the specified opt for this parsed arg
  var match = Opt({});
  this.specs.forEach(function(opt) {
    if (opt.matches(arg)) {
       match = opt;
    }
  });
  return match;
};

ArgParser.prototype.setOption = function(options, arg, value) {
  var option = this.opt(arg);
  if (option.callback) {
    var message = option.callback(value);

    if (typeof message == "string") {
      this.print(message, 1);
    }
  }

  if (option.type != "string") {
     try {
       // infer type by JSON parsing the string
       value = JSON.parse(value)
     }
     catch(e) {}
  }

  if (option.transform) {
     value = option.transform(value);
  }

  var name = option.name || arg;
  if (option.choices && option.choices.indexOf(value) == -1) {
     this.print(name + " must be one of: " + option.choices.join(", "), 1);
  }

  if (option.list) {
    if (!options[name]) {
      options[name] = [value];
    }
    else {
      options[name].push(value);
    }
  }
  else {
    options[name] = value;
  }
};

/* an arg is an item that's actually parsed from the command line
   e.g. "-l", "log.txt", or "--logfile=log.txt" */
var Arg = function (str) {
  var abbrRegex = /^\-(\w+?)$/,
      fullRegex = /^\-\-(no\-)?(.+?)(?:=(.+))?$/,
      valRegex = /^[^\-].*/;

  var charMatch = abbrRegex.exec(str),
      chars = charMatch && charMatch[1].split("");

  var fullMatch = fullRegex.exec(str),
      full = fullMatch && fullMatch[2];

  var isValue = str !== undefined && (str === "" || valRegex.test(str));
  var value;
  if (isValue) {
    value = str;
  }
  else if (full) {
    value = fullMatch[1] ? false : fullMatch[3];
  }

  return {
    str: str,
    chars: chars,
    full: full,
    value: value,
    isValue: isValue
  };
};

/* an opt is what's specified by the user in opts hash */
var Opt = function(opt) {
  var strings = (opt.string || "").split(","),
      abbr, full, metavar;
  for (var i = 0; i < strings.length; i++) {
    var string = strings[i].trim(),
        matches;
    if (matches = string.match(/^\-([^-])(?:\s+(.*))?$/)) {
      abbr = matches[1];
      metavar = matches[2];
    }
    else if (matches = string.match(/^\-\-(.+?)(?:[=\s]+(.+))?$/)) {
      full = matches[1];
      metavar = metavar || matches[2];
    }
  }

  matches = matches || [];
  var abbr = opt.abbr || abbr,   // e.g. v from -v
      full = opt.full || full, // e.g. verbose from --verbose
      metavar = opt.metavar || metavar;  // e.g. PATH from '--config=PATH'

  var string;
  if (opt.string) {
    string = opt.string;
  }
  else if (opt.position === undefined) {
    string = "";
    if (abbr) {
      string += "-" + abbr;
      if (metavar)
        string += " " + metavar
      string += ", ";
    }
    string += "--" + (full || opt.name);
    if (metavar) {
      string += " " + metavar;
    }
  }

  opt = _(opt).extend({
    name: opt.name || full || abbr,
    string: string,
    abbr: abbr,
    full: full,
    metavar: metavar,
    matches: function(arg) {
      return opt.full == arg || opt.abbr == arg || opt.position == arg
        || opt.name == arg || (opt.list && arg >= opt.position);
    }
  });
  return opt;
}

var createParser = function() {
  return new ArgParser();
}

var nomnom = createParser();

for (var i in nomnom) {
  if (typeof nomnom[i] == "function") {
     createParser[i] = _(nomnom[i]).bind(nomnom);
  }
}

module.exports = createParser;
<commit_msg>add support for #66; in *theory* it was already partly there in the 'full' option regex, but the `--no-option` vs. `--option` doesn't seem to be checked thoroughly yet.<commit_after>var _ = require("underscore"), chalk = require('chalk');

function ArgParser() {
   this.commands = {};  // expected commands
   this.specs = {};     // option specifications
}

ArgParser.prototype = {
  /* Add a command to the expected commands */
  command : function(name) {
    var command;
    if (name) {
      command = this.commands[name] = {
        name: name,
        specs: {}
      };
    }
    else {
      command = this.fallback = {
        specs: {}
      };
    }

    // facilitates command('name').options().cb().help()
    var chain = {
      options : function(specs) {
        command.specs = specs;
        return chain;
      },
      opts : function(specs) {
        // old API
        return this.options(specs);
      },
      option : function(name, spec) {
        command.specs[name] = spec;
        return chain;
      },
      callback : function(cb) {
        command.cb = cb;
        return chain;
      },
      help : function(help) {
        command.help = help;
        return chain;
      },
      usage : function(usage) {
        command._usage = usage;
        return chain;
      }
    };
    return chain;
  },

  nocommand : function() {
    return this.command();
  },

  options : function(specs) {
    this.specs = specs;
    return this;
  },

  opts : function(specs) {
    // old API
    return this.options(specs);
  },

  globalOpts : function(specs) {
    // old API
    return this.options(specs);
  },

  option : function(name, spec) {
    this.specs[name] = spec;
    return this;
  },

  usage : function(usage) {
    this._usage = usage;
    return this;
  },

  printer : function(print) {
    this.print = print;
    return this;
  },

  script : function(script) {
    this._script = script;
    return this;
  },

  scriptName : function(script) {
    // old API
    return this.script(script);
  },

  help : function(help) {
    this._help = help;
    return this;
  },

  colors: function() {
    // deprecated - colors are on by default now
    return this;
  },

  nocolors : function() {
    this._nocolors = true;
    return this;
  },

  parseArgs : function(argv) {
    // old API
    return this.parse(argv);
  },

  nom : function(argv) {
    return this.parse(argv);
  },

  parse : function(argv) {
    this.print = this.print || function(str, code) {
      console.log(str);
      process.exit(code || 0);
    };
    this._help = this._help || "";
    this._script = this._script || process.argv[0] + " "
          + require('path').basename(process.argv[1]);
    this.specs = this.specs || {};

    argv = argv || process.argv.slice(2);

    var arg = Arg(argv[0]).isValue && argv[0],
        command = arg && this.commands[arg],
        commandExpected = !_(this.commands).isEmpty();

    if (commandExpected) {
       if (command) {
          _(this.specs).extend(command.specs);
          this._script += " " + command.name;
          if (command.help) {
            this._help = command.help;
          }
          this.command = command;
       }
       else if (arg) {
          return this.print(this._script + ": no such command '" + arg + "'", 1);
       }
       else {
          // no command but command expected e.g. 'git -v'
          var helpStringBuilder = {
            list : function() {
               return 'one of: ' + _(this.commands).keys().join(", ");
            },
            twoColumn : function() {
              // find the longest command name to ensure horizontal alignment
              var maxLength = _(this.commands).max(function (cmd) {
                return cmd.name.length;
              }).name.length;

              // create the two column text strings
              var cmdHelp = _.map(this.commands, function(cmd, name) {
                var diff = maxLength - name.length;
                var pad = new Array(diff + 4).join(" ");
                return "  " + [ name, pad, cmd.help ].join(" ");
              });
              return "\n" + cmdHelp.join("\n");
            }
          };

          // if there are a small number of commands and all have help strings,
          // display them in a two column table; otherwise use the brief version.
          // The arbitrary choice of "20" comes from the number commands git
          // displays as "common commands"
          var helpType = 'list';
          if (_(this.commands).size() <= 20) {
            if (_(this.commands).every(function (cmd) { return cmd.help; })) {
                helpType = 'twoColumn';
            }
          }

          this.specs.command = {
            position: 0,
            help: helpStringBuilder[helpType].call(this)
          };

          if (this.fallback) {
            _(this.specs).extend(this.fallback.specs);
            this._help = this.fallback.help;
          } else {
            this.specs.command.required = true;
          }
       }
    }

    if (this.specs.length === undefined) {
      // specs is a hash not an array
      this.specs = _(this.specs).map(function(opt, name) {
        opt.name = name;
        return opt;
      });
    }
    this.specs = this.specs.map(function(opt) {
      return Opt(opt);
    });

    if (argv.indexOf("--help") >= 0 || argv.indexOf("-h") >= 0) {
      return this.print(this.getUsage());
    }

    var options = {};
    var args = argv.map(function(arg) {
      return Arg(arg);
    })
    .concat(Arg());

    var positionals = [];

    /* parse the args */
    var that = this;
    args.reduce(function(arg, val) {
      /* positional */
      if (arg.isValue) {
        positionals.push(arg.value);
      }
      else if (arg.chars) {
        var last = arg.chars.pop();

        /* -cfv */
        (arg.chars).forEach(function(ch) {
          that.setOption(options, ch, true);
        });

        /* -v key */
        if (!that.opt(last).flag) {
           if (val.isValue)  {
              that.setOption(options, last, val.value);
              return Arg(); // skip next turn - swallow arg
           }
           else {
              that.print("'-" + (that.opt(last).name || last) + "'"
                + " expects a value\n\n" + that.getUsage(), 1);
           }
        }
        else {
          /* -v */
          that.setOption(options, last, true);
        }
      }
      else if (arg.full) {
        var value = arg.value;

        /* --key */
        if (value === undefined) {
          /* --key value */
          if (!that.opt(arg.full).flag) {
            if (val.isValue) {
              that.setOption(options, arg.full, val.value);
              return Arg();
            }
            else {
              that.print("'--" + (that.opt(arg.full).name || arg.full) + "'"
                + " expects a value\n\n" + that.getUsage(), 1);
            }
          }
          else {
            /* --flag */
            value = true;
          }
        }
        that.setOption(options, arg.full, value);
      }
      return val;
    });

    positionals.forEach(function(pos, index) {
      this.setOption(options, index, pos);
    }, this);

    options._ = positionals;

    this.specs.forEach(function(opt) {
      if (opt.default !== undefined && options[opt.name] === undefined) {
        options[opt.name] = opt.default;
      }
    }, this);

    // exit if required arg isn't present
    this.specs.forEach(function(opt) {
      if (opt.required && options[opt.name] === undefined) {
         var msg = opt.name + " argument is required";
         msg = this._nocolors ? msg : chalk.red(msg);

         this.print("\n" + msg + "\n" + this.getUsage(), 1);
      }
    }, this);

    if (command && command.cb) {
      command.cb(options);
    }
    else if (this.fallback && this.fallback.cb) {
      this.fallback.cb(options);
    }

    return options;
  },

  getUsage : function() {
    if (this.command && this.command._usage) {
      return this.command._usage;
    }
    else if (this.fallback && this.fallback._usage) {
      return this.fallback._usage;
    }
    if (this._usage) {
      return this._usage;
    }

    // todo: use a template
    var str = "\n"
    if (!this._nocolors) {
      str += chalk.bold("Usage:");
    }
    else {
      str += "Usage:";
    }
    str += " " + this._script;

    var positionals = _(this.specs).select(function(opt) {
      return opt.position != undefined;
    })
    positionals = _(positionals).sortBy(function(opt) {
      return opt.position;
    });
    var options = _(this.specs).select(function(opt) {
      return opt.position === undefined;
    });

    // assume there are no gaps in the specified pos. args
    positionals.forEach(function(pos) {
      str += " ";
      var posStr = pos.string;
      if (!posStr) {
        posStr = pos.name || "arg" + pos.position;
        if (pos.required) {
            posStr = "<" + posStr + ">";
        } else {
            posStr = "[" + posStr + "]";
        }
        if (pos.list) {
          posStr += "...";
        }
      }
      str += posStr;
    });

    if (options.length) {
      if (!this._nocolors) {
        // must be a better way to do this
        str += chalk.blue(" [options]");
      }
      else {
        str += " [options]";
      }
    }

    if (options.length || positionals.length) {
      str += "\n\n";
    }

    function spaces(length) {
      var spaces = "";
      for (var i = 0; i < length; i++) {
        spaces += " ";
      }
      return spaces;
    }
    var longest = positionals.reduce(function(max, pos) {
      return pos.name.length > max ? pos.name.length : max;
    }, 0);

    positionals.forEach(function(pos) {
      var posStr = pos.string || pos.name;
      str += posStr + spaces(longest - posStr.length) + "     ";
      if (!this._nocolors) {
        str += chalk.grey(pos.help || "")
      }
      else {
        str += (pos.help || "")
      }
      str += "\n";
    }, this);
    if (positionals.length && options.length) {
      str += "\n";
    }

    if (options.length) {
      if (!this._nocolors) {
        str += chalk.blue("Options:");
      }
      else {
        str += "Options:";
      }
      str += "\n"

      var longest = options.reduce(function(max, opt) {
        return opt.string.length > max && !opt.hidden ? opt.string.length : max;
      }, 0);

      options.forEach(function(opt) {
        if (!opt.hidden) {
          str += "   " + opt.string + spaces(longest - opt.string.length) + "   ";

          var defaults = (opt.default != null ? "  [" + opt.default + "]" : "");
          var help = opt.help ? opt.help + defaults : "";
          str += this._nocolors ? help: chalk.grey(help);

          str += "\n";
        }
      }, this);
    }

    if (this._help) {
      str += "\n" + this._help;
    }
    return str;
  }
};

ArgParser.prototype.opt = function(arg) {
  // get the specified opt for this parsed arg
  var match = Opt({});
  this.specs.forEach(function(opt) {
    if (opt.matches(arg)) {
       match = opt;
    }
  });
  return match;
};

ArgParser.prototype.setOption = function(options, arg, value) {
  var option = this.opt(arg);
  if (option.callback) {
    var message = option.callback(value);

    if (typeof message == "string") {
      this.print(message, 1);
    }
  }

  if (option.type != "string") {
     try {
       // infer type by JSON parsing the string
       value = JSON.parse(value)
     }
     catch(e) {}
  }

  if (option.transform) {
     value = option.transform(value);
  }

  var name = option.name || arg;
  if (option.choices && option.choices.indexOf(value) == -1) {
     this.print(name + " must be one of: " + option.choices.join(", "), 1);
  }

  if (option.list) {
    if (!options[name]) {
      options[name] = [value];
    }
    else {
      options[name].push(value);
    }
  }
  else {
    options[name] = value;
  }
};

/* an arg is an item that's actually parsed from the command line
   e.g. "-l", "-l+", "log.txt", or "--logfile=log.txt" */
var Arg = function (str) {
  var abbrRegex = /^\-(\w+?)([+-]?)$/,
      fullRegex = /^\-\-(no\-)?(.+?)(?:=(.+))?$/,
      valRegex = /^[^\-].*/;

  var charMatch = abbrRegex.exec(str),
      chars = charMatch && charMatch[1].split("");

  var fullMatch = fullRegex.exec(str),
      full = fullMatch && fullMatch[2];

  var isValue = str !== undefined && (str === "" || valRegex.test(str));
  var value;
  if (isValue) {
    value = str;
  }
  else if (full) {
    value = fullMatch[1] ? false : fullMatch[3];
  }
  else if (chars && chars.length === 1) {
    // Only allow `-v-` or `-v+` when option `-v` is alone. Do *not* allow `-cfv-` or `-cfv+`!
    value = charMatch[2] ? charMatch[2] === '+' : undefined;
  }

  return {
    str: str,
    chars: chars,
    full: full,
    value: value,
    isValue: isValue
  };
};

/* an opt is what's specified by the user in opts hash */
var Opt = function(opt) {
  var strings = (opt.string || "").split(","),
      abbr, full, metavar;
  for (var i = 0; i < strings.length; i++) {
    var string = strings[i].trim(),
        matches;
    if (matches = string.match(/^\-([^-])(?:\s+(.*))?$/)) {
      abbr = matches[1];
      metavar = matches[2];
    }
    else if (matches = string.match(/^\-\-(.+?)(?:[=\s]+(.+))?$/)) {
      full = matches[1];
      metavar = metavar || matches[2];
    }
  }

  matches = matches || [];
  var abbr = opt.abbr || abbr,   // e.g. v from -v
      full = opt.full || full, // e.g. verbose from --verbose
      metavar = opt.metavar || metavar;  // e.g. PATH from '--config=PATH'

  var string;
  if (opt.string) {
    string = opt.string;
  }
  else if (opt.position === undefined) {
    string = "";
    if (abbr) {
      string += "-" + abbr;
      if (metavar)
        string += " " + metavar
      string += ", ";
    }
    string += "--" + (full || opt.name);
    if (metavar) {
      string += " " + metavar;
    }
  }

  opt = _(opt).extend({
    name: opt.name || full || abbr,
    string: string,
    abbr: abbr,
    full: full,
    metavar: metavar,
    matches: function(arg) {
      return opt.full == arg || opt.abbr == arg || opt.position == arg
        || opt.name == arg || (opt.list && arg >= opt.position);
    }
  });
  return opt;
}

var createParser = function() {
  return new ArgParser();
}

var nomnom = createParser();

for (var i in nomnom) {
  if (typeof nomnom[i] == "function") {
     createParser[i] = _(nomnom[i]).bind(nomnom);
  }
}

module.exports = createParser;
<|endoftext|>


<commit_before>// routes/say.js

var mongoose = require("mongoose");

exports.get_url = "/say/:id";

exports.get = function(req, res) {
    var Say = mongoose.model("Say");
    var id = req.params.id;
    Say.findById(id, function(err, say) {
        if (err != null) {
            res.send("404", 404);
        }
        else if (say == null) {
            res.send("404", 404);
        }
        else {
            say.renderMarkdown()
                .then(function(rendered_html) {

                    res.render("say", {
                        title: "@" + say.name + " on " + (say.date_str),
                        date: say.date,
                        html: rendered_html,
                        date_str: say.readableDateStr(),
                        _id: say._id,
                        raw_markdown: (say.raw_markdown || "").replace(/(^\s+)|(\s+$)/g, "")
                    });
                });
        }
    });
};
<commit_msg>fixing indent<commit_after>// routes/say.js

var mongoose = require("mongoose");

exports.get_url = "/say/:id";

exports.get = function(req, res) {
    var Say = mongoose.model("Say");
    var id = req.params.id;
    Say.findById(id, function(err, say) {
        if (err != null) {
            res.send("404", 404);
        }
        else if (say == null) {
            res.send("404", 404);
        }
        else {
            say.renderMarkdown()
                .then(function(rendered_html) {

                    res.render("say", {
                        title: "@" + say.name + " on " + (say.date_str),
                        date: say.date,
                        html: rendered_html,
                        date_str: say.readableDateStr(),
                        _id: say._id,
                        raw_markdown: (say.raw_markdown || "")
                        .replace(/(^\s+)|(\s+$)/g, "")
                    });
                });
        }
    });
};
<|endoftext|>


<commit_before>var RSA = (function(Reveal, BigNumber){
    var RSA = {};

    Reveal.addEventListener('poc', function(){
	var span = document.getElementById('number');
	span.textContent = '37';
    });

    return RSA;
})(Reveal, BigNumber);
<commit_msg>add model and view<commit_after>var RSA = (function(Reveal, BigNumber){
    var RSA = {};

    RSA.BigNumber = function(number){
	this.source = BigNumber(number);
    };

    var View = RSA.BigNumberView = function(parent, model){
	this.parent = parent;
	this.model = model;
	this.update();
    };
    View.prototype.update = function(){
	var container = this.container();
	container.textContent = this.model.source.toString();
    }
    View.prototype.container = function(){
	return this.parent;
    }

    var n = new RSA.BigNumber('51');

    Reveal.addEventListener('poc', function(){
	var span = document.getElementById('number');
	new RSA.BigNumberView(span, n);
    });

    return RSA;
})(Reveal, BigNumber);
<|endoftext|>


<commit_before>const debug = require('debug')('Action');

class Action {
    /**
     * @param {object} options
     * @param {string} options.selector
     * @param {ActionOptions} options.actionOptions
     * @param {string} options.parentSelector
     * @param {AbstractEnvironment} options.env
     * @param {Actions} options.actions
     * @param {Parser} options.parser
     * @param {*} options.prevResult
     */
    constructor(options) {
        this._selector = options.selector;
        this._parentSelector = options.parentSelector;
        this._options = options.actionOptions;
        this._env = options.env;
        this._actions = options.actions;
        this._parser = options.parser;
        this._prevResult = options.prevResult;
    }

    async perform() {
        throw new Error('You must redefine this method in the child class');
    }

    /**
     * @returns {Array.<string>} the list of the methods of environment on which action depends
     */
    dependsOn() {
        return [];
    }

    log(msg) {
        const args = Array.prototype.slice.call(arguments);
        args.splice(0, 1, `[${this._options.type}] ${msg}`);
        debug.apply(null, args);
    }
}

module.exports = Action;
<commit_msg>- added ability to set breaker function<commit_after>const debug = require('debug')('Action');

class Action {
    /**
     * @param {object} options
     * @param {string} options.selector
     * @param {ActionOptions} options.actionOptions
     * @param {string} options.parentSelector
     * @param {AbstractEnvironment} options.env
     * @param {Actions} options.actions
     * @param {Parser} options.parser
     * @param {*} options.prevResult
     */
    constructor(options) {
        this._selector = options.selector;
        this._parentSelector = options.parentSelector;
        this._options = options.actionOptions;
        if (!this._options.breaker) {
            this._options.breaker = () => false;
        }
        this._env = options.env;
        this._actions = options.actions;
        this._parser = options.parser;
        this._prevResult = options.prevResult;
    }

    async perform() {
        throw new Error('You must redefine this method in the real action');
    }

    /**
     * @returns {Array.<string>} The list of the methods of other components on which it depends on
     *                           (e.g. Environment.evaluateJs, Parser.parse, ...)
     */
    dependsOn() {
        return [];
    }

    log(msg) {
        const args = Array.prototype.slice.call(arguments);
        args.splice(0, 1, `[${this._options.type}] ${msg}`);
        debug.apply(null, args);
    }
}

module.exports = Action;
<|endoftext|>


<commit_before>// ルーティング
// /api/vms

const express = require('express');
const router = express.Router();
const vboxm = require('../vm/vboxm.js');

router.get('/vm/list', function (req, res) {
	let vmList = vboxm.getVMInfoList();

	vmList.forEach((info) => {
		info.state = info.State.substring(0, info.State.indexOf(' (since'))
	});
	res.send({ action: 'list', status: 'success', list: vmList});
});

router.get('/vm/start/:uuid', function (req, res) {
	let stdout = vboxm.start(req.params.uuid);
    res.send({ action: 'start', status: 'success', message: stdout });
});

router.get('/vm/stop/:uuid', function (req, res) {
	let stdout = vboxm.stop(req.params.uuid);
    res.send({ action: 'stop', status: 'success', message: stdout });
});

router.get('/vm/resume/:uuid', function (req, res) {
	let stdout = vboxm.resume(req.params.uuid);
    res.send({ action: 'resume', status: 'success', message: stdout });
});

router.get('/vm/pause/:uuid', function (req, res) {
	let stdout = vboxm.pause(req.params.uuid);
    res.send({ action: 'pause', status: 'success', message: stdout });
});

router.get('/vm/poweroff/:uuid', function (req, res) {
	let stdout = vboxm.poweroff(req.params.uuid);
    res.send({ action: 'poweroff', status: 'success', message: stdout });
});

router.get('/vm/info/:uuid', function (req, res) {
	let stdout = vboxm.getVMInfo(req.params.uuid);
    res.send({ action: 'info', status: 'success', message: '', info: stdout });
});

router.post('/vm/clone/:uuid', function (req, res) {
	let name = req.body.name;

	// 入力チェック
	let regResult = name.match(/^[a-zA-Z0-9!\(\)-=^~\\|@`\[{;+:*\]},<.>/?\_ ]+$/);
	if (regResult == null) {
    	res.send({ action: 'clone', status: 'error', message: `Invalid name : ${name}`});
		return;
	}

	let stdout = vboxm.clone(req.params.uuid, name);
    res.send({ action: 'clone', status: 'success', message: stdout});
});

router.get('/vm/destroy/:uuid', function (req, res) {
	let stdout = vboxm.delete(req.params.uuid);
    res.send({ action: 'destroy', status: 'success', message: stdout});
});

module.exports = router;
<commit_msg>日本語に若干対応<commit_after>// ルーティング
// /api/vms

const express = require('express');
const router = express.Router();
const vboxm = require('../vm/vboxm.js');

router.get('/vm/list', function (req, res) {
	let vmList = vboxm.getVMInfoList();

	vmList.forEach((info) => {
		info.state = info.State.substring(0, info.State.indexOf(' (since'))
	});
	res.send({ action: 'list', status: 'success', list: vmList});
});

router.get('/vm/start/:uuid', function (req, res) {
	let stdout = vboxm.start(req.params.uuid);
    res.send({ action: 'start', status: 'success', message: stdout });
});

router.get('/vm/stop/:uuid', function (req, res) {
	let stdout = vboxm.stop(req.params.uuid);
    res.send({ action: 'stop', status: 'success', message: stdout });
});

router.get('/vm/resume/:uuid', function (req, res) {
	let stdout = vboxm.resume(req.params.uuid);
    res.send({ action: 'resume', status: 'success', message: stdout });
});

router.get('/vm/pause/:uuid', function (req, res) {
	let stdout = vboxm.pause(req.params.uuid);
    res.send({ action: 'pause', status: 'success', message: stdout });
});

router.get('/vm/poweroff/:uuid', function (req, res) {
	let stdout = vboxm.poweroff(req.params.uuid);
    res.send({ action: 'poweroff', status: 'success', message: stdout });
});

router.get('/vm/info/:uuid', function (req, res) {
	let stdout = vboxm.getVMInfo(req.params.uuid);
    res.send({ action: 'info', status: 'success', message: '', info: stdout });
});

router.post('/vm/clone/:uuid', function (req, res) {
	let name = req.body.name;

	// 入力チェック
	let regResult = name.match(
		/^[a-zA-Z0-9!\(\)-=^~\\|@`\[{;+:*\]},<.>/?\_ 一-龠ぁ-んーァ-ヶＡ-Ｚａ-ｚ０-９　]+$/);
	if (regResult == null) {
    	res.send({ action: 'clone', status: 'error', message: `Invalid name : ${name}`});
		return;
	}

	let stdout = vboxm.clone(req.params.uuid, name);
    res.send({ action: 'clone', status: 'success', message: stdout});
});

router.get('/vm/destroy/:uuid', function (req, res) {
	let stdout = vboxm.delete(req.params.uuid);
    res.send({ action: 'destroy', status: 'success', message: stdout});
});

module.exports = router;
<|endoftext|>


<commit_before>'use strict';
var blessed = require('blessed');
var DBClient = require('./database_client');
var github = require('octonode');

var screen = blessed.screen({
  smartCSR: true
});

function MainWindow(repositoryName) {
  this.repositoryName = repositoryName;
  this.db = new DBClient(repositoryName);
  this.list = this.createList();
  this.selected = null;
  this.escapeKeys = ['escape', 'q', 'C-c'];
  this.modal = null;

  this.list.on('select', (selected) => {
    this.selected = selected.index;
    this.selectHandler();
  });
  this.addCloseHandler();
}

MainWindow.prototype.selectHandler = function() {
  if(this.modal) {
    return;
  }
  var selected = this.issues[this.selected - 1];
  this.modal = new ModalWindow(selected);
  this.modal.render();
};

MainWindow.prototype.fetchFromGitHub = function() {
  return new Promise((fullfill, reject) => {
    var client = github.client();
    var ghrepo = client.repo(this.repositoryName);
    ghrepo.issues(function(err, issues) {
      if(err) {
        reject(err);
      } else {
        fullfill(issues);
      }
    });
  });
}

MainWindow.prototype.initialFetch = function() {
  var self = this;
  this.db.insertDesignDocument()
  .then(this.fetchFromGitHub)
  .then(function(issues) {
    return self.db.saveToDatabase(issues);
  })
  .then(function() {
    return self.db.fetchDocuments();
  });
}

MainWindow.prototype.addCloseHandler = function() {
  screen.key(this.escapeKeys, () => {
    if(this.modal) {
      this.modal.close();
      screen.render();
      this.modal = null;
    } else {
      return process.exit(0);
    }
  });
};

MainWindow.prototype.initialize = function() {
  var self = this;
  this.db.fetchDocuments().then((resultSet) => {
    if (resultSet.total_rows > 0) {
      return Promise.resolve(resultSet);
    } else {
      return self.initialFetch();
    }
  })
  .then(function(resultSet) {
    return Promise.resolve(resultSet.rows.map((row) => {
      return row.doc;
    }));
  })
  .then(function(resultSet) {
    self.list.setItems(resultSet.map(function(d) {
      let title = d.title.toString();
      let id = d.number.toString();
      return `${id}-${title}`;
    }));
    screen.render();
    self.issues = resultSet;
  })
  .catch(function(err) {
    console.log("ERR", err);
  });
};

MainWindow.prototype.createList = function() {
  return blessed.List({
    width: '100%',
    height: '100%',
    top: 'center',
    left: 'center',
    align: 'left',
    fg: 'blue',
    border: {
      type: 'line'
    },
    selectedBg: 'green',
    mouse: true,
    keys: true,
    vi: true
  });
};

MainWindow.prototype.render = function() {
  screen.title = 'taskboard';
  screen.append(this.list);
  this.list.focus();
  screen.render();
};

function ModalWindow(issue) {
  this.box = null;
  this.issue = issue;
}

ModalWindow.prototype.render = function() {
  if(!this.box) {
    this.box = blessed.box({
      top: 'center',
      left: 'center',
      width: '50%',
      height: '50%',
      content: this.issue.body,
      tags: true,
      border: {
        type: 'line'
      },
      style: {
        fg: 'white',
        bg: 'magenta',
        border: {
          fg: '#f0f0f0'
        },
        hover: {
          bg: 'green'
        }
      }
    });
    screen.append(this.box);
  }
  screen.render();
};

ModalWindow.prototype.close = function() {
  screen.remove(this.box);
  this.box = null;
};

module.exports = MainWindow;
<commit_msg>update modal when selecting new issue<commit_after>'use strict';
var blessed = require('blessed');
var DBClient = require('./database_client');
var github = require('octonode');

var screen = blessed.screen({
  smartCSR: true
});

function MainWindow(repositoryName) {
  this.repositoryName = repositoryName;
  this.db = new DBClient(repositoryName);
  this.list = this.createList();
  this.selected = null;
  this.escapeKeys = ['escape', 'q', 'C-c'];
  this.modal = null;

  this.list.on('select', (selected) => {
    this.selected = selected.index;
    this.selectHandler();
  });
  this.addCloseHandler();
}

MainWindow.prototype.selectHandler = function() {
  var selected = this.issues[this.selected - 1];
  if(this.modal) {
    this.modal.update(selected);
  } else {
    this.modal = new ModalWindow(selected);
    this.modal.render();
  }
};

MainWindow.prototype.fetchFromGitHub = function() {
  return new Promise((fullfill, reject) => {
    var client = github.client();
    var ghrepo = client.repo(this.repositoryName);
    ghrepo.issues(function(err, issues) {
      if(err) {
        reject(err);
      } else {
        fullfill(issues);
      }
    });
  });
}

MainWindow.prototype.initialFetch = function() {
  var self = this;
  this.db.insertDesignDocument()
  .then(this.fetchFromGitHub)
  .then(function(issues) {
    return self.db.saveToDatabase(issues);
  })
  .then(function() {
    return self.db.fetchDocuments();
  });
}

MainWindow.prototype.addCloseHandler = function() {
  screen.key(this.escapeKeys, () => {
    if(this.modal) {
      this.modal.close();
      screen.render();
      this.modal = null;
    } else {
      return process.exit(0);
    }
  });
};

MainWindow.prototype.initialize = function() {
  var self = this;
  this.db.fetchDocuments().then((resultSet) => {
    if (resultSet.total_rows > 0) {
      return Promise.resolve(resultSet);
    } else {
      return self.initialFetch();
    }
  })
  .then(function(resultSet) {
    return Promise.resolve(resultSet.rows.map((row) => {
      return row.doc;
    }));
  })
  .then(function(resultSet) {
    self.list.setItems(resultSet.map(function(d) {
      let title = d.title.toString();
      let id = d.number.toString();
      return `${id}-${title}`;
    }));
    screen.render();
    self.issues = resultSet;
  })
  .catch(function(err) {
    console.log("ERR", err);
  });
};

MainWindow.prototype.createList = function() {
  return blessed.List({
    width: '100%',
    height: '100%',
    top: 'center',
    left: 'center',
    align: 'left',
    fg: 'blue',
    border: {
      type: 'line'
    },
    selectedBg: 'green',
    mouse: true,
    keys: true,
    vi: true
  });
};

MainWindow.prototype.render = function() {
  screen.title = 'taskboard';
  screen.append(this.list);
  this.list.focus();
  screen.render();
};

function ModalWindow(issue) {
  this.box = null;
  this.issue = issue;
}

ModalWindow.prototype.render = function() {
  if(!this.box) {
    this.box = blessed.box({
      top: 'center',
      left: 'center',
      width: '50%',
      height: '50%',
      content: this.issue.body,
      tags: true,
      border: {
        type: 'line'
      },
      style: {
        fg: 'white',
        bg: 'magenta',
        border: {
          fg: '#f0f0f0'
        },
        hover: {
          bg: 'green'
        }
      }
    });
    screen.append(this.box);
  }
  screen.render();
};

ModalWindow.prototype.update = function(newIssue) {
  this.issue = newIssue;
  this.box.content = newIssue.body;
  screen.render();
};

ModalWindow.prototype.close = function() {
  screen.remove(this.box);
  this.box = null;
};

module.exports = MainWindow;
<|endoftext|>


<commit_before>// Closes the sidebar menu
$("#menu-close").click(function(e) {
    e.preventDefault();
    $("#sidebar-wrapper").toggleClass("active");
});

// Opens the sidebar menu
$("#menu-toggle").click(function(e) {
    e.preventDefault();
    $("#sidebar-wrapper").toggleClass("active");
});

// Scrolls to the selected menu item on the page
$(function() {
  $("#nav").find("ul li a[href^='#']").on('click', function(e) {

    // prevent default anchor click behavior
    e.preventDefault();

    // store hash
    var hash = this.hash;

    // animate
    $('html, body').animate({
      scrollTop: $(hash).offset().top
    }, 300, function(){

      // when done, add hash to url
      // (default click behaviour)
      window.location.hash = hash;
    });

  });

});

angular.module('rdd', ['ui.bootstrap'])
  .controller('RddController', ['$scope', '$http', '$modal', '$rootScope', '$location', function($scope, $http, $modal, $rootScope, $location) {
    $http.get('data.json').then(function(response) {
      $scope.staff = response.data.staff;
      $scope.speakers = response.data.speakers;
    });

    $scope.personModal = function(person) {
      var scope = $rootScope.$new();
      scope.person = person;

      var modalInstance = $modal.open({
        templateUrl: 'js/templates/person-modal.html',
        scope: scope
      });
    };
    $scope.go = function ( url ) {
      window.open(url, '_blank');
    };
  }]);<commit_msg>Fixed selectors<commit_after>// Closes the sidebar menu
$("#menu-close").click(function(e) {
    e.preventDefault();
    $("#sidebar-wrapper").toggleClass("active");
});

// Opens the sidebar menu
$(".navbar-toggle").click(function(e) {
    e.preventDefault();
    $(".navbar-collapse").toggleClass("collapse");
});

// Scrolls to the selected menu item on the page
$(function() {
  $("#nav").find("ul li a[href^='#']").on('click', function(e) {
    $(".navbar-collapse").addClass("collapse");

    // prevent default anchor click behavior
    e.preventDefault();

    // store hash
    var hash = this.hash;

    // animate
    $('html, body').animate({
      scrollTop: $(hash).offset().top
    }, 300, function(){

      // when done, add hash to url
      // (default click behaviour)
      window.location.hash = hash;
    });

  });

});

angular.module('rdd', ['ui.bootstrap'])
  .controller('RddController', ['$scope', '$http', '$modal', '$rootScope', '$location', function($scope, $http, $modal, $rootScope, $location) {
    $http.get('data.json').then(function(response) {
      $scope.staff = response.data.staff;
      $scope.speakers = response.data.speakers;
    });

    $scope.personModal = function(person) {
      var scope = $rootScope.$new();
      scope.person = person;

      var modalInstance = $modal.open({
        templateUrl: 'js/templates/person-modal.html',
        scope: scope
      });
    };
    $scope.go = function ( url ) {
      window.open(url, '_blank');
    };
  }]);<|endoftext|>


<commit_before>/*
 * Script Content Parser Adapter
 * @module   adapter/script
 * @author   genify(caijf@corp.netease.com)
 */
var util = require('util'),
    path = require('path'),
    uglify = require('uglify-js');
// ast node checker
var AST_CHECKER = {
    Dot:function(n){n.name=n.property;return n;},
    ObjectKeyVal:function(n){n.name=n.key;return n;},
    Var:function(n){var a = [];n.definitions.forEach(function(n){a.push(n.name);});return a;},
    VarDef:function(n){return n.name||{};},
    Function:function(n){return n.name||{};},
    If:function(n){return n.condition;},
    Defun:function(n){return n.name||{};},
    SymbolRef:function(n){return n.definition();},
    SymbolCatch:function(n){return n.definition();},
    Dft:function(n){return n;}
};
// reserved variable key
var KEY_RESERVED = ['lt','gt','amp','nbsp','quot','apos','copy','reg'];
// script parser
// map      file map, eg. {'/path/to/out.js':['/path/to/a.js','/path/to/b.js',...],...}
// code     code map, eg. {'/path/to/a.js':'var a = 111;','/path/to/b.js':'var b = 222;',...}
var Parser = module.exports =
    require('../util/klass.js').create();
var pro = Parser.extend(require('../util/event.js'));
/**
 * class initialization
 * @param  {Object} config - config parameters
 * @return {Void}
 */
pro.init = function(config){
    this._super(config);
    // overwrite uglify logger
    uglify.AST_Node.
        warn_function =
            this._warn.bind(this);
    // merge code
    config = config||{};
    this._mergeCodeAndToAST(
        config.map||{},
        config.code||{}
    );
};
/**
 * parse code, compress code, mangle variables
 * @param  {Object}  config - config object
 * @param  {Object}  config.bags        - variable name will be mangled map
 * @param  {Number}  config.level       - obfuscate level, value between 0 and 3
 * @param  {Boolean} config.compatible  - use nej compatible mode
 * @param  {Boolean} config.dropconsole - drop console statement
 * @param  {Object}  config.sourcemap   - output source map config
 * @return {Void}
 */
pro.parse = function(config){
    config = config||{};
    this._result = {};
    // mangle local variables
    this._ast.figure_out_scope();
    this._ast.compute_char_frequency();
    var bags = config.bags||{},
        except = this._getMangleExcept(bags);
    this._ast.mangle_names({
        sort:!0,
        except:except
    });
    // mangle api
    var level = parseInt(config.level,10);
    if (isNaN(level)||level<0||level>3){
        level = 3;
    }
    if (level>0){
        this._ast.compute_char_frequency();
        this._mangleAPI(level,bags,except);
    }
    this._generateOutput(config||{});
};
/**
 * dump result after uglify
 * @return {Object} parse result, eg. {bags:{},code:{},sourcemap:{}}
 */
pro.dump = function(){
    return this._result;
};
/**
 * output uglifyjs warn logger
 * @private
 * @return {Void}
 */
pro._warn = function(msg){
    this.emit('warn',{
        message:msg
    });
};
/**
 * dump mangle except variables
 * @private
 * @param  {Object} bags - mangled variables map
 * @return {Array}  variables list
 */
pro._getMangleExcept = function(bags){
    var ret = [
        '__proto__',
        '__defineGetter__',
        '__defineSetter__',
        '__lookupGetter__',
        '__lookupSetter__'
    ];
    bags = bags||{};
    Object.keys(bags).forEach(function(key){
        var value = bags[key];
        if (key===value){
            ret.push(key);
            delete bags[key];
        }
    });
    return ret;
};
/**
 * merge code and generator ast
 * @private
 * @param  {Object} map - file map
 * @param  {Object} codes - code map
 */
pro._mergeCodeAndToAST = function(map,codes){
    // merge code
    this._asts = {};
    this._code = {};
    // top level ast
    this._ast = uglify.parse(
        '',{fromString:!0}
    );
    Object.keys(map).forEach(
        function(file){
            // merge source
            var ret = [];
            map[file].forEach(function(name){
                var code = codes[name];
                if (!code){
                    this.emit('warn',{
                        data:[name],
                        message:'empty js code for file %s'
                    });
                }else{
                    ret.push(code);
                }
            },this);
            try{
                // parse ast
                var code = ret.join('\n'),
                    ast = uglify.parse(
                        code,{
                            fromString:!0,
                            filename:file
                        }
                    );
                this._code[file] = code;
                this._asts[file] = ast;
                // merge ast
                this._ast.body =
                    this._ast.body.concat(ast.body);
                this._ast.end = ast.end;
            }catch(ex){
                var files = {},
                    name = path.basename(file);
                files[name] = code;
                // dump error source code
                var source = [],
                    index = ex.line-1;
                for(var i=Math.max(0,index-2),
                        l=Math.min(ret.length,index+4);i<l;i++){
                    var sep = index===i?'-->\t':'\t\t';
                    source.push(sep+(i+1)+':\t'+ret[i]);
                }
                // trigger error event
                this.emit(
                    'error',{
                        files:files,
                        data:[file,name,ex.message,ex.line,ex.col,source.join('\n')],
                        message:'js code parse error for %s, source code in %s, exception information :\n%s at line %s col %s\n'
                    }
                )
            }
        },this
    );
};
/**
 * mangle api variables
 * @private
 * @param  {Number} level  - obfuscate level
 * @param  {Object} bags   - name mangled map
 * @param  {Array}  except - variable reserved array
 * @return {Void}
 */
pro._mangleAPI = (function(){
    var _varMap;
    var _doMapVariables = function(node){
        if (!node){
            return;
        }
        // cache mangled variables
        if (!util.isArray(node)){
            var name = node.mangled_name||node.name;
            if (name!=null){
                _varMap[name] = !0;
            }
            return;
        }
        node.forEach(_doMapVariables);
    };
    var _doClearVariables = function(bags){
        Object.keys(bags).forEach(function(key){
            var name = bags[key];
            if (_varMap[name]){
                delete bags[key];
            }else{
                _varMap[name] = !0;
            }
        });
    };
    var _next;
    var _doGenNextName = function(orig,bags){
        var name = bags[orig];
        if (!name){
            do{
                name = uglify.base54(++_next);
            }while(
                !uglify.is_identifier(name)||!!_varMap[name]
            )
        }
        return name;
    };
    var _keyMap = {
        Dot:'property',
        ObjectKeyVal:'key',
        SymbolRef:'mangled_name'
    };
    var _isNodeNeedMangle = function(node){
        var ret = !1;
        Object.keys(_keyMap).some(function(key){
            if (node instanceof uglify['AST_'+key]){
                ret = !0;
                return ret;
            }
        });
        return ret;
    };
    var _levelMap = {
        1:function(c0,c1){
            return c0=='_'&&c1!='_'&&c1!='$';
        },
        2:function(c0,c1){
            return c0=='_'&&c1!='$';
        },
        3:function(c0,c1){
            return c0=='_';
        }
    };
    var _isNameNeedMangle = function(name,level,except){
        name = ''+name;
        return except.indexOf(name)<0&&
            _levelMap[level](
                name.charAt(0),name.charAt(1)
            );
    };
    return function(level,bags,except){
        // init reserved key list
        _next = -1;
        _varMap = {};
        uglify.base54.reset();
        KEY_RESERVED.forEach(function(key){
            _varMap[key] = !0;
        });
        // dump mangled node list
        var list = [];
        var walker = new uglify.TreeWalker(function(node){
            var type = node.TYPE,
                target = AST_CHECKER[type]||AST_CHECKER['Dft'](node);
            _doMapVariables(target);
            // check local variables
            if (node instanceof uglify.AST_Scope){
                node.variables.each(_doMapVariables);
            }
            // check api or property
            if (_isNodeNeedMangle(node)&&
                !target.mangled_name&&
                _isNameNeedMangle(node.name,level,except)){
                list.push(node);
            }
        });
        this._ast.walk(walker);
        _doClearVariables(bags);
        // mangle api variables
        var ret = {};
        list.forEach(function(node){
            var type = node.TYPE,
                key = _keyMap[type],
                name = node.name,
                mangled = _doGenNextName(name,bags);
            // update node value
            if (type==='SymbolRef'){
                node.definition()[key] = mangled;
            }else{
                node[key] = mangled;
            }
            // save and lock new name
            _varMap[mangled] = !0;
            bags[name] = mangled;
            ret[name] = mangled;
        });
        this._result.bags = ret;
    };
})();
/**
 * generate output content
 * @private
 * @param  {Object} opt - source map output config
 * @param  {String} opt.sourcemap   - source map config, eg. {root:''}
 * @param  {Boolean} opt.compatible - compatiable for nej
 * @return {Void}
 */
pro._generateOutput = function(opt){
    // init config
    var config = {
        max_line_len:10000
    };
    var compressor = uglify.Compressor({
        sequences:!1,
        properties:!1,
        conditionals:!1,
        drop_console:!!opt.dropconsole,
        global_defs:{
            DEBUG:!1,
            CMPT:!!opt.compatible
        }
    });
    // init output code
    var root = (opt.sourcemap||{}).root;
    this._result.code = {};
    if (!!root){
        this._result.sourcemap = {};
    }
    // do output
    Object.keys(this._asts).forEach(
        function(file){
            var ast = this._asts[file];
            // compress first
            ast.figure_out_scope();
            ast = ast.transform(compressor);
            // need output source map
            if (!!root){
                var name = path.basename(file);
                config.source_map = uglify.SourceMap({
                    file:name,root:'',orig:null
                });
                config.source_map.get().setSourceContent(
                    name,this._code[file]
                );
            }
            // output code
            var code = ast.print_to_string(config);
            // check source map
            if (!!root){
                code += '\n//# sourceMappingURL='+root+name+'.map ';
                this._result.sourcemap[name+'.map'] = ''+config.source_map;
            }
            // save code
            this._result.code[file] = code;
        },this
    );
};<commit_msg>support source code for script adapter<commit_after>/*
 * Script Content Parser Adapter
 * @module   adapter/script
 * @author   genify(caijf@corp.netease.com)
 */
var util = require('util'),
    path = require('path'),
    uglify = require('uglify-js');
// ast node checker
var AST_CHECKER = {
    Dot:function(n){n.name=n.property;return n;},
    ObjectKeyVal:function(n){n.name=n.key;return n;},
    Var:function(n){var a = [];n.definitions.forEach(function(n){a.push(n.name);});return a;},
    VarDef:function(n){return n.name||{};},
    Function:function(n){return n.name||{};},
    If:function(n){return n.condition;},
    Defun:function(n){return n.name||{};},
    SymbolRef:function(n){return n.definition();},
    SymbolCatch:function(n){return n.definition();},
    Dft:function(n){return n;}
};
// reserved variable key
var KEY_RESERVED = ['lt','gt','amp','nbsp','quot','apos','copy','reg'];
// script parser
// map      file map, eg. {'/path/to/out.js':['/path/to/a.js','/path/to/b.js',...],...}
// code     code map, eg. {'/path/to/a.js':'var a = 111;','/path/to/b.js':'var b = 222;',...}
var Parser = module.exports =
    require('../util/klass.js').create();
var pro = Parser.extend(require('../util/event.js'));
/**
 * class initialization
 * @param  {Object} config - config parameters
 * @return {Void}
 */
pro.init = function(config){
    this._super(config);
    // overwrite uglify logger
    uglify.AST_Node.
        warn_function =
            this._warn.bind(this);
    // merge code
    config = config||{};
    this._mergeCodeAndToAST(
        config.map||{},
        config.code||{}
    );
};
/**
 * parse code, compress code, mangle variables
 * @param  {Object}  config - config object
 * @param  {Object}  config.bags        - variable name will be mangled map
 * @param  {Number}  config.level       - obfuscate level, value between 0 and 3
 * @param  {Boolean} config.compatible  - use nej compatible mode
 * @param  {Boolean} config.dropconsole - drop console statement
 * @param  {Object}  config.sourcemap   - output source map config
 * @return {Void}
 */
pro.parse = function(config){
    config = config||{};
    this._result = {};
    // mangle local variables
    this._ast.figure_out_scope();
    this._ast.compute_char_frequency();
    var bags = config.bags||{},
        except = this._getMangleExcept(bags);
    this._ast.mangle_names({
        sort:!0,
        except:except
    });
    // mangle api
    var level = parseInt(config.level,10);
    if (isNaN(level)||level<0||level>3){
        level = 3;
    }
    if (level>0){
        this._ast.compute_char_frequency();
        this._mangleAPI(level,bags,except);
    }
    this._generateOutput(config||{});
};
/**
 * dump result after uglify
 * @return {Object} parse result, eg. {bags:{},code:{},sourcemap:{}}
 */
pro.dump = function(){
    return this._result;
};
/**
 * output uglifyjs warn logger
 * @private
 * @return {Void}
 */
pro._warn = function(msg){
    this.emit('warn',{
        message:msg
    });
};
/**
 * dump mangle except variables
 * @private
 * @param  {Object} bags - mangled variables map
 * @return {Array}  variables list
 */
pro._getMangleExcept = function(bags){
    var ret = [
        '__proto__',
        '__defineGetter__',
        '__defineSetter__',
        '__lookupGetter__',
        '__lookupSetter__'
    ];
    bags = bags||{};
    Object.keys(bags).forEach(function(key){
        var value = bags[key];
        if (key===value){
            ret.push(key);
            delete bags[key];
        }
    });
    return ret;
};
/**
 * merge code and generator ast
 * @private
 * @param  {Object} map - file map
 * @param  {Object} codes - code map
 */
pro._mergeCodeAndToAST = function(map,codes){
    // merge code
    this._asts = {};
    this._code = {};
    // top level ast
    this._ast = uglify.parse(
        '',{fromString:!0}
    );
    Object.keys(map).forEach(
        function(file){
            // merge source
            var ret = [];
            if (typeof map[file]==='string'){
                ret.push(map[file]);
            }else{
                map[file].forEach(function(name){
                    var code = codes[name];
                    if (!code){
                        this.emit('warn',{
                            data:[name],
                            message:'empty js code for file %s'
                        });
                    }else{
                        ret.push(code);
                    }
                },this);
            }
            try{
                // parse ast
                var code = ret.join('\n'),
                    ast = uglify.parse(
                        code,{
                            fromString:!0,
                            filename:file
                        }
                    );
                this._code[file] = code;
                this._asts[file] = ast;
                // merge ast
                this._ast.body =
                    this._ast.body.concat(ast.body);
                this._ast.end = ast.end;
            }catch(ex){
                var files = {},
                    name = path.basename(file);
                files[name] = code;
                // dump error source code
                var source = [],
                    index = ex.line-1;
                for(var i=Math.max(0,index-2),
                        l=Math.min(ret.length,index+4);i<l;i++){
                    var sep = index===i?'-->\t':'\t\t';
                    source.push(sep+(i+1)+':\t'+ret[i]);
                }
                // trigger error event
                this.emit(
                    'error',{
                        files:files,
                        data:[file,name,ex.message,ex.line,ex.col,source.join('\n')],
                        message:'js code parse error for %s, source code in %s, exception information :\n%s at line %s col %s\n'
                    }
                )
            }
        },this
    );
};
/**
 * mangle api variables
 * @private
 * @param  {Number} level  - obfuscate level
 * @param  {Object} bags   - name mangled map
 * @param  {Array}  except - variable reserved array
 * @return {Void}
 */
pro._mangleAPI = (function(){
    var _varMap;
    var _doMapVariables = function(node){
        if (!node){
            return;
        }
        // cache mangled variables
        if (!util.isArray(node)){
            var name = node.mangled_name||node.name;
            if (name!=null){
                _varMap[name] = !0;
            }
            return;
        }
        node.forEach(_doMapVariables);
    };
    var _doClearVariables = function(bags){
        Object.keys(bags).forEach(function(key){
            var name = bags[key];
            if (_varMap[name]){
                delete bags[key];
            }else{
                _varMap[name] = !0;
            }
        });
    };
    var _next;
    var _doGenNextName = function(orig,bags){
        var name = bags[orig];
        if (!name){
            do{
                name = uglify.base54(++_next);
            }while(
                !uglify.is_identifier(name)||!!_varMap[name]
            )
        }
        return name;
    };
    var _keyMap = {
        Dot:'property',
        ObjectKeyVal:'key',
        SymbolRef:'mangled_name'
    };
    var _isNodeNeedMangle = function(node){
        var ret = !1;
        Object.keys(_keyMap).some(function(key){
            if (node instanceof uglify['AST_'+key]){
                ret = !0;
                return ret;
            }
        });
        return ret;
    };
    var _levelMap = {
        1:function(c0,c1){
            return c0=='_'&&c1!='_'&&c1!='$';
        },
        2:function(c0,c1){
            return c0=='_'&&c1!='$';
        },
        3:function(c0,c1){
            return c0=='_';
        }
    };
    var _isNameNeedMangle = function(name,level,except){
        name = ''+name;
        return except.indexOf(name)<0&&
            _levelMap[level](
                name.charAt(0),name.charAt(1)
            );
    };
    return function(level,bags,except){
        // init reserved key list
        _next = -1;
        _varMap = {};
        uglify.base54.reset();
        KEY_RESERVED.forEach(function(key){
            _varMap[key] = !0;
        });
        // dump mangled node list
        var list = [];
        var walker = new uglify.TreeWalker(function(node){
            var type = node.TYPE,
                target = AST_CHECKER[type]||AST_CHECKER['Dft'](node);
            _doMapVariables(target);
            // check local variables
            if (node instanceof uglify.AST_Scope){
                node.variables.each(_doMapVariables);
            }
            // check api or property
            if (_isNodeNeedMangle(node)&&
                !target.mangled_name&&
                _isNameNeedMangle(node.name,level,except)){
                list.push(node);
            }
        });
        this._ast.walk(walker);
        _doClearVariables(bags);
        // mangle api variables
        var ret = {};
        list.forEach(function(node){
            var type = node.TYPE,
                key = _keyMap[type],
                name = node.name,
                mangled = _doGenNextName(name,bags);
            // update node value
            if (type==='SymbolRef'){
                node.definition()[key] = mangled;
            }else{
                node[key] = mangled;
            }
            // save and lock new name
            _varMap[mangled] = !0;
            bags[name] = mangled;
            ret[name] = mangled;
        });
        this._result.bags = ret;
    };
})();
/**
 * generate output content
 * @private
 * @param  {Object} opt - source map output config
 * @param  {String} opt.sourcemap   - source map config, eg. {root:''}
 * @param  {Boolean} opt.compatible - compatiable for nej
 * @return {Void}
 */
pro._generateOutput = function(opt){
    // init config
    var config = {
        max_line_len:10000
    };
    var compressor = uglify.Compressor({
        sequences:!1,
        properties:!1,
        conditionals:!1,
        drop_console:!!opt.dropconsole,
        global_defs:{
            DEBUG:!1,
            CMPT:!!opt.compatible
        }
    });
    // init output code
    var root = (opt.sourcemap||{}).root;
    this._result.code = {};
    if (!!root){
        this._result.sourcemap = {};
    }
    // do output
    Object.keys(this._asts).forEach(
        function(file){
            var ast = this._asts[file];
            // compress first
            ast.figure_out_scope();
            ast = ast.transform(compressor);
            // need output source map
            if (!!root){
                var name = path.basename(file);
                config.source_map = uglify.SourceMap({
                    file:name,root:'',orig:null
                });
                config.source_map.get().setSourceContent(
                    name,this._code[file]
                );
            }
            // output code
            var code = ast.print_to_string(config);
            // check source map
            if (!!root){
                code += '\n//# sourceMappingURL='+root+name+'.map ';
                this._result.sourcemap[name+'.map'] = ''+config.source_map;
            }
            // save code
            this._result.code[file] = code;
        },this
    );
};<|endoftext|>


<commit_before>'use strict';
const tools = require('../tools.js');
const events = require('events');

function addInstance(device, options, cb){
	// options.newInstances
    // options.existingInstances
    // device - additional info about device
    // options.log - logger
    // options.language - system language
    tools.words['comments'] = {
        "en": "Warning! All Cameras/NVT on the network must have the same user",
		"de": "Warnung! Alle Kameras/NVT im Netzwerk müssen denselben Benutzer haben",
		"ru": "Предупреждение! Все камеры/NVT в сети должны иметь одного и того же пользователя",
		"pt": "Atenção! Todas as câmeras/NVT na rede devem ter o mesmo usuário",
		"nl": "Waarschuwing! Alle camera's/NVT op het netwerk moeten dezelfde gebruiker hebben",
		"fr": "Avertissement! Toutes les caméras/NVT sur le réseau doivent avoir le même utilisateur",
		"it": "Avvertimento! Tutte le telecamere/NVT sulla rete devono avere lo stesso utente",
		"es": "¡Advertencia! Todas las cámaras/NVT en la red deben tener el mismo usuario",
		"pl": "Ostrzeżenie! Wszystkie kamery/NVT w sieci muszą mieć tego samego użytkownika",
		"zh-cn": "警告！网络上的所有摄像机/ NVT必须具有相同的用户"
    };

    let instance = tools.findInstance(options, 'onvif', obj => {
		options.log.debug('Onvif. obj = ' + JSON.stringify(obj));
		return obj.common.name === 'onvif';
	});

    if (instance){
		let comment = 'Cameras/NVT - ' + device.count + ' (' + tools.translate(options.language, 'comments') + ')';
		const id = tools.getNextInstanceID('onvif', options);

		instance = {
			_id: id,
			common: {
				name:    'onvif',
				enabled: true,
				title:   'Onvif Support'
			},
			comment: {
				add: [comment],
				inputs: [
					{
						name: 'native.user',
						def: 'admin',
						type: 'text', // text, checkbox, number, select, password. Select requires
						title: 'user', // see translation in words.js
						options: { // example for select
							name1: 'value1',
							name2: 'value2',
							name3: 'value3'
						}
					},
					{
						name:  'native.password',
						def:   'admin',
						type:  'password',
						title: 'password' // see translation in words.js
					}
				]
			},
			native: {
				"start_range": device.start_range,
				"end_range": device.end_range,
				"ip_list": device.ip_list,
				"ports": device.ports,
				"autostartDiscovery": true
			}
		};
		options.newInstances.push(instance);

		cb && cb(true);
    } else {
		options.log.debug('Onvif. The adapter instance is already installed.');
        cb && cb(false);
    }
}

function s4() {
	return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};

function guid() {
	return (s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4());
};

var Discovery = Object.create(new events.EventEmitter());

function Discovery_probe(options, callback) {
	var cams = []
		, errors = []
		, messageID = 'urn:uuid:' + guid()
		, request = Buffer.from(
			'<Envelope xmlns="http://www.w3.org/2003/05/soap-envelope" xmlns:dn="http://www.onvif.org/ver10/network/wsdl">' +
				'' +
					'<wsa:MessageID xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">' + messageID + '</wsa:MessageID>' +
					'<wsa:To xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>' +
					'<wsa:Action xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action>' +
				'' +
				'' +
					'<Probe xmlns="http://schemas.xmlsoap.org/ws/2005/04/discovery" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
						'<Types>dn:NetworkVideoTransmitter</Types>' +
						'<Scopes />' +
					'</Probe>' +
				'' +
			'</Envelope>'
		)
		, socket = require('dgram').createSocket('udp4');

	socket.on('error', function(err) {
		Discovery.emit('error', err);
		callback(err, null);
	});

	const listener = function(msg, rinfo) {
		let message = msg.toString();
		options.log.debug('Onvif. listener: msg = ' + JSON.stringify(message));
		options.log.debug('Onvif. listener: rinfo = ' + JSON.stringify(rinfo));
		let start = message.indexOf('<d:XAddrs>') + 10;
		if (start > 10){
			let end = message.indexOf('</d:XAddrs>');
			message = message.substring(start, end);
			options.log.debug('Onvif. listener: msg = ' + JSON.stringify(message));
			start = message.indexOf('://') + 3;
			end = message.indexOf('/onvif/');
			message = message.substring(start, end);
			message = message.split(':');
			options.log.debug('Onvif. listener ip: ' + message[0] + ':' + message[1]);
			cams.push({
			'ip': message[0],
			'port': message[1]
			});
		}
	};

	socket.on('message', listener);
	socket.send(request, 0, request.length, 3702, '239.255.255.250');

	setTimeout(function() {
		socket.removeListener('message', listener);
		socket.close();
		callback(null, cams);
	}.bind(this), 5000);
};

function detect(ip, device, options, callback){
    function cb(err, is, ip){
        if (callback){
            callback(err, is, ip);
            callback = null;
        }
    }

	Discovery_probe(options, (err, cams) => {
		if (err) {
			cb && cb(err, false, ip);
		} else {
			let ports = '';
			let ip_list = [];
			let splits;
			let start_range;
			let end_range;
			let count = 0;
			cams.forEach(device => {
				count++;
				if (ports.indexOf(device.port) < 0){
					if (ports.length > 1) {ports = ports + ', ' + device.port}
					else {ports = device.port}
				}
				ip_list.push(device.ip);
				splits = device.ip.split('.');
				splits[3] = 1;
				start_range = splits.join('.');
				splits[3] = 254;
				end_range = splits.join('.');
			});

			device = {
				count: count,
				start_range: start_range,
				end_range: end_range,
				ip_list: ip_list,
				ports: ports
			}
			options.log.debug('Onvif. device = ' + JSON.stringify(device));
			if (count == 0) {
				cb && cb(null, false, ip);
			} else {
				addInstance(device, options, (state) => {
					cb && cb(null, state, ip);
				});
			}
		}
	});
}

exports.detect = detect;
exports.type = ['udp'];
exports.timeout = 6000;
<commit_msg>Update onvif.js<commit_after>'use strict';
const tools = require('../tools.js');
const events = require('events');

function addInstance(device, options, cb){
	// options.newInstances
    // options.existingInstances
    // device - additional info about device
    // options.log - logger
    // options.language - system language
    tools.words['comments'] = {
        "en": "Warning! All Cameras/NVT on the network must have the same user",
		"de": "Warnung! Alle Kameras/NVT im Netzwerk müssen denselben Benutzer haben",
		"ru": "Предупреждение! Все камеры/NVT в сети должны иметь одного и того же пользователя",
		"pt": "Atenção! Todas as câmeras/NVT na rede devem ter o mesmo usuário",
		"nl": "Waarschuwing! Alle camera's/NVT op het netwerk moeten dezelfde gebruiker hebben",
		"fr": "Avertissement! Toutes les caméras/NVT sur le réseau doivent avoir le même utilisateur",
		"it": "Avvertimento! Tutte le telecamere/NVT sulla rete devono avere lo stesso utente",
		"es": "¡Advertencia! Todas las cámaras/NVT en la red deben tener el mismo usuario",
		"pl": "Ostrzeżenie! Wszystkie kamery/NVT w sieci muszą mieć tego samego użytkownika",
		"zh-cn": "警告！网络上的所有摄像机/ NVT必须具有相同的用户"
    };

    let instance = tools.findInstance(options, 'onvif', obj => {
		options.log.debug('Onvif. obj = ' + JSON.stringify(obj));
		return obj.common.name === 'onvif';
	});

    if (!instance){
		let comment = 'Cameras/NVT - ' + device.count + ' (' + tools.translate(options.language, 'comments') + ')';
		const id = tools.getNextInstanceID('onvif', options);

		instance = {
			_id: id,
			common: {
				name:    'onvif',
				enabled: true,
				title:   'Onvif Support'
			},
			comment: {
				add: [comment],
				inputs: [
					{
						name: 'native.user',
						def: 'admin',
						type: 'text', // text, checkbox, number, select, password. Select requires
						title: 'user', // see translation in words.js
						options: { // example for select
							name1: 'value1',
							name2: 'value2',
							name3: 'value3'
						}
					},
					{
						name:  'native.password',
						def:   'admin',
						type:  'password',
						title: 'password' // see translation in words.js
					}
				]
			},
			native: {
				"start_range": device.start_range,
				"end_range": device.end_range,
				"ip_list": device.ip_list,
				"ports": device.ports,
				"autostartDiscovery": true
			}
		};
		options.newInstances.push(instance);

		cb && cb(true);
    } else {
		options.log.debug('Onvif. The adapter instance is already installed.');
        cb && cb(false);
    }
}

function s4() {
	return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};

function guid() {
	return (s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4());
};

var Discovery = Object.create(new events.EventEmitter());

function Discovery_probe(options, callback) {
	var cams = []
		, errors = []
		, messageID = 'urn:uuid:' + guid()
		, request = Buffer.from(
			'<Envelope xmlns="http://www.w3.org/2003/05/soap-envelope" xmlns:dn="http://www.onvif.org/ver10/network/wsdl">' +
				'' +
					'<wsa:MessageID xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">' + messageID + '</wsa:MessageID>' +
					'<wsa:To xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">urn:schemas-xmlsoap-org:ws:2005:04:discovery</wsa:To>' +
					'<wsa:Action xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing">http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe</wsa:Action>' +
				'' +
				'' +
					'<Probe xmlns="http://schemas.xmlsoap.org/ws/2005/04/discovery" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
						'<Types>dn:NetworkVideoTransmitter</Types>' +
						'<Scopes />' +
					'</Probe>' +
				'' +
			'</Envelope>'
		)
		, socket = require('dgram').createSocket('udp4');

	socket.on('error', function(err) {
		Discovery.emit('error', err);
		callback(err, null);
	});

	const listener = function(msg, rinfo) {
		let message = msg.toString();
		options.log.debug('Onvif. listener: msg = ' + JSON.stringify(message));
		options.log.debug('Onvif. listener: rinfo = ' + JSON.stringify(rinfo));
		let start = message.indexOf('<d:XAddrs>') + 10;
		if (start > 10){
			let end = message.indexOf('</d:XAddrs>');
			message = message.substring(start, end);
			options.log.debug('Onvif. listener: msg = ' + JSON.stringify(message));
			start = message.indexOf('://') + 3;
			end = message.indexOf('/onvif/');
			message = message.substring(start, end);
			message = message.split(':');
			options.log.debug('Onvif. listener ip: ' + message[0] + ':' + message[1]);
			cams.push({
			'ip': message[0],
			'port': message[1]
			});
		}
	};

	socket.on('message', listener);
	socket.send(request, 0, request.length, 3702, '239.255.255.250');

	setTimeout(function() {
		socket.removeListener('message', listener);
		socket.close();
		callback(null, cams);
	}.bind(this), 5000);
};

function detect(ip, device, options, callback){
    function cb(err, is, ip){
        if (callback){
            callback(err, is, ip);
            callback = null;
        }
    }

	Discovery_probe(options, (err, cams) => {
		if (err) {
			cb && cb(err, false, ip);
		} else {
			let ports = '';
			let ip_list = [];
			let splits;
			let start_range;
			let end_range;
			let count = 0;
			cams.forEach(device => {
				count++;
				if (ports.indexOf(device.port) < 0){
					if (ports.length > 1) {ports = ports + ', ' + device.port}
					else {ports = device.port}
				}
				ip_list.push(device.ip);
				splits = device.ip.split('.');
				splits[3] = 1;
				start_range = splits.join('.');
				splits[3] = 254;
				end_range = splits.join('.');
			});

			device = {
				count: count,
				start_range: start_range,
				end_range: end_range,
				ip_list: ip_list,
				ports: ports
			}
			options.log.debug('Onvif. device = ' + JSON.stringify(device));
			if (count == 0) {
				cb && cb(null, false, ip);
			} else {
				addInstance(device, options, (state) => {
					cb && cb(null, state, ip);
				});
			}
		}
	});
}

exports.detect = detect;
exports.type = ['udp'];
exports.timeout = 6000;
<|endoftext|>


<commit_before>exports.BattleServer = {
	Settings : {
		"silently resolve rule exceptions" : true // Whether, if a client sends a party, etc. which breaks some of the set rules, BattleServer should just attempt to align the data with the rules, or whether it should send a warning message back to the client
	},
	clients : [],
	battles : [],
	receive : function (message, from) {
		switch (message.action) {
			case "connect":
				var newcomer = from, others = [];
				newcomer.user = message.user;
				newcomer.invitations = {};
				exports.BattleServer.clients.forEach(function (client) {
					others.push({
						user : client.user,
						ip : client.ip
					});
					exports.BattleServer.send({
						action : "users",
						user : {
							user : newcomer.user,
							ip : newcomer.ip
						}
					}, client);
				});
				exports.BattleServer.clients.push(newcomer);
				exports.BattleServer.send({
					action : "users",
					users : others
				}, newcomer);
				break;
			case "disconnect":
				var index = exports.BattleServer.clients.indexOf(from);
				if (index >= 0)
					exports.BattleServer.clients.splice(index, 1);
				index = -1;
				exports.BattleServer.battles.forEach(function (battle, i) {
					if (battle.clientA.client === from) {
						index = i;
						return;
					} else if (battle.clientB.client === from) {
						index = i;
						return;
					}
				});
				if (index >= 0)
					exports.BattleServer.battles.splice(index, 1);
				exports.BattleServer.clients.forEach(function (client) {
					exports.BattleServer.send({
						action : "disconnect",
						who : {
							user : from.user,
							ip : from.ip
						},
					}, client);
				});
				break;
			case "invite":
				if (exports.BattleServer.battleForClient(from) === null) {
					if (exports.BattleServer.validateForRules(from, message.trainer, message.settings.rules, message.reference)) {
						exports.BattleServer.clients.forEach(function (client) {
							if (client !== from && (!message.hasOwnProperty("who") || (client.ip === message.who.ip && client.user === message.who.user)) && !from.invitations.hasOwnProperty(client.user + ":" + client.ip)) {
								exports.BattleServer.send({
									action : "invitation",
									from : {
										user : from.user,
										ip : from.ip
									}
								}, client);
								from.invitations[client.user + ":" + client.ip] = {
									trainer : message.trainer,
									settings : message.settings
								};
							}
						});
					}
				}
				break;
			case "accept":
				if (exports.BattleServer.battleForClient(from) === null) {
					var clientB = from, clientA = null;
					exports.BattleServer.clients.forEach(function (client) {
						if (client !== from && client.ip === message.who.ip && client.user === message.who.user) {
							clientA = client;
							return;
						}
					});
					if (clientA !== null && exports.BattleServer.battleForClient(clientA) === null && clientA.invitations.hasOwnProperty(clientB.user + ":" + clientB.ip)) {
						var storage = clientA.invitations[clientB.user + ":" + clientB.ip];
						if (exports.BattleServer.validateForRules(from, message.trainer, storage.settings.rules, message.reference)) {
							var battle = {
								clientA : {
									client : clientA,
									trainer : storage.trainer
								},
								clientB : {
									client : clientB,
									trainer : message.trainer
								},
								seed : Math.random() * Math.pow(2, 32),
								settings : storage.settings
							};
							exports.BattleServer.battles.push(battle);
							var clientAInfo = {
								user : battle.clientA.client.user,
								ip : battle.clientA.client.ip,
								trainer : battle.clientA.trainer
							}, clientBInfo = {
								user : battle.clientB.client.user,
								ip : battle.clientB.client.ip,
								trainer : battle.clientB.trainer
							};
							exports.BattleServer.send({
								action : "begin",
								team : 0,
								self : clientAInfo,
								other : clientBInfo,
								seed : Battle.seed,
								settings : Battle.settings
							}, clientA);
							exports.BattleServer.send({
								action : "begin",
								team : 1,
								self : clientBInfo,
								other : clientAInfo,
								seed : Battle.seed,
								settings : Battle.settings
							}, clientB);
						}
					}
				}
				break;
			case "reject":
				exports.BattleServer.clients.forEach(function (client) {
					if (client !== from && client.ip === message.who.ip && client.user === message.who.user) {
						delete client.invitations[from.user + ":" + from.ip];
						exports.BattleServer.send({
							action : "reject",
							who : {
								user : from.user,
								ip : from.ip
							}
						}, client);
						return true;
					}
				});
				break;
			case "actions":
				exports.BattleServer.battles.forEach(function (battle) {
					if (battle.clientA.client === from) {
						exports.BattleServer.send({
							action : "actions",
							actions : message.actions
						}, battle.clientB.client);
						return;
					} else if (battle.clientB.client === from) {
						exports.BattleServer.send({
							action : "actions",
							actions : message.actions
						}, battle.clientA.client);
						return;
					}
				});
				break;
			default:
				console.log("Received an unknown action from a client: ", message);
				break;
		}
	},
	send : function (message, to) {
		if (!message.hasOwnProperty("reference"))
			message.reference = (new Date());
		to.send(JSON.stringify([56, message]).slice(1, -1));
		return message.reference;
	},
	validateForRules : function (client, trainer, rules, reference) {
		var valid = true;
		if (trainer.party.length === 0) {
			valid = false;
			exports.BattleServer.send({
				action : "issue",
				message : "You haven't got any Pokémon!",
				reference : reference
			}, client);
			return;
		}
		for (var rule in rules) {
			var setting = rules[rule];
			switch (rule) {
				case "levels":
					switch (setting) {
						case "any":
							// No validation or changes are needed with this setting
							break;
						case "flatten: 50":
							trainer.party.forEach(function (poke) {
								var obj = new pokemon(poke);
								poke.level = 50;
								poke.health = obj.maximumHealth();
								poke.experience = 0;
							});
							break;
						case "flatten: 100":
							trainer.party.forEach(function (poke) {
								var obj = new pokemon(poke);
								poke.level = 100;
								poke.health = obj.maximumHealth();
								poke.experience = 0;
							});
							break;
					}
					break;
				case "party":
					switch (setting) {
						case "up to: 6":
							// No validation or changes are needed with this setting
							break;
						case "up to: 3":
							if (trainer.party.length > 3) {
								if (exports.BattleServer.Settings["silently resolve rule exceptions"]) {
									// Use the first 3 Pokémon in the party
									trainer.party = trainer.party.slice(0, 3);
								} else {
									valid = false;
									exports.BattleServer.send({
										action : "issue",
										message : "Only 3 Pokémon are allowed, but you have " + trainer.party.length + "!",
										reference : reference
									}, client);
								}
							}
							break;
						case "solo":
							if (trainer.party.length > 1) {
								if (exports.BattleServer.Settings["silently resolve rule exceptions"]) {
									// Use the first 3 Pokémon in the party
									trainer.party = trainer.party.slice(0, 1);
								} else {
									valid = false;
									exports.BattleServer.send({
										action : "issue",
										message : "Only one Pokémon is allowed, but you have " + trainer.party.length + "!",
										reference : reference
									}, client);
								}
							}
							break;
					}
					break;
				case "items":
					// This rule is handled by the battle system
					break;
			}
		}
		return valid;
	},
	battleForClient : function (client) {
		var which = null;
		exports.BattleServer.battles.forEach(function (battle) {
			if (battle.clientA.client === client || battle.clientB.client === client) {
				which = battle;
				return;
			}
		});
		return which;
	}
};<commit_msg>More changes to correct BattleServer<commit_after>exports.BattleServer = {
	Settings : {
		"silently resolve rule exceptions" : true // Whether, if a client sends a party, etc. which breaks some of the set rules, BattleServer should just attempt to align the data with the rules, or whether it should send a warning message back to the client
	},
	clients : [],
	battles : [],
	receive : function (message, from) {
		switch (message.action) {
			case "connect":
				var newcomer = from, others = [];
				newcomer.user = message.user;
				newcomer.invitations = {};
				exports.BattleServer.clients.forEach(function (client) {
					others.push({
						user : client.user,
						ip : client.ip
					});
					exports.BattleServer.send({
						action : "users",
						user : {
							user : newcomer.user,
							ip : newcomer.ip
						}
					}, client);
				});
				exports.BattleServer.clients.push(newcomer);
				exports.BattleServer.send({
					action : "users",
					users : others
				}, newcomer);
				break;
			case "disconnect":
				var index = exports.BattleServer.clients.indexOf(from);
				if (index >= 0)
					exports.BattleServer.clients.splice(index, 1);
				index = -1;
				exports.BattleServer.battles.forEach(function (battle, i) {
					if (battle.clientA.client === from) {
						index = i;
						return;
					} else if (battle.clientB.client === from) {
						index = i;
						return;
					}
				});
				if (index >= 0)
					exports.BattleServer.battles.splice(index, 1);
				exports.BattleServer.clients.forEach(function (client) {
					exports.BattleServer.send({
						action : "disconnect",
						who : {
							user : from.user,
							ip : from.ip
						},
					}, client);
				});
				break;
			case "invite":
				if (exports.BattleServer.battleForClient(from) === null) {
					if (exports.BattleServer.validateForRules(from, message.trainer, message.settings.rules, message.reference)) {
						exports.BattleServer.clients.forEach(function (client) {
							if (client !== from && (!message.hasOwnProperty("who") || (client.ip === message.who.ip && client.user === message.who.user)) && !from.invitations.hasOwnProperty(client.user + ":" + client.ip)) {
								exports.BattleServer.send({
									action : "invitation",
									from : {
										user : from.user,
										ip : from.ip
									}
								}, client);
								from.invitations[client.user + ":" + client.ip] = {
									trainer : message.trainer,
									settings : message.settings
								};
							}
						});
					}
				}
				break;
			case "accept":
				if (exports.BattleServer.battleForClient(from) === null) {
					var clientB = from, clientA = null;
					exports.BattleServer.clients.forEach(function (client) {
						if (client !== from && client.ip === message.who.ip && client.user === message.who.user) {
							clientA = client;
							return;
						}
					});
					if (clientA !== null && exports.BattleServer.battleForClient(clientA) === null && clientA.invitations.hasOwnProperty(clientB.user + ":" + clientB.ip)) {
						var storage = clientA.invitations[clientB.user + ":" + clientB.ip];
						if (exports.BattleServer.validateForRules(from, message.trainer, storage.settings.rules, message.reference)) {
							var battle = {
								clientA : {
									client : clientA,
									trainer : storage.trainer
								},
								clientB : {
									client : clientB,
									trainer : message.trainer
								},
								seed : Math.random() * Math.pow(2, 32),
								settings : storage.settings
							};
							exports.BattleServer.battles.push(battle);
							var clientAInfo = {
								user : battle.clientA.client.user,
								ip : battle.clientA.client.ip,
								trainer : battle.clientA.trainer
							}, clientBInfo = {
								user : battle.clientB.client.user,
								ip : battle.clientB.client.ip,
								trainer : battle.clientB.trainer
							};
							exports.BattleServer.send({
								action : "begin",
								team : 0,
								self : clientAInfo,
								other : clientBInfo,
								seed : battle.seed,
								settings : battle.settings
							}, clientA);
							exports.BattleServer.send({
								action : "begin",
								team : 1,
								self : clientBInfo,
								other : clientAInfo,
								seed : battle.seed,
								settings : battle.settings
							}, clientB);
						}
					}
				}
				break;
			case "reject":
				exports.BattleServer.clients.forEach(function (client) {
					if (client !== from && client.ip === message.who.ip && client.user === message.who.user) {
						delete client.invitations[from.user + ":" + from.ip];
						exports.BattleServer.send({
							action : "reject",
							who : {
								user : from.user,
								ip : from.ip
							}
						}, client);
						return true;
					}
				});
				break;
			case "actions":
				exports.BattleServer.battles.forEach(function (battle) {
					if (battle.clientA.client === from) {
						exports.BattleServer.send({
							action : "actions",
							actions : message.actions
						}, battle.clientB.client);
						return;
					} else if (battle.clientB.client === from) {
						exports.BattleServer.send({
							action : "actions",
							actions : message.actions
						}, battle.clientA.client);
						return;
					}
				});
				break;
			default:
				console.log("Received an unknown action from a client: ", message);
				break;
		}
	},
	send : function (message, to) {
		if (!message.hasOwnProperty("reference"))
			message.reference = (new Date());
		to.send(JSON.stringify([56, message]).slice(1, -1));
		return message.reference;
	},
	validateForRules : function (client, trainer, rules, reference) {
		var valid = true;
		if (trainer.party.length === 0) {
			valid = false;
			exports.BattleServer.send({
				action : "issue",
				message : "You haven't got any Pokémon!",
				reference : reference
			}, client);
			return;
		}
		for (var rule in rules) {
			var setting = rules[rule];
			switch (rule) {
				case "levels":
					switch (setting) {
						case "any":
							// No validation or changes are needed with this setting
							break;
						case "flatten: 50":
							trainer.party.forEach(function (poke) {
								var obj = new pokemon(poke);
								poke.level = 50;
								poke.health = obj.maximumHealth();
								poke.experience = 0;
							});
							break;
						case "flatten: 100":
							trainer.party.forEach(function (poke) {
								var obj = new pokemon(poke);
								poke.level = 100;
								poke.health = obj.maximumHealth();
								poke.experience = 0;
							});
							break;
					}
					break;
				case "party":
					switch (setting) {
						case "up to: 6":
							// No validation or changes are needed with this setting
							break;
						case "up to: 3":
							if (trainer.party.length > 3) {
								if (exports.BattleServer.Settings["silently resolve rule exceptions"]) {
									// Use the first 3 Pokémon in the party
									trainer.party = trainer.party.slice(0, 3);
								} else {
									valid = false;
									exports.BattleServer.send({
										action : "issue",
										message : "Only 3 Pokémon are allowed, but you have " + trainer.party.length + "!",
										reference : reference
									}, client);
								}
							}
							break;
						case "solo":
							if (trainer.party.length > 1) {
								if (exports.BattleServer.Settings["silently resolve rule exceptions"]) {
									// Use the first 3 Pokémon in the party
									trainer.party = trainer.party.slice(0, 1);
								} else {
									valid = false;
									exports.BattleServer.send({
										action : "issue",
										message : "Only one Pokémon is allowed, but you have " + trainer.party.length + "!",
										reference : reference
									}, client);
								}
							}
							break;
					}
					break;
				case "items":
					// This rule is handled by the battle system
					break;
			}
		}
		return valid;
	},
	battleForClient : function (client) {
		var which = null;
		exports.BattleServer.battles.forEach(function (battle) {
			if (battle.clientA.client === client || battle.clientB.client === client) {
				which = battle;
				return;
			}
		});
		return which;
	}
};<|endoftext|>