diff options
Diffstat (limited to 'comm/suite/chatzilla/js/lib/command-manager.js')
-rw-r--r-- | comm/suite/chatzilla/js/lib/command-manager.js | 952 |
1 files changed, 952 insertions, 0 deletions
diff --git a/comm/suite/chatzilla/js/lib/command-manager.js b/comm/suite/chatzilla/js/lib/command-manager.js new file mode 100644 index 0000000000..76d2818c3c --- /dev/null +++ b/comm/suite/chatzilla/js/lib/command-manager.js @@ -0,0 +1,952 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// @internal +function getAccessKey(str) +{ + var i = str.indexOf("&"); + if (i == -1) + return ""; + return str[i + 1]; +} + +// @internal +function CommandRecord(name, func, usage, help, label, accesskey, flags, + keystr, tip, format, helpUsage) +{ + this.name = name; + this.func = func; + this._usage = usage; + this.scanUsage(); + this.help = help; + this.label = label ? label : name; + this.accesskey = accesskey ? accesskey : ""; + this.format = format; + this.helpUsage = helpUsage; + this.labelstr = label.replace ("&", ""); + this.tip = tip; + this.flags = flags; + this._enabled = true; + this.keyNodes = new Array(); + this.keystr = keystr; + this.uiElements = new Array(); +} + +CommandRecord.prototype.__defineGetter__ ("enabled", cr_getenable); +function cr_getenable () +{ + return this._enabled; +} + +CommandRecord.prototype.__defineSetter__ ("enabled", cr_setenable); +function cr_setenable (state) +{ + for (var i = 0; i < this.uiElements.length; ++i) + { + if (state) + this.uiElements[i].removeAttribute ("disabled"); + else + this.uiElements[i].setAttribute ("disabled", "true"); + } + return (this._enabled = state); +} + +CommandRecord.prototype.__defineSetter__ ("usage", cr_setusage); +function cr_setusage (usage) +{ + this._usage = usage; + this.scanUsage(); +} + +CommandRecord.prototype.__defineGetter__ ("usage", cr_getusage); +function cr_getusage() +{ + return this._usage; +} + +/** + * @internal + * + * Scans the argument spec, in the format "<a1> <a2> [<o1> <o2>]", into an + * array of strings. + */ +CommandRecord.prototype.scanUsage = +function cr_scanusage() +{ + var spec = this._usage; + var currentName = ""; + var inName = false; + var len = spec.length; + var capNext = false; + + this._usage = spec; + this.argNames = new Array(); + + for (var i = 0; i < len; ++i) + { + switch (spec[i]) + { + case '[': + this.argNames.push (":"); + break; + + case '<': + inName = true; + break; + + case '-': + capNext = true; + break; + + case '>': + inName = false; + this.argNames.push (currentName); + currentName = ""; + capNext = false; + break; + + default: + if (inName) + currentName += capNext ? spec[i].toUpperCase() : spec[i]; + capNext = false; + break; + } + } +} + +/** + * Manages commands, with accelerator keys, help text and argument processing. + * + * You should never need to create an instance of this prototype; access the + * command manager through |client.commandManager|. + * + * @param defaultBundle An |nsIStringBundle| object to load command parameters, + * labels a help text from. + */ +function CommandManager(defaultBundle) +{ + this.commands = new Object(); + this.commandHistory = new Object(); + this.defaultBundle = defaultBundle; + this.currentDispatchDepth = 0; + this.maxDispatchDepth = 10; + this.dispatchUnwinding = false; +} + +// @undocumented +CommandManager.prototype.defaultFlags = 0; + +/** + * Adds multiple commands in a single call. + * + * @param cmdary |Array| containing commands to define; each item in the |Array| + * is also an |Array|, with either 3 or 4 items - corresponding to + * the first three or four arguments of |defineCommand|. An extra + * property, |stringBundle|, may be set on the |cmdary| |Array| + * to override the |defaultBundle| for all the commands. + */ +CommandManager.prototype.defineCommands = +function cmgr_defcmds(cmdary) +{ + var len = cmdary.length; + var commands = new Object(); + var bundle = "stringBundle" in cmdary ? cmdary.stringBundle : null; + + for (var i = 0; i < len; ++i) + { + let name = cmdary[i][0]; + let func = cmdary[i][1]; + let flags = cmdary[i][2]; + let usage = (3 in cmdary[i]) ? cmdary[i][3] : ""; + commands[name] = this.defineCommand(name, func, flags, usage, bundle); + } + + return commands; +} + +/** + * Adds a single command. + * + * @param name The |String| name of the command to define. + * @param func A |Function| to call to handle dispatch of the new command. + * @param flags Optional. A |Number| indicating any special requirements for the + * command. + * @param usage Optional. A |String| specifying the arguments to the command. If + * not specified, then it is assumed there are none. + * @param bundle Optional. An |nsIStringBundle| to fetch parameters, labels, + * accelerator keys and help from. If not specified, the + * |defaultBundle| is used. + */ +CommandManager.prototype.defineCommand = +function cmdmgr_defcmd(name, func, flags, usage, bundle) +{ + if (!bundle) + bundle = this.defaultBundle; + + var helpDefault = MSG_NO_HELP; + var labelDefault = name; + var aliasFor; + + if (typeof flags != "number") + flags = this.defaultFlags; + + if (typeof usage != "string") + usage = ""; + + if (typeof func == "string") + { + var ary = func.match(/(\S+)/); + if (ary) + aliasFor = ary[1]; + else + aliasFor = null; + helpDefault = getMsg (MSG_DEFAULT_ALIAS_HELP, func); + if (aliasFor) + labelDefault = getMsgFrom (bundle, "cmd." + aliasFor + ".label", + null, name); + } + + var label = getMsgFrom(bundle, "cmd." + name + ".label", null, + labelDefault); + var accesskey = getMsgFrom(bundle, "cmd." + name + ".accesskey", null, + getAccessKey(label)); + var help = helpDefault; + var helpUsage = ""; + // Help is only shown for commands that available from the console. + if (flags & CMD_CONSOLE) + { + help = getMsgFrom(bundle, "cmd." + name + ".help", null, helpDefault); + // Only need to lookup localized helpUsage for commands that have them. + if (usage) + { + helpUsage = getMsgFrom(bundle, "cmd." + name + ".helpUsage", null, + ""); + } + } + var keystr = getMsgFrom (bundle, "cmd." + name + ".key", null, ""); + var format = getMsgFrom (bundle, "cmd." + name + ".format", null, null); + var tip = getMsgFrom (bundle, "cmd." + name + ".tip", null, ""); + var command = new CommandRecord(name, func, usage, help, label, accesskey, + flags, keystr, tip, format, helpUsage); + this.addCommand(command); + if (aliasFor) + command.aliasFor = aliasFor; + + return command; +} + +/** + * Installs accelerator keys for commands into an existing document. + * + * @internal + * @param document An |XULDocument| within which to install the accelerator + * keys. Each command's key is installed by |installKey|. + * @param commands Optional. An |Array| or |Object| continaing |CommandRecord| + * objects. If not specified, all commands in the + * |CommandManager| are installed. + */ +CommandManager.prototype.installKeys = +function cmgr_instkeys(document, commands) +{ + var parentElem = document.getElementById("dynamic-keys"); + if (!parentElem) + { + parentElem = document.createElement("keyset"); + parentElem.setAttribute("id", "dynamic-keys"); + document.documentElement.appendChild(parentElem); + } + + if (!commands) + commands = this.commands; + + for (var c in commands) + this.installKey (parentElem, commands[c]); +} + +/** + * Installs the accelerator key for a single command. + * + * This creates a <key> XUL element inside |parentElem|. It should usually be + * called once per command, per document, so that accelerator keys work in all + * application windows. + * + * @internal + * @param parentElem An |XULElement| to add the <key> too. + * @param command The |CommandRecord| to install. + */ +CommandManager.prototype.installKey = +function cmgr_instkey(parentElem, command) +{ + if (!command.keystr) + return; + + var ary = command.keystr.match (/(.*\s)?([\S]+)$/); + if (!ASSERT(ary, "couldn't parse key string ``" + command.keystr + + "'' for command ``" + command.name + "''")) + { + return; + } + + var key = document.createElement ("key"); + key.setAttribute ("id", "key:" + command.name); + key.setAttribute ("oncommand", "dispatch('" + command.name + + "', {isInteractive: true, source: 'keyboard'});"); + + if (ary[1]) + key.setAttribute ("modifiers", ary[1]); + + if (ary[2].indexOf("VK_") == 0) + key.setAttribute ("keycode", ary[2]); + else + key.setAttribute ("key", ary[2]); + + parentElem.appendChild(key); + command.keyNodes.push(key); +} + +/** + * Uninstalls accelerator keys for commands from a document. + * + * @internal + * @param commands Optional. An |Array| or |Object| continaing |CommandRecord| + * objects. If not specified, all commands in the + * |CommandManager| are uninstalled. + */ +CommandManager.prototype.uninstallKeys = +function cmgr_uninstkeys(commands) +{ + if (!commands) + commands = this.commands; + + for (var c in commands) + this.uninstallKey (commands[c]); +} + +/** + * Uninstalls the accelerator key for a single command. + * + * @internal + * @param command The |CommandRecord| to uninstall. + */ +CommandManager.prototype.uninstallKey = +function cmgr_uninstkey(command) +{ + for (var i in command.keyNodes) + { + try + { + /* document may no longer exist in a useful state. */ + command.keyNodes[i].parentNode.removeChild(command.keyNodes[i]); + } + catch (ex) + { + dd ("*** caught exception uninstalling key node: " + ex); + } + } +} + +/** + * Use |defineCommand|. + * + * @internal + * @param command The |CommandRecord| to add to the |CommandManager|. + */ +CommandManager.prototype.addCommand = +function cmgr_add(command) +{ + if (objectContains(this.commands, command.name)) + { + /* We've already got a command with this name - invoke the history + * storage so that we can undo this back to its original state. + */ + if (!objectContains(this.commandHistory, command.name)) + this.commandHistory[command.name] = new Array(); + this.commandHistory[command.name].push(this.commands[command.name]); + } + this.commands[command.name] = command; +} + +/** + * Removes multiple commands in a single call. + * + * @param cmdary An |Array| or |Object| containing |CommandRecord| objects. + * Ideally use the value returned from |defineCommands|. + */ +CommandManager.prototype.removeCommands = +function cmgr_removes(cmdary) +{ + for (var i in cmdary) + { + var command = isinstance(cmdary[i], Array) ? + {name: cmdary[i][0]} : cmdary[i]; + this.removeCommand(command); + } +} + +/** + * Removes a single command. + * + * @param command The |CommandRecord| to remove from the |CommandManager|. + * Ideally use the value returned from |defineCommand|. + */ +CommandManager.prototype.removeCommand = +function cmgr_remove(command) +{ + delete this.commands[command.name]; + if (objectContains(this.commandHistory, command.name)) + { + /* There was a previous command with this name - restore the most + * recent from the history, returning the command to its former glory. + */ + this.commands[command.name] = this.commandHistory[command.name].pop(); + if (this.commandHistory[command.name].length == 0) + delete this.commandHistory[command.name]; + } +} + +/** + * Registers a hook for a particular command. + * + * A command hook is uniquely identified by the pair |id|, |before|; only a + * single hook may exist for a given pair of |id| and |before| values. It is + * wise to use a unique |id|; plugins should construct an |id| using + * |plugin.id|, e.g. |plugin.id + "-my-hook-1"|. + * + * @param commandName A |String| command name to hook. The command named must + * already exist in the |CommandManager|; if it does not, no + * hook is added. + * @param func A |Function| to handle the hook. + * @param id A |String| identifier for the hook. + * @param before A |Boolean| indicating whether the hook wishes to be + * called before or after the command executes. + */ +CommandManager.prototype.addHook = +function cmgr_hook (commandName, func, id, before) +{ + if (!ASSERT(objectContains(this.commands, commandName), + "Unknown command '" + commandName + "'")) + { + return; + } + + var command = this.commands[commandName]; + + if (before) + { + if (!("beforeHooks" in command)) + command.beforeHooks = new Object(); + command.beforeHooks[id] = func; + } + else + { + if (!("afterHooks" in command)) + command.afterHooks = new Object(); + command.afterHooks[id] = func; + } +} + +/** + * Registers multiple hooks for commands. + * + * @param hooks An |Object| containing |Function| objects to call for each + * hook; the key of each item is the name of the command it + * wishes to hook. Optionally, the |_before| property can be + * added to a |function| to override the default |before| value + * of |false|. + * @param prefix Optional. A |String| prefix to apply to each hook's command + * name to compute an |id| for it. + */ +CommandManager.prototype.addHooks = +function cmgr_hooks (hooks, prefix) +{ + if (!prefix) + prefix = ""; + + for (var h in hooks) + { + this.addHook(h, hooks[h], prefix + ":" + h, + ("_before" in hooks[h]) ? hooks[h]._before : false); + } +} + +/** + * Unregisters multiple hooks for commands. + * + * @param hooks An |Object| identical to the one passed to |addHooks|. + * @param prefix Optional. A |String| identical to the one passed to |addHooks|. + */ +CommandManager.prototype.removeHooks = +function cmgr_remhooks (hooks, prefix) +{ + if (!prefix) + prefix = ""; + + for (var h in hooks) + { + this.removeHook(h, prefix + ":" + h, + ("before" in hooks[h]) ? hooks[h].before : false); + } +} + +/** + * Unregisters a hook for a particular command. + * + * The arguments to |removeHook| are the same as |addHook|, but without the + * hook function itself. + * + * @param commandName The |String| command name to unhook. + * @param id The |String| identifier for the hook. + * @param before A |Boolean| indicating whether the hook was to be + * called before or after the command executed. + */ +CommandManager.prototype.removeHook = +function cmgr_unhook (commandName, id, before) +{ + var command = this.commands[commandName]; + + if (before) + delete command.beforeHooks[id]; + else + delete command.afterHooks[id]; +} + +/** + * Gets a sorted |Array| of |CommandRecord| objects which match. + * + * After filtering by |flags| (if specified), if an exact match for + * |partialName| is found, only that is returned; otherwise, all commands + * starting with |partialName| are returned in alphabetical order by |label|. + * + * @param partialName Optional. A |String| prefix to search for. + * @param flags Optional. Flags to logically AND with commands. + */ +CommandManager.prototype.list = +function cmgr_list(partialName, flags, exact) +{ + /* returns array of command objects which look like |partialName|, or + * all commands if |partialName| is not specified */ + function compare (a, b) + { + a = a.labelstr.toLowerCase(); + b = b.labelstr.toLowerCase(); + + if (a == b) + return 0; + + if (a > b) + return 1; + + return -1; + } + + var ary = new Array(); + var commandNames = keys(this.commands); + + for (var name of commandNames) + { + let command = this.commands[name]; + if ((!flags || (command.flags & flags)) && + (!partialName || command.name.startsWith(partialName))) + { + if (exact && partialName && + partialName.length == command.name.length) + { + /* exact match */ + return [command]; + } + ary.push(command); + } + } + + ary.sort(compare); + return ary; +} + +/** + * Gets a sorted |Array| of command names which match. + * + * |listNames| operates identically to |list|, except that only command names + * are returned, not |CommandRecord| objects. + */ +CommandManager.prototype.listNames = +function cmgr_listnames (partialName, flags) +{ + var cmds = this.list(partialName, flags, false); + var cmdNames = new Array(); + + for (var c in cmds) + cmdNames.push (cmds[c].name); + + cmdNames.sort(); + return cmdNames; +} + +/** + * Internal use only. + * + * Called to parse the arguments stored in |e.inputData|, as properties of |e|, + * for the CommandRecord stored on |e.command|. + * + * @params e Event object to be processed. + */ +// @undocumented +CommandManager.prototype.parseArguments = +function cmgr_parseargs (e) +{ + var rv = this.parseArgumentsRaw(e); + //dd("parseArguments '" + e.command.usage + "' " + + // (rv ? "passed" : "failed") + "\n" + dumpObjectTree(e)); + delete e.currentArgIndex; + return rv; +} + +/** + * Internal use only. + * + * Don't call parseArgumentsRaw directly, use parseArguments instead. + * + * Parses the arguments stored in the |inputData| property of the event object, + * according to the format specified by the |command| property. + * + * On success this method returns true, and propery names corresponding to the + * argument names used in the format spec will be created on the event object. + * All optional parameters will be initialized to |null| if not already present + * on the event. + * + * On failure this method returns false and a description of the problem + * will be stored in the |parseError| property of the event. + * + * For example... + * Given the argument spec "<int> <word> [ <word2> <word3> ]", and given the + * input string "411 foo", stored as |e.command.usage| and |e.inputData| + * respectively, this method would add the following propertys to the event + * object... + * -name---value--notes- + * e.int 411 Parsed as an integer + * e.word foo Parsed as a string + * e.word2 null Optional parameters not specified will be set to null. + * e.word3 null If word2 had been provided, word3 would be required too. + * + * Each parameter is parsed by calling the function with the same name, located + * in this.argTypes. The first parameter is parsed by calling the function + * this.argTypes["int"], for example. This function is expected to act on + * e.unparsedData, taking it's chunk, and leaving the rest of the string. + * The default parse functions are... + * <word> parses contiguous non-space characters. + * <int> parses as an int. + * <rest> parses to the end of input data. + * <state> parses yes, on, true, 1, 0, false, off, no as a boolean. + * <toggle> parses like a <state>, except allows "toggle" as well. + * <...> parses according to the parameter type before it, until the end + * of the input data. Results are stored in an array named + * paramnameList, where paramname is the name of the parameter + * before <...>. The value of the parameter before this will be + * paramnameList[0]. + * + * If there is no parse function for an argument type, "word" will be used by + * default. You can alias argument types with code like... + * commandManager.argTypes["my-integer-name"] = commandManager.argTypes["int"]; + */ +// @undocumented +CommandManager.prototype.parseArgumentsRaw = +function parse_parseargsraw (e) +{ + var argc = e.command.argNames.length; + + function initOptionals() + { + for (var i = 0; i < argc; ++i) + { + if (e.command.argNames[i] != ":" && + e.command.argNames[i] != "..." && + !(e.command.argNames[i] in e)) + { + e[e.command.argNames[i]] = null; + } + + if (e.command.argNames[i] == "...") + { + var paramName = e.command.argNames[i - 1]; + if (paramName == ":") + paramName = e.command.argNames[i - 2]; + var listName = paramName + "List"; + if (!(listName in e)) + e[listName] = [ e[paramName] ]; + } + } + } + + if ("inputData" in e && e.inputData) + { + /* if data has been provided, parse it */ + e.unparsedData = e.inputData; + var parseResult; + var currentArg; + e.currentArgIndex = 0; + + if (argc) + { + currentArg = e.command.argNames[e.currentArgIndex]; + + while (e.unparsedData) + { + if (currentArg != ":") + { + if (!this.parseArgument (e, currentArg)) + return false; + } + if (++e.currentArgIndex < argc) + currentArg = e.command.argNames[e.currentArgIndex]; + else + break; + } + + if (e.currentArgIndex < argc && currentArg != ":") + { + /* parse loop completed because it ran out of data. We haven't + * parsed all of the declared arguments, and we're not stopped + * at an optional marker, so we must be missing something + * required... */ + e.parseError = getMsg(MSG_ERR_REQUIRED_PARAM, + e.command.argNames[e.currentArgIndex]); + return false; + } + } + + if (e.unparsedData) + { + /* parse loop completed with unparsed data, which means we've + * successfully parsed all arguments declared. Whine about the + * extra data... */ + display (getMsg(MSG_EXTRA_PARAMS, e.unparsedData), MT_WARN); + } + } + + var rv = this.isCommandSatisfied(e); + if (rv) + initOptionals(); + return rv; +} + +/** + * Returns true if |e| has the properties required to call the command + * |command|. + * + * If |command| is not provided, |e.command| is used instead. + * + * @param e Event object to test against the command. + * @param command Command to test. + */ +// @undocumented +CommandManager.prototype.isCommandSatisfied = +function cmgr_isok (e, command) +{ + if (typeof command == "undefined") + command = e.command; + else if (typeof command == "string") + command = this.commands[command]; + + if (!command.enabled) + return false; + + for (var i = 0; i < command.argNames.length; ++i) + { + if (command.argNames[i] == ":") + return true; + + if (!(command.argNames[i] in e)) + { + e.parseError = getMsg(MSG_ERR_REQUIRED_PARAM, command.argNames[i]); + //dd("command '" + command.name + "' unsatisfied: " + e.parseError); + return false; + } + } + + //dd ("command '" + command.name + "' satisfied."); + return true; +} + +/** + * Internal use only. + * See parseArguments above and the |argTypes| object below. + * + * Parses the next argument by calling an appropriate parser function, or the + * generic "word" parser if none other is found. + * + * @param e event object. + * @param name property name to use for the parse result. + */ +// @undocumented +CommandManager.prototype.parseArgument = +function cmgr_parsearg (e, name) +{ + var parseResult; + + if (name in this.argTypes) + parseResult = this.argTypes[name](e, name, this); + else + parseResult = this.argTypes["word"](e, name, this); + + if (!parseResult) + e.parseError = getMsg(MSG_ERR_INVALID_PARAM, + [name, e.unparsedData]); + + return parseResult; +} + +// @undocumented +CommandManager.prototype.argTypes = new Object(); + +/** + * Convenience function used to map a list of new types to an existing parse + * function. + */ +// @undocumented +CommandManager.prototype.argTypes.__aliasTypes__ = +function at_alias (list, type) +{ + for (var i in list) + { + this[list[i]] = this[type]; + } +} + +/** + * Internal use only. + * + * Parses an integer, stores result in |e[name]|. + */ +// @undocumented +CommandManager.prototype.argTypes["int"] = +function parse_int (e, name) +{ + var ary = e.unparsedData.match (/(\d+)(?:\s+(.*))?$/); + if (!ary) + return false; + e[name] = Number(ary[1]); + e.unparsedData = arrayHasElementAt(ary, 2) ? ary[2] : ""; + return true; +} + +/** + * Internal use only. + * + * Parses a word, which is defined as a list of nonspace characters. + * + * Stores result in |e[name]|. + */ +// @undocumented +CommandManager.prototype.argTypes["word"] = +function parse_word (e, name) +{ + var ary = e.unparsedData.match (/(\S+)(?:\s+(.*))?$/); + if (!ary) + return false; + e[name] = ary[1]; + e.unparsedData = arrayHasElementAt(ary, 2) ? ary[2] : ""; + return true; +} + +/** + * Internal use only. + * + * Parses a "state" which can be "true", "on", "yes", or 1 to indicate |true|, + * or "false", "off", "no", or 0 to indicate |false|. + * + * Stores result in |e[name]|. + */ +// @undocumented +CommandManager.prototype.argTypes["state"] = +function parse_state (e, name) +{ + var ary = + e.unparsedData.match (/(true|on|yes|1|false|off|no|0)(?:\s+(.*))?$/i); + if (!ary) + return false; + if (ary[1].search(/true|on|yes|1/i) != -1) + e[name] = true; + else + e[name] = false; + e.unparsedData = arrayHasElementAt(ary, 2) ? ary[2] : ""; + return true; +} + +/** + * Internal use only. + * + * Parses a "toggle" which can be "true", "on", "yes", or 1 to indicate |true|, + * or "false", "off", "no", or 0 to indicate |false|. In addition, the string + * "toggle" is accepted, in which case |e[name]| will be the string "toggle". + * + * Stores result in |e[name]|. + */ +// @undocumented +CommandManager.prototype.argTypes["toggle"] = +function parse_toggle (e, name) +{ + var ary = e.unparsedData.match + (/(toggle|true|on|yes|1|false|off|no|0)(?:\s+(.*))?$/i); + + if (!ary) + return false; + if (ary[1].search(/toggle/i) != -1) + e[name] = "toggle"; + else if (ary[1].search(/true|on|yes|1/i) != -1) + e[name] = true; + else + e[name] = false; + e.unparsedData = arrayHasElementAt(ary, 2) ? ary[2] : ""; + return true; +} + +/** + * Internal use only. + * + * Returns all unparsed data to the end of the line. + * + * Stores result in |e[name]|. + */ +// @undocumented +CommandManager.prototype.argTypes["rest"] = +function parse_rest (e, name) +{ + e[name] = e.unparsedData; + e.unparsedData = ""; + return true; +} + +/** + * Internal use only. + * + * Parses the rest of the unparsed data the same way the previous argument was + * parsed. Can't be used as the first parameter. if |name| is "..." then the + * name of the previous argument, plus the suffix "List" will be used instead. + * + * Stores result in |e[name]| or |e[lastName + "List"]|. + */ +// @undocumented +CommandManager.prototype.argTypes["..."] = +function parse_repeat (e, name, cm) +{ + ASSERT (e.currentArgIndex > 0, "<...> can't be the first argument."); + + var lastArg = e.command.argNames[e.currentArgIndex - 1]; + if (lastArg == ":") + lastArg = e.command.argNames[e.currentArgIndex - 2]; + + var listName = lastArg + "List"; + e[listName] = [ e[lastArg] ]; + + while (e.unparsedData) + { + if (!cm.parseArgument(e, lastArg)) + return false; + e[listName].push(e[lastArg]); + } + + e[lastArg] = e[listName][0]; + return true; +} |