1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3
* The contents of this file are subject to the Mozilla Public
4
* License Version 1.1 (the "License"); you may not use this file
5
* except in compliance with the License. You may obtain a copy of
6
* the License at http://www.mozilla.org/MPL/
8
* Software distributed under the License is distributed on an "AS
9
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
10
* implied. See the License for the specific language governing
11
* rights and limitations under the License.
13
* The Original Code is JSIRC Library
15
* The Initial Developer of the Original Code is New Dimensions Consulting,
16
* Inc. Portions created by New Dimensions Consulting, Inc. are
17
* Copyright (C) 1999 New Dimenstions Consulting, Inc. All
21
* Robert Ginda, rginda@ndcico.com, original author
24
function getAccessKey (str)
26
var i = str.indexOf("&");
32
function CommandRecord (name, func, usage, help, label, flags, keystr, tip,
40
this.label = label ? label : name;
42
this.labelstr = label.replace ("&", "");
46
this.keyNodes = new Array();
48
this.uiElements = new Array();
51
CommandRecord.prototype.__defineGetter__ ("enabled", cr_getenable);
52
function cr_getenable ()
57
CommandRecord.prototype.__defineSetter__ ("enabled", cr_setenable);
58
function cr_setenable (state)
60
for (var i = 0; i < this.uiElements.length; ++i)
63
this.uiElements[i].removeAttribute ("disabled");
65
this.uiElements[i].setAttribute ("disabled", "true");
67
return (this._enabled = state);
70
CommandRecord.prototype.__defineSetter__ ("usage", cr_setusage);
71
function cr_setusage (usage)
77
CommandRecord.prototype.__defineGetter__ ("usage", cr_getusage);
78
function cr_getusage()
86
* Scans the argument spec, in the format "<a1> <a2> [<o1> <o2>]", into an
89
CommandRecord.prototype.scanUsage =
90
function cr_scanusage()
92
var spec = this._usage;
95
var len = spec.length;
99
this.argNames = new Array();
101
for (var i = 0; i < len; ++i)
106
this.argNames.push (":");
119
this.argNames.push (currentName);
126
currentName += capNext ? spec[i].toUpperCase() : spec[i];
134
* Returns the command formatted for presentation as part of online
137
CommandRecord.prototype.getDocumentation =
138
function cr_getdocs(flagFormatter)
142
str = getMsg(MSG_DOC_COMMANDLABEL,
143
[this.label.replace("&", ""), this.name]) + "\n";
144
str += getMsg(MSG_DOC_KEY, this.keystr ? this.keystr : MSG_VAL_NA) + "\n";
145
str += getMsg(MSG_DOC_SYNTAX, [this.name, this.usage]) + "\n";
146
str += MSG_DOC_NOTES + "\n";
147
str += (flagFormatter ? flagFormatter(this.flags) : this.flags) + "\n\n";
148
str += MSG_DOC_DESCRIPTION + "\n";
149
str += wrapText(this.help, 75) + "\n";
153
CommandRecord.prototype.argNames = new Array();
155
function CommandManager (defaultBundle)
157
this.commands = new Object();
158
this.defaultBundle = defaultBundle;
161
CommandManager.prototype.defaultFlags = 0;
163
CommandManager.prototype.defineCommands =
164
function cmgr_defcmds (cmdary)
166
var len = cmdary.length;
167
var commands = new Object();
168
var bundle = "stringBundle" in cmdary ? cmdary.stringBundle : null;
170
for (var i = 0; i < len; ++i)
172
var name = cmdary[i][0];
173
var func = cmdary[i][1];
174
var flags = cmdary[i][2];
177
usage = cmdary[i][3];
179
var command = this.defineCommand(name, func, flags, usage, bundle);
180
commands[name] = command;
187
CommandManager.prototype.defineCommand =
188
function cmdmgr_defcmd (name, func, flags, usage, bundle)
191
bundle = this.defaultBundle;
194
var labelDefault = name;
197
if (typeof flags != "number")
198
flags = this.defaultFlags;
200
if (flags & CMD_NO_HELP)
201
helpDefault = MSG_NO_HELP;
203
if (typeof usage != "string")
204
usage = getMsgFrom(bundle, "cmd." + name + ".params", null, "");
206
if (typeof func == "string")
208
var ary = func.match(/(\S+)/);
213
helpDefault = getMsg (MSG_DEFAULT_ALIAS_HELP, func);
215
labelDefault = getMsgFrom (bundle, "cmd." + aliasFor + ".label",
219
var label = getMsgFrom(bundle, "cmd." + name + ".label", null,
221
var help = getMsgFrom(bundle, "cmd." + name + ".help", null,
223
var keystr = getMsgFrom (bundle, "cmd." + name + ".key", null, "");
224
var format = getMsgFrom (bundle, "cmd." + name + ".format", null, null);
225
var tip = getMsgFrom (bundle, "cmd." + name + ".tip", null, "");
226
var command = new CommandRecord (name, func, usage, help, label, flags,
227
keystr, tip, format);
228
this.addCommand(command);
230
command.aliasFor = aliasFor;
235
CommandManager.prototype.installKeys =
236
function cmgr_instkeys (document, commands)
238
var parentElem = document.getElementById("dynamic-keys");
241
parentElem = document.createElement("keyset");
242
parentElem.setAttribute ("id", "dynamic-keys");
243
document.firstChild.appendChild (parentElem);
247
commands = this.commands;
249
for (var c in commands)
250
this.installKey (parentElem, commands[c]);
254
* Create a <key> node relative to a DOM node. Usually called once per command,
255
* per document, so that accelerator keys work in all application windows.
257
* @param parentElem A reference to the DOM node which should contain the new
259
* @param command reference to the CommandRecord to install.
261
CommandManager.prototype.installKey =
262
function cmgr_instkey (parentElem, command)
267
var ary = command.keystr.match (/(.*\s)?([\S]+)$/);
268
if (!ASSERT(ary, "couldn't parse key string ``" + command.keystr +
269
"'' for command ``" + command.name + "''"))
274
var key = document.createElement ("key");
275
key.setAttribute ("id", "key:" + command.name);
276
key.setAttribute ("oncommand", "dispatch('" + command.name +
277
"', {isInteractive: true});");
278
key.setAttribute ("modifiers", ary[1]);
279
if (ary[2].indexOf("VK_") == 0)
280
key.setAttribute ("keycode", ary[2]);
282
key.setAttribute ("key", ary[2]);
284
parentElem.appendChild(key);
285
command.keyNodes.push(key);
288
CommandManager.prototype.uninstallKeys =
289
function cmgr_uninstkeys (commands)
292
commands = this.commands;
294
for (var c in commands)
295
this.uninstallKey (commands[c]);
298
CommandManager.prototype.uninstallKey =
299
function cmgr_uninstkey (command)
301
for (var i in command.keyNodes)
305
/* document may no longer exist in a useful state. */
306
command.keyNodes[i].parentNode.removeChild(command.keyNodes[i]);
310
dd ("*** caught exception uninstalling key node: " + ex);
316
* Register a new command with the manager.
318
CommandManager.prototype.addCommand =
319
function cmgr_add (command)
321
this.commands[command.name] = command;
324
CommandManager.prototype.removeCommands =
325
function cmgr_removes (commands)
327
for (var c in commands)
328
this.removeCommand(commands[c]);
331
CommandManager.prototype.removeCommand =
332
function cmgr_remove (command)
334
delete this.commands[command.name];
338
* Register a hook for a particular command name. |id| is a human readable
339
* identifier for the hook, and can be used to unregister the hook. If you
340
* re-use a hook id, the previous hook function will be replaced.
341
* If |before| is |true|, the hook will be called *before* the command executes,
342
* if |before| is |false|, or not specified, the hook will be called *after*
343
* the command executes.
345
CommandManager.prototype.addHook =
346
function cmgr_hook (commandName, func, id, before)
348
if (!ASSERT(commandName in this.commands,
349
"Unknown command '" + commandName + "'"))
354
var command = this.commands[commandName];
358
if (!("beforeHooks" in command))
359
command.beforeHooks = new Object();
360
command.beforeHooks[id] = func;
364
if (!("afterHooks" in command))
365
command.afterHooks = new Object();
366
command.afterHooks[id] = func;
370
CommandManager.prototype.addHooks =
371
function cmgr_hooks (hooks, prefix)
378
this.addHook(h, hooks[h], prefix + ":" + h,
379
("_before" in hooks[h]) ? hooks[h]._before : false);
383
CommandManager.prototype.removeHooks =
384
function cmgr_remhooks (hooks, prefix)
391
this.removeHook(h, prefix + ":" + h,
392
("before" in hooks[h]) ? hooks[h].before : false);
396
CommandManager.prototype.removeHook =
397
function cmgr_unhook (commandName, id, before)
399
var command = this.commands[commandName];
402
delete command.beforeHooks[id];
404
delete command.afterHooks[id];
408
* Return an array of all CommandRecords which start with the string
409
* |partialName|, sorted by |label| property.
411
* @param partialName Prefix to search for.
412
* @param flags logical ANDed with command flags.
413
* @returns array Array of matching commands, sorted by |label| property.
415
CommandManager.prototype.list =
416
function cmgr_list (partialName, flags)
418
/* returns array of command objects which look like |partialName|, or
419
* all commands if |partialName| is not specified */
420
function compare (a, b)
422
a = a.labelstr.toLowerCase();
423
b = b.labelstr.toLowerCase();
434
var ary = new Array();
435
var commandNames = keys(this.commands);
437
/* a command named "eval" wouldn't show up in the result of keys() because
438
* eval is not-enumerable, even if overwritten. */
439
if ("eval" in this.commands && typeof this.commands.eval == "object")
440
commandNames.push ("eval");
442
for (var i in commandNames)
444
var name = commandNames[i];
445
if (!flags || (this.commands[name].flags & flags))
448
this.commands[name].name.indexOf(partialName) == 0)
451
partialName.length == this.commands[name].name.length)
454
return [this.commands[name]];
458
ary.push (this.commands[name]);
469
* Return a sorted array of the command names which start with the string
472
* @param partialName Prefix to search for.
473
* @param flags logical ANDed with command flags.
474
* @returns array Sorted Array of matching command names.
476
CommandManager.prototype.listNames =
477
function cmgr_listnames (partialName, flags)
479
var cmds = this.list(partialName, flags);
480
var cmdNames = new Array();
483
cmdNames.push (cmds[c].name);
492
* Called to parse the arguments stored in |e.inputData|, as properties of |e|,
493
* for the CommandRecord stored on |e.command|.
495
* @params e Event object to be processed.
497
CommandManager.prototype.parseArguments =
498
function cmgr_parseargs (e)
500
var rv = this.parseArgumentsRaw(e);
501
//dd("parseArguments '" + e.command.usage + "' " +
502
// (rv ? "passed" : "failed") + "\n" + dumpObjectTree(e));
503
delete e.currentArgIndex;
510
* Don't call parseArgumentsRaw directly, use parseArguments instead.
512
* Parses the arguments stored in the |inputData| property of the event object,
513
* according to the format specified by the |command| property.
515
* On success this method returns true, and propery names corresponding to the
516
* argument names used in the format spec will be created on the event object.
517
* All optional parameters will be initialized to |null| if not already present
520
* On failure this method returns false and a description of the problem
521
* will be stored in the |parseError| property of the event.
524
* Given the argument spec "<int> <word> [ <word2> <word3> ]", and given the
525
* input string "411 foo", stored as |e.command.usage| and |e.inputData|
526
* respectively, this method would add the following propertys to the event
528
* -name---value--notes-
529
* e.int 411 Parsed as an integer
530
* e.word foo Parsed as a string
531
* e.word2 null Optional parameters not specified will be set to null.
532
* e.word3 null If word2 had been provided, word3 would be required too.
534
* Each parameter is parsed by calling the function with the same name, located
535
* in this.argTypes. The first parameter is parsed by calling the function
536
* this.argTypes["int"], for example. This function is expected to act on
537
* e.unparsedData, taking it's chunk, and leaving the rest of the string.
538
* The default parse functions are...
539
* <word> parses contiguous non-space characters.
540
* <int> parses as an int.
541
* <rest> parses to the end of input data.
542
* <state> parses yes, on, true, 1, 0, false, off, no as a boolean.
543
* <toggle> parses like a <state>, except allows "toggle" as well.
544
* <...> parses according to the parameter type before it, until the end
545
* of the input data. Results are stored in an array named
546
* paramnameList, where paramname is the name of the parameter
547
* before <...>. The value of the parameter before this will be
550
* If there is no parse function for an argument type, "word" will be used by
551
* default. You can alias argument types with code like...
552
* commandManager.argTypes["my-integer-name"] = commandManager.argTypes["int"];
554
CommandManager.prototype.parseArgumentsRaw =
555
function parse_parseargsraw (e)
557
var argc = e.command.argNames.length;
559
function initOptionals()
561
for (var i = 0; i < argc; ++i)
563
if (e.command.argNames[i] != ":" &&
564
e.command.argNames[i] != "..." &&
565
!(e.command.argNames[i] in e))
567
e[e.command.argNames[i]] = null;
570
if (e.command.argNames[i] == "...")
572
var paramName = e.command.argNames[i - 1];
573
if (paramName == ":")
574
paramName = e.command.argNames[i - 2];
575
var listName = paramName + "List";
576
if (!(listName in e))
577
e[listName] = [ e[paramName] ];
582
if ("inputData" in e && e.inputData)
584
/* if data has been provided, parse it */
585
e.unparsedData = e.inputData;
588
e.currentArgIndex = 0;
592
currentArg = e.command.argNames[e.currentArgIndex];
594
while (e.unparsedData)
596
if (currentArg != ":")
598
if (!this.parseArgument (e, currentArg))
601
if (++e.currentArgIndex < argc)
602
currentArg = e.command.argNames[e.currentArgIndex];
607
if (e.currentArgIndex < argc && currentArg != ":")
609
/* parse loop completed because it ran out of data. We haven't
610
* parsed all of the declared arguments, and we're not stopped
611
* at an optional marker, so we must be missing something
613
e.parseError = getMsg(MSG_ERR_REQUIRED_PARAM,
614
e.command.argNames[e.currentArgIndex]);
621
/* parse loop completed with unparsed data, which means we've
622
* successfully parsed all arguments declared. Whine about the
624
display (getMsg(MSG_EXTRA_PARAMS, e.unparsedData), MT_WARN);
628
var rv = this.isCommandSatisfied(e);
635
* Returns true if |e| has the properties required to call the command
638
* If |command| is not provided, |e.command| is used instead.
640
* @param e Event object to test against the command.
641
* @param command Command to test.
643
CommandManager.prototype.isCommandSatisfied =
644
function cmgr_isok (e, command)
646
if (typeof command == "undefined")
648
else if (typeof command == "string")
649
command = this.commands[command];
651
if (!command.enabled)
654
for (var i = 0; i < command.argNames.length; ++i)
656
if (command.argNames[i] == ":")
659
if (!(command.argNames[i] in e))
661
e.parseError = getMsg(MSG_ERR_REQUIRED_PARAM, command.argNames[i]);
662
//dd("command '" + command.name + "' unsatisfied: " + e.parseError);
667
//dd ("command '" + command.name + "' satisfied.");
673
* See parseArguments above and the |argTypes| object below.
675
* Parses the next argument by calling an appropriate parser function, or the
676
* generic "word" parser if none other is found.
678
* @param e event object.
679
* @param name property name to use for the parse result.
681
CommandManager.prototype.parseArgument =
682
function cmgr_parsearg (e, name)
686
if (name in this.argTypes)
687
parseResult = this.argTypes[name](e, name, this);
689
parseResult = this.argTypes["word"](e, name, this);
692
e.parseError = getMsg(MSG_ERR_INVALID_PARAM,
693
[name, e.unparsedData]);
698
CommandManager.prototype.argTypes = new Object();
701
* Convenience function used to map a list of new types to an existing parse
704
CommandManager.prototype.argTypes.__aliasTypes__ =
705
function at_alias (list, type)
709
this[list[i]] = this[type];
716
* Parses an integer, stores result in |e[name]|.
718
CommandManager.prototype.argTypes["int"] =
719
function parse_int (e, name)
721
var ary = e.unparsedData.match (/(\d+)(?:\s+(.*))?$/);
724
e[name] = Number(ary[1]);
725
e.unparsedData = arrayHasElementAt(ary, 2) ? ary[2] : "";
732
* Parses a word, which is defined as a list of nonspace characters.
734
* Stores result in |e[name]|.
736
CommandManager.prototype.argTypes["word"] =
737
function parse_word (e, name)
739
var ary = e.unparsedData.match (/(\S+)(?:\s+(.*))?$/);
743
e.unparsedData = arrayHasElementAt(ary, 2) ? ary[2] : "";
750
* Parses a "state" which can be "true", "on", "yes", or 1 to indicate |true|,
751
* or "false", "off", "no", or 0 to indicate |false|.
753
* Stores result in |e[name]|.
755
CommandManager.prototype.argTypes["state"] =
756
function parse_state (e, name)
759
e.unparsedData.match (/(true|on|yes|1|false|off|no|0)(?:\s+(.*))?$/i);
762
if (ary[1].search(/true|on|yes|1/i) != -1)
766
e.unparsedData = arrayHasElementAt(ary, 2) ? ary[2] : "";
773
* Parses a "toggle" which can be "true", "on", "yes", or 1 to indicate |true|,
774
* or "false", "off", "no", or 0 to indicate |false|. In addition, the string
775
* "toggle" is accepted, in which case |e[name]| will be the string "toggle".
777
* Stores result in |e[name]|.
779
CommandManager.prototype.argTypes["toggle"] =
780
function parse_toggle (e, name)
782
var ary = e.unparsedData.match
783
(/(toggle|true|on|yes|1|false|off|no|0)(?:\s+(.*))?$/i);
787
if (ary[1].search(/toggle/i) != -1)
789
else if (ary[1].search(/true|on|yes|1/i) != -1)
793
e.unparsedData = arrayHasElementAt(ary, 2) ? ary[2] : "";
800
* Returns all unparsed data to the end of the line.
802
* Stores result in |e[name]|.
804
CommandManager.prototype.argTypes["rest"] =
805
function parse_rest (e, name)
807
e[name] = e.unparsedData;
815
* Parses the rest of the unparsed data the same way the previous argument was
816
* parsed. Can't be used as the first parameter. if |name| is "..." then the
817
* name of the previous argument, plus the suffix "List" will be used instead.
819
* Stores result in |e[name]| or |e[lastName + "List"]|.
821
CommandManager.prototype.argTypes["..."] =
822
function parse_repeat (e, name, cm)
824
ASSERT (e.currentArgIndex > 0, "<...> can't be the first argument.");
826
var lastArg = e.command.argNames[e.currentArgIndex - 1];
828
lastArg = e.command.argNames[e.currentArgIndex - 2];
830
var listName = lastArg + "List";
831
e[listName] = [ e[lastArg] ];
833
while (e.unparsedData)
835
if (!cm.parseArgument(e, lastArg))
837
e[listName].push(e[lastArg]);
840
e[lastArg] = e[listName][0];