clockwerk-opensim-stable – 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 public string[] Resolve(string[] cmd)
428 {
429 string[] 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 new string[0];
462 }
463 else
464 {
465 break;
466 }
467 }
468  
469 if (current.ContainsKey(String.Empty))
470 {
471 CommandInfo ci = (CommandInfo)current[String.Empty];
472 if (ci.fn.Count == 0)
473 return new string[0];
474 foreach (CommandDelegate fn in ci.fn)
475 {
476 if (fn != null)
477 fn(ci.module, result);
478 else
479 return new string[0];
480 }
481 return result;
482 }
483  
484 return new string[0];
485 }
486  
487 public XmlElement GetXml(XmlDocument doc)
488 {
489 CommandInfo help = (CommandInfo)((Dictionary<string, object>)tree["help"])[String.Empty];
490 ((Dictionary<string, object>)tree["help"]).Remove(string.Empty);
491 if (((Dictionary<string, object>)tree["help"]).Count == 0)
492 tree.Remove("help");
493  
494 CommandInfo quit = (CommandInfo)((Dictionary<string, object>)tree["quit"])[String.Empty];
495 ((Dictionary<string, object>)tree["quit"]).Remove(string.Empty);
496 if (((Dictionary<string, object>)tree["quit"]).Count == 0)
497 tree.Remove("quit");
498  
499 XmlElement root = doc.CreateElement("", "HelpTree", "");
500  
501 ProcessTreeLevel(tree, root, doc);
502  
503 if (!tree.ContainsKey("help"))
504 tree["help"] = (object) new Dictionary<string, object>();
505 ((Dictionary<string, object>)tree["help"])[String.Empty] = help;
506  
507 if (!tree.ContainsKey("quit"))
508 tree["quit"] = (object) new Dictionary<string, object>();
509 ((Dictionary<string, object>)tree["quit"])[String.Empty] = quit;
510  
511 return root;
512 }
513  
514 private void ProcessTreeLevel(Dictionary<string, object> level, XmlElement xml, XmlDocument doc)
515 {
516 foreach (KeyValuePair<string, object> kvp in level)
517 {
518 if (kvp.Value is Dictionary<string, Object>)
519 {
520 XmlElement next = doc.CreateElement("", "Level", "");
521 next.SetAttribute("Name", kvp.Key);
522  
523 xml.AppendChild(next);
524  
525 ProcessTreeLevel((Dictionary<string, object>)kvp.Value, next, doc);
526 }
527 else
528 {
529 CommandInfo c = (CommandInfo)kvp.Value;
530  
531 XmlElement cmd = doc.CreateElement("", "Command", "");
532  
533 XmlElement e;
534  
535 e = doc.CreateElement("", "Module", "");
536 cmd.AppendChild(e);
537 e.AppendChild(doc.CreateTextNode(c.module));
538  
539 e = doc.CreateElement("", "Shared", "");
540 cmd.AppendChild(e);
541 e.AppendChild(doc.CreateTextNode(c.shared.ToString()));
542  
543 e = doc.CreateElement("", "HelpText", "");
544 cmd.AppendChild(e);
545 e.AppendChild(doc.CreateTextNode(c.help_text));
546  
547 e = doc.CreateElement("", "LongHelp", "");
548 cmd.AppendChild(e);
549 e.AppendChild(doc.CreateTextNode(c.long_help));
550  
551 e = doc.CreateElement("", "Description", "");
552 cmd.AppendChild(e);
553 e.AppendChild(doc.CreateTextNode(c.descriptive_help));
554  
555 xml.AppendChild(cmd);
556 }
557 }
558 }
559  
560 public void FromXml(XmlElement root, CommandDelegate fn)
561 {
562 CommandInfo help = (CommandInfo)((Dictionary<string, object>)tree["help"])[String.Empty];
563 ((Dictionary<string, object>)tree["help"]).Remove(string.Empty);
564 if (((Dictionary<string, object>)tree["help"]).Count == 0)
565 tree.Remove("help");
566  
567 CommandInfo quit = (CommandInfo)((Dictionary<string, object>)tree["quit"])[String.Empty];
568 ((Dictionary<string, object>)tree["quit"]).Remove(string.Empty);
569 if (((Dictionary<string, object>)tree["quit"]).Count == 0)
570 tree.Remove("quit");
571  
572 tree.Clear();
573  
574 ReadTreeLevel(tree, root, fn);
575  
576 if (!tree.ContainsKey("help"))
577 tree["help"] = (object) new Dictionary<string, object>();
578 ((Dictionary<string, object>)tree["help"])[String.Empty] = help;
579  
580 if (!tree.ContainsKey("quit"))
581 tree["quit"] = (object) new Dictionary<string, object>();
582 ((Dictionary<string, object>)tree["quit"])[String.Empty] = quit;
583 }
584  
585 private void ReadTreeLevel(Dictionary<string, object> level, XmlNode node, CommandDelegate fn)
586 {
587 Dictionary<string, object> next;
588 string name;
589  
590 XmlNodeList nodeL = node.ChildNodes;
591 XmlNodeList cmdL;
592 CommandInfo c;
593  
594 foreach (XmlNode part in nodeL)
595 {
596 switch (part.Name)
597 {
598 case "Level":
599 name = ((XmlElement)part).GetAttribute("Name");
600 next = new Dictionary<string, object>();
601 level[name] = next;
602 ReadTreeLevel(next, part, fn);
603 break;
604 case "Command":
605 cmdL = part.ChildNodes;
606 c = new CommandInfo();
607 foreach (XmlNode cmdPart in cmdL)
608 {
609 switch (cmdPart.Name)
610 {
611 case "Module":
612 c.module = cmdPart.InnerText;
613 break;
614 case "Shared":
615 c.shared = Convert.ToBoolean(cmdPart.InnerText);
616 break;
617 case "HelpText":
618 c.help_text = cmdPart.InnerText;
619 break;
620 case "LongHelp":
621 c.long_help = cmdPart.InnerText;
622 break;
623 case "Description":
624 c.descriptive_help = cmdPart.InnerText;
625 break;
626 }
627 }
628 c.fn = new List<CommandDelegate>();
629 c.fn.Add(fn);
630 level[String.Empty] = c;
631 break;
632 }
633 }
634 }
635 }
636  
637 public class Parser
638 {
639 // If an unquoted portion ends with an element matching this regex
640 // and the next element contains a space, then we have stripped
641 // embedded quotes that should not have been stripped
642 private static Regex optionRegex = new Regex("^--[a-zA-Z0-9-]+=$");
643  
644 public static string[] Parse(string text)
645 {
646 List<string> result = new List<string>();
647  
648 int index;
649  
650 string[] unquoted = text.Split(new char[] {'"'});
651  
652 for (index = 0 ; index < unquoted.Length ; index++)
653 {
654 if (index % 2 == 0)
655 {
656 string[] words = unquoted[index].Split(new char[] {' '});
657  
658 bool option = false;
659 foreach (string w in words)
660 {
661 if (w != String.Empty)
662 {
663 if (optionRegex.Match(w) == Match.Empty)
664 option = false;
665 else
666 option = true;
667 result.Add(w);
668 }
669 }
670 // The last item matched the regex, put the quotes back
671 if (option)
672 {
673 // If the line ended with it, don't do anything
674 if (index < (unquoted.Length - 1))
675 {
676 // Get and remove the option name
677 string optionText = result[result.Count - 1];
678 result.RemoveAt(result.Count - 1);
679  
680 // Add the quoted value back
681 optionText += "\"" + unquoted[index + 1] + "\"";
682  
683 // Push the result into our return array
684 result.Add(optionText);
685  
686 // Skip the already used value
687 index++;
688 }
689 }
690 }
691 else
692 {
693 result.Add(unquoted[index]);
694 }
695 }
696  
697 return result.ToArray();
698 }
699 }
700  
701 /// <summary>
702 /// A console that processes commands internally
703 /// </summary>
704 public class CommandConsole : ConsoleBase, ICommandConsole
705 {
706 // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
707  
708 public event OnOutputDelegate OnOutput;
709  
710 public ICommands Commands { get; private set; }
711  
712 public CommandConsole(string defaultPrompt) : base(defaultPrompt)
713 {
714 Commands = new Commands();
715  
716 Commands.AddCommand(
717 "Help", false, "help", "help [<item>]",
718 "Display help on a particular command or on a list of commands in a category", Help);
719 }
720  
721 private void Help(string module, string[] cmd)
722 {
723 List<string> help = Commands.GetHelp(cmd);
724  
725 foreach (string s in help)
726 Output(s);
727 }
728  
729 protected void FireOnOutput(string text)
730 {
731 OnOutputDelegate onOutput = OnOutput;
732 if (onOutput != null)
733 onOutput(text);
734 }
735  
736 /// <summary>
737 /// Display a command prompt on the console and wait for user input
738 /// </summary>
739 public void Prompt()
740 {
741 string line = ReadLine(DefaultPrompt + "# ", true, true);
742  
743 if (line != String.Empty)
744 Output("Invalid command");
745 }
746  
747 public void RunCommand(string cmd)
748 {
749 string[] parts = Parser.Parse(cmd);
750 Commands.Resolve(parts);
751 }
752  
753 public override string ReadLine(string p, bool isCommand, bool e)
754 {
755 System.Console.Write("{0}", p);
756 string cmdinput = System.Console.ReadLine();
757  
758 if (isCommand)
759 {
760 string[] cmd = Commands.Resolve(Parser.Parse(cmdinput));
761  
762 if (cmd.Length != 0)
763 {
764 int i;
765  
766 for (i=0 ; i < cmd.Length ; i++)
767 {
768 if (cmd[i].Contains(" "))
769 cmd[i] = "\"" + cmd[i] + "\"";
770 }
771 return String.Empty;
772 }
773 }
774 return cmdinput;
775 }
776 }
777 }