clockwerk-opensim – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 /*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27  
28 using System;
29 using System.Xml;
30 using System.Collections.Generic;
31 using System.Diagnostics;
32 using System.Linq;
33 using System.Reflection;
34 using System.Text;
35 using System.Text.RegularExpressions;
36 using System.Threading;
37 using log4net;
38 using OpenSim.Framework;
39  
40 namespace OpenSim.Framework.Console
41 {
42 public class Commands : ICommands
43 {
44 // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
45  
46 /// <summary>
47 /// Encapsulates a command that can be invoked from the console
48 /// </summary>
49 private class CommandInfo
50 {
51 /// <value>
52 /// The module from which this command comes
53 /// </value>
54 public string module;
55  
56 /// <value>
57 /// Whether the module is shared
58 /// </value>
59 public bool shared;
60  
61 /// <value>
62 /// Very short BNF description
63 /// </value>
64 public string help_text;
65  
66 /// <value>
67 /// Longer one line help text
68 /// </value>
69 public string long_help;
70  
71 /// <value>
72 /// Full descriptive help for this command
73 /// </value>
74 public string descriptive_help;
75  
76 /// <value>
77 /// The method to invoke for this command
78 /// </value>
79 public List<CommandDelegate> fn;
80 }
81  
82 public const string GeneralHelpText
83 = "To enter an argument that contains spaces, surround the argument with double quotes.\nFor example, show object name \"My long object name\"\n";
84  
85 public const string ItemHelpText
86 = @"For more information, type 'help all' to get a list of all commands,
87 or type help <item>' where <item> is one of the following:";
88  
89 /// <value>
90 /// Commands organized by keyword in a tree
91 /// </value>
92 private Dictionary<string, object> tree =
93 new Dictionary<string, object>();
94  
95 /// <summary>
96 /// Commands organized by module
97 /// </summary>
98 private Dictionary<string, List<CommandInfo>> m_modulesCommands = new Dictionary<string, List<CommandInfo>>();
99  
100 /// <summary>
101 /// Get help for the given help string
102 /// </summary>
103 /// <param name="helpParts">Parsed parts of the help string. If empty then general help is returned.</param>
104 /// <returns></returns>
105 public List<string> GetHelp(string[] cmd)
106 {
107 List<string> help = new List<string>();
108 List<string> helpParts = new List<string>(cmd);
109  
110 // Remove initial help keyword
111 helpParts.RemoveAt(0);
112  
113 help.Add(""); // Will become a newline.
114  
115 // General help
116 if (helpParts.Count == 0)
117 {
118 help.Add(GeneralHelpText);
119 help.Add(ItemHelpText);
120 help.AddRange(CollectModulesHelp(tree));
121 }
122 else if (helpParts.Count == 1 && helpParts[0] == "all")
123 {
124 help.AddRange(CollectAllCommandsHelp());
125 }
126 else
127 {
128 help.AddRange(CollectHelp(helpParts));
129 }
130  
131 help.Add(""); // Will become a newline.
132  
133 return help;
134 }
135  
136 /// <summary>
137 /// Collects the help from all commands and return in alphabetical order.
138 /// </summary>
139 /// <returns></returns>
140 private List<string> CollectAllCommandsHelp()
141 {
142 List<string> help = new List<string>();
143  
144 lock (m_modulesCommands)
145 {
146 foreach (List<CommandInfo> commands in m_modulesCommands.Values)
147 {
148 var ourHelpText = commands.ConvertAll(c => string.Format("{0} - {1}", c.help_text, c.long_help));
149 help.AddRange(ourHelpText);
150 }
151 }
152  
153 help.Sort();
154  
155 return help;
156 }
157  
158 /// <summary>
159 /// See if we can find the requested command in order to display longer help
160 /// </summary>
161 /// <param name="helpParts"></param>
162 /// <returns></returns>
163 private List<string> CollectHelp(List<string> helpParts)
164 {
165 string originalHelpRequest = string.Join(" ", helpParts.ToArray());
166 List<string> help = new List<string>();
167  
168 // Check modules first to see if we just need to display a list of those commands
169 if (TryCollectModuleHelp(originalHelpRequest, help))
170 {
171 help.Insert(0, ItemHelpText);
172 return help;
173 }
174  
175 Dictionary<string, object> dict = tree;
176 while (helpParts.Count > 0)
177 {
178 string helpPart = helpParts[0];
179  
180 if (!dict.ContainsKey(helpPart))
181 break;
182  
183 //m_log.Debug("Found {0}", helpParts[0]);
184  
185 if (dict[helpPart] is Dictionary<string, Object>)
186 dict = (Dictionary<string, object>)dict[helpPart];
187  
188 helpParts.RemoveAt(0);
189 }
190  
191 // There was a command for the given help string
192 if (dict.ContainsKey(String.Empty))
193 {
194 CommandInfo commandInfo = (CommandInfo)dict[String.Empty];
195 help.Add(commandInfo.help_text);
196 help.Add(commandInfo.long_help);
197  
198 string descriptiveHelp = commandInfo.descriptive_help;
199  
200 // If we do have some descriptive help then insert a spacing line before for readability.
201 if (descriptiveHelp != string.Empty)
202 help.Add(string.Empty);
203  
204 help.Add(commandInfo.descriptive_help);
205 }
206 else
207 {
208 help.Add(string.Format("No help is available for {0}", originalHelpRequest));
209 }
210  
211 return help;
212 }
213  
214 /// <summary>
215 /// Try to collect help for the given module if that module exists.
216 /// </summary>
217 /// <param name="moduleName"></param>
218 /// <param name="helpText">/param>
219 /// <returns>true if there was the module existed, false otherwise.</returns>
220 private bool TryCollectModuleHelp(string moduleName, List<string> helpText)
221 {
222 lock (m_modulesCommands)
223 {
224 foreach (string key in m_modulesCommands.Keys)
225 {
226 // Allow topic help requests to succeed whether they are upper or lowercase.
227 if (moduleName.ToLower() == key.ToLower())
228 {
229 List<CommandInfo> commands = m_modulesCommands[key];
230 var ourHelpText = commands.ConvertAll(c => string.Format("{0} - {1}", c.help_text, c.long_help));
231 ourHelpText.Sort();
232 helpText.AddRange(ourHelpText);
233  
234 return true;
235 }
236 }
237  
238 return false;
239 }
240 }
241  
242 private List<string> CollectModulesHelp(Dictionary<string, object> dict)
243 {
244 lock (m_modulesCommands)
245 {
246 List<string> helpText = new List<string>(m_modulesCommands.Keys);
247 helpText.Sort();
248 return helpText;
249 }
250 }
251  
252 // private List<string> CollectHelp(Dictionary<string, object> dict)
253 // {
254 // List<string> result = new List<string>();
255 //
256 // foreach (KeyValuePair<string, object> kvp in dict)
257 // {
258 // if (kvp.Value is Dictionary<string, Object>)
259 // {
260 // result.AddRange(CollectHelp((Dictionary<string, Object>)kvp.Value));
261 // }
262 // else
263 // {
264 // if (((CommandInfo)kvp.Value).long_help != String.Empty)
265 // result.Add(((CommandInfo)kvp.Value).help_text+" - "+
266 // ((CommandInfo)kvp.Value).long_help);
267 // }
268 // }
269 // return result;
270 // }
271  
272 /// <summary>
273 /// Add a command to those which can be invoked from the console.
274 /// </summary>
275 /// <param name="module"></param>
276 /// <param name="command"></param>
277 /// <param name="help"></param>
278 /// <param name="longhelp"></param>
279 /// <param name="fn"></param>
280 public void AddCommand(string module, bool shared, string command,
281 string help, string longhelp, CommandDelegate fn)
282 {
283 AddCommand(module, shared, command, help, longhelp, String.Empty, fn);
284 }
285  
286 /// <summary>
287 /// Add a command to those which can be invoked from the console.
288 /// </summary>
289 /// <param name="module"></param>
290 /// <param name="command"></param>
291 /// <param name="help"></param>
292 /// <param name="longhelp"></param>
293 /// <param name="descriptivehelp"></param>
294 /// <param name="fn"></param>
295 public void AddCommand(string module, bool shared, string command,
296 string help, string longhelp, string descriptivehelp,
297 CommandDelegate fn)
298 {
299 string[] parts = Parser.Parse(command);
300  
301 Dictionary<string, Object> current = tree;
302  
303 foreach (string part in parts)
304 {
305 if (current.ContainsKey(part))
306 {
307 if (current[part] is Dictionary<string, Object>)
308 current = (Dictionary<string, Object>)current[part];
309 else
310 return;
311 }
312 else
313 {
314 current[part] = new Dictionary<string, Object>();
315 current = (Dictionary<string, Object>)current[part];
316 }
317 }
318  
319 CommandInfo info;
320  
321 if (current.ContainsKey(String.Empty))
322 {
323 info = (CommandInfo)current[String.Empty];
324 if (!info.shared && !info.fn.Contains(fn))
325 info.fn.Add(fn);
326  
327 return;
328 }
329  
330 info = new CommandInfo();
331 info.module = module;
332 info.shared = shared;
333 info.help_text = help;
334 info.long_help = longhelp;
335 info.descriptive_help = descriptivehelp;
336 info.fn = new List<CommandDelegate>();
337 info.fn.Add(fn);
338 current[String.Empty] = info;
339  
340 // Now add command to modules dictionary
341 lock (m_modulesCommands)
342 {
343 List<CommandInfo> commands;
344 if (m_modulesCommands.ContainsKey(module))
345 {
346 commands = m_modulesCommands[module];
347 }
348 else
349 {
350 commands = new List<CommandInfo>();
351 m_modulesCommands[module] = commands;
352 }
353  
354 // m_log.DebugFormat("[COMMAND CONSOLE]: Adding to category {0} command {1}", module, command);
355 commands.Add(info);
356 }
357 }
358  
359 public string[] FindNextOption(string[] cmd, bool term)
360 {
361 Dictionary<string, object> current = tree;
362  
363 int remaining = cmd.Length;
364  
365 foreach (string s in cmd)
366 {
367 remaining--;
368  
369 List<string> found = new List<string>();
370  
371 foreach (string opt in current.Keys)
372 {
373 if (remaining > 0 && opt == s)
374 {
375 found.Clear();
376 found.Add(opt);
377 break;
378 }
379 if (opt.StartsWith(s))
380 {
381 found.Add(opt);
382 }
383 }
384  
385 if (found.Count == 1 && (remaining != 0 || term))
386 {
387 current = (Dictionary<string, object>)current[found[0]];
388 }
389 else if (found.Count > 0)
390 {
391 return found.ToArray();
392 }
393 else
394 {
395 break;
396 // return new string[] {"<cr>"};
397 }
398 }
399  
400 if (current.Count > 1)
401 {
402 List<string> choices = new List<string>();
403  
404 bool addcr = false;
405 foreach (string s in current.Keys)
406 {
407 if (s == String.Empty)
408 {
409 CommandInfo ci = (CommandInfo)current[String.Empty];
410 if (ci.fn.Count != 0)
411 addcr = true;
412 }
413 else
414 choices.Add(s);
415 }
416 if (addcr)
417 choices.Add("<cr>");
418 return choices.ToArray();
419 }
420  
421 if (current.ContainsKey(String.Empty))
422 return new string[] { "Command help: "+((CommandInfo)current[String.Empty]).help_text};
423  
424 return new string[] { new List<string>(current.Keys)[0] };
425 }
426  
427 private CommandInfo ResolveCommand(string[] cmd, out string[] result)
428 {
429 result = cmd;
430 int index = -1;
431  
432 Dictionary<string, object> current = tree;
433  
434 foreach (string s in cmd)
435 {
436 index++;
437  
438 List<string> found = new List<string>();
439  
440 foreach (string opt in current.Keys)
441 {
442 if (opt == s)
443 {
444 found.Clear();
445 found.Add(opt);
446 break;
447 }
448 if (opt.StartsWith(s))
449 {
450 found.Add(opt);
451 }
452 }
453  
454 if (found.Count == 1)
455 {
456 result[index] = found[0];
457 current = (Dictionary<string, object>)current[found[0]];
458 }
459 else if (found.Count > 0)
460 {
461 return null;
462 }
463 else
464 {
465 break;
466 }
467 }
468  
469 if (current.ContainsKey(String.Empty))
470 return (CommandInfo)current[String.Empty];
471  
472 return null;
473 }
474  
475 public bool HasCommand(string command)
476 {
477 string[] result;
478 return ResolveCommand(Parser.Parse(command), out result) != null;
479 }
480  
481 public string[] Resolve(string[] cmd)
482 {
483 string[] result;
484 CommandInfo ci = ResolveCommand(cmd, out result);
485  
486 if (ci == null)
487 return new string[0];
488  
489 if (ci.fn.Count == 0)
490 return new string[0];
491  
492 foreach (CommandDelegate fn in ci.fn)
493 {
494 if (fn != null)
495 fn(ci.module, result);
496 else
497 return new string[0];
498 }
499  
500 return result;
501 }
502  
503 public XmlElement GetXml(XmlDocument doc)
504 {
505 CommandInfo help = (CommandInfo)((Dictionary<string, object>)tree["help"])[String.Empty];
506 ((Dictionary<string, object>)tree["help"]).Remove(string.Empty);
507 if (((Dictionary<string, object>)tree["help"]).Count == 0)
508 tree.Remove("help");
509  
510 CommandInfo quit = (CommandInfo)((Dictionary<string, object>)tree["quit"])[String.Empty];
511 ((Dictionary<string, object>)tree["quit"]).Remove(string.Empty);
512 if (((Dictionary<string, object>)tree["quit"]).Count == 0)
513 tree.Remove("quit");
514  
515 XmlElement root = doc.CreateElement("", "HelpTree", "");
516  
517 ProcessTreeLevel(tree, root, doc);
518  
519 if (!tree.ContainsKey("help"))
520 tree["help"] = (object) new Dictionary<string, object>();
521 ((Dictionary<string, object>)tree["help"])[String.Empty] = help;
522  
523 if (!tree.ContainsKey("quit"))
524 tree["quit"] = (object) new Dictionary<string, object>();
525 ((Dictionary<string, object>)tree["quit"])[String.Empty] = quit;
526  
527 return root;
528 }
529  
530 private void ProcessTreeLevel(Dictionary<string, object> level, XmlElement xml, XmlDocument doc)
531 {
532 foreach (KeyValuePair<string, object> kvp in level)
533 {
534 if (kvp.Value is Dictionary<string, Object>)
535 {
536 XmlElement next = doc.CreateElement("", "Level", "");
537 next.SetAttribute("Name", kvp.Key);
538  
539 xml.AppendChild(next);
540  
541 ProcessTreeLevel((Dictionary<string, object>)kvp.Value, next, doc);
542 }
543 else
544 {
545 CommandInfo c = (CommandInfo)kvp.Value;
546  
547 XmlElement cmd = doc.CreateElement("", "Command", "");
548  
549 XmlElement e;
550  
551 e = doc.CreateElement("", "Module", "");
552 cmd.AppendChild(e);
553 e.AppendChild(doc.CreateTextNode(c.module));
554  
555 e = doc.CreateElement("", "Shared", "");
556 cmd.AppendChild(e);
557 e.AppendChild(doc.CreateTextNode(c.shared.ToString()));
558  
559 e = doc.CreateElement("", "HelpText", "");
560 cmd.AppendChild(e);
561 e.AppendChild(doc.CreateTextNode(c.help_text));
562  
563 e = doc.CreateElement("", "LongHelp", "");
564 cmd.AppendChild(e);
565 e.AppendChild(doc.CreateTextNode(c.long_help));
566  
567 e = doc.CreateElement("", "Description", "");
568 cmd.AppendChild(e);
569 e.AppendChild(doc.CreateTextNode(c.descriptive_help));
570  
571 xml.AppendChild(cmd);
572 }
573 }
574 }
575  
576 public void FromXml(XmlElement root, CommandDelegate fn)
577 {
578 CommandInfo help = (CommandInfo)((Dictionary<string, object>)tree["help"])[String.Empty];
579 ((Dictionary<string, object>)tree["help"]).Remove(string.Empty);
580 if (((Dictionary<string, object>)tree["help"]).Count == 0)
581 tree.Remove("help");
582  
583 CommandInfo quit = (CommandInfo)((Dictionary<string, object>)tree["quit"])[String.Empty];
584 ((Dictionary<string, object>)tree["quit"]).Remove(string.Empty);
585 if (((Dictionary<string, object>)tree["quit"]).Count == 0)
586 tree.Remove("quit");
587  
588 tree.Clear();
589  
590 ReadTreeLevel(tree, root, fn);
591  
592 if (!tree.ContainsKey("help"))
593 tree["help"] = (object) new Dictionary<string, object>();
594 ((Dictionary<string, object>)tree["help"])[String.Empty] = help;
595  
596 if (!tree.ContainsKey("quit"))
597 tree["quit"] = (object) new Dictionary<string, object>();
598 ((Dictionary<string, object>)tree["quit"])[String.Empty] = quit;
599 }
600  
601 private void ReadTreeLevel(Dictionary<string, object> level, XmlNode node, CommandDelegate fn)
602 {
603 Dictionary<string, object> next;
604 string name;
605  
606 XmlNodeList nodeL = node.ChildNodes;
607 XmlNodeList cmdL;
608 CommandInfo c;
609  
610 foreach (XmlNode part in nodeL)
611 {
612 switch (part.Name)
613 {
614 case "Level":
615 name = ((XmlElement)part).GetAttribute("Name");
616 next = new Dictionary<string, object>();
617 level[name] = next;
618 ReadTreeLevel(next, part, fn);
619 break;
620 case "Command":
621 cmdL = part.ChildNodes;
622 c = new CommandInfo();
623 foreach (XmlNode cmdPart in cmdL)
624 {
625 switch (cmdPart.Name)
626 {
627 case "Module":
628 c.module = cmdPart.InnerText;
629 break;
630 case "Shared":
631 c.shared = Convert.ToBoolean(cmdPart.InnerText);
632 break;
633 case "HelpText":
634 c.help_text = cmdPart.InnerText;
635 break;
636 case "LongHelp":
637 c.long_help = cmdPart.InnerText;
638 break;
639 case "Description":
640 c.descriptive_help = cmdPart.InnerText;
641 break;
642 }
643 }
644 c.fn = new List<CommandDelegate>();
645 c.fn.Add(fn);
646 level[String.Empty] = c;
647 break;
648 }
649 }
650 }
651 }
652  
653 public class Parser
654 {
655 // If an unquoted portion ends with an element matching this regex
656 // and the next element contains a space, then we have stripped
657 // embedded quotes that should not have been stripped
658 private static Regex optionRegex = new Regex("^--[a-zA-Z0-9-]+=$");
659  
660 public static string[] Parse(string text)
661 {
662 List<string> result = new List<string>();
663  
664 int index;
665  
666 string[] unquoted = text.Split(new char[] {'"'});
667  
668 for (index = 0 ; index < unquoted.Length ; index++)
669 {
670 if (index % 2 == 0)
671 {
672 string[] words = unquoted[index].Split(new char[] {' '});
673  
674 bool option = false;
675 foreach (string w in words)
676 {
677 if (w != String.Empty)
678 {
679 if (optionRegex.Match(w) == Match.Empty)
680 option = false;
681 else
682 option = true;
683 result.Add(w);
684 }
685 }
686 // The last item matched the regex, put the quotes back
687 if (option)
688 {
689 // If the line ended with it, don't do anything
690 if (index < (unquoted.Length - 1))
691 {
692 // Get and remove the option name
693 string optionText = result[result.Count - 1];
694 result.RemoveAt(result.Count - 1);
695  
696 // Add the quoted value back
697 optionText += "\"" + unquoted[index + 1] + "\"";
698  
699 // Push the result into our return array
700 result.Add(optionText);
701  
702 // Skip the already used value
703 index++;
704 }
705 }
706 }
707 else
708 {
709 result.Add(unquoted[index]);
710 }
711 }
712  
713 return result.ToArray();
714 }
715 }
716  
717 /// <summary>
718 /// A console that processes commands internally
719 /// </summary>
720 public class CommandConsole : ConsoleBase, ICommandConsole
721 {
722 // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
723  
724 public event OnOutputDelegate OnOutput;
725  
726 public ICommands Commands { get; private set; }
727  
728 public CommandConsole(string defaultPrompt) : base(defaultPrompt)
729 {
730 Commands = new Commands();
731  
732 Commands.AddCommand(
733 "Help", false, "help", "help [<item>]",
734 "Display help on a particular command or on a list of commands in a category", Help);
735 }
736  
737 private void Help(string module, string[] cmd)
738 {
739 List<string> help = Commands.GetHelp(cmd);
740  
741 foreach (string s in help)
742 Output(s);
743 }
744  
745 protected void FireOnOutput(string text)
746 {
747 OnOutputDelegate onOutput = OnOutput;
748 if (onOutput != null)
749 onOutput(text);
750 }
751  
752 /// <summary>
753 /// Display a command prompt on the console and wait for user input
754 /// </summary>
755 public void Prompt()
756 {
757 string line = ReadLine(DefaultPrompt + "# ", true, true);
758  
759 if (line != String.Empty)
760 Output("Invalid command");
761 }
762  
763 public void RunCommand(string cmd)
764 {
765 string[] parts = Parser.Parse(cmd);
766 Commands.Resolve(parts);
767 }
768  
769 public override string ReadLine(string p, bool isCommand, bool e)
770 {
771 System.Console.Write("{0}", p);
772 string cmdinput = System.Console.ReadLine();
773  
774 if (isCommand)
775 {
776 string[] cmd = Commands.Resolve(Parser.Parse(cmdinput));
777  
778 if (cmd.Length != 0)
779 {
780 int i;
781  
782 for (i=0 ; i < cmd.Length ; i++)
783 {
784 if (cmd[i].Contains(" "))
785 cmd[i] = "\"" + cmd[i] + "\"";
786 }
787 return String.Empty;
788 }
789 }
790 return cmdinput;
791 }
792 }
793 }