OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
CommandConsole.cs
Go to the documentation of this file.
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 
49  private class CommandInfo
50  {
54  public string module;
55 
59  public bool shared;
60 
64  public string help_text;
65 
69  public string long_help;
70 
74  public string descriptive_help;
75 
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 
92  private Dictionary<string, object> tree =
93  new Dictionary<string, object>();
94 
98  private Dictionary<string, List<CommandInfo>> m_modulesCommands = new Dictionary<string, List<CommandInfo>>();
99 
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 
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 
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 
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 
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 
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 
721  {
722 // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
723 
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 
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 }
static string[] Parse(string text)
void AddCommand(string module, bool shared, string command, string help, string longhelp, CommandDelegate fn)
Add a command to those which can be invoked from the console.
delegate void CommandDelegate(string module, string[] cmd)
void AddCommand(string module, bool shared, string command, string help, string longhelp, string descriptivehelp, CommandDelegate fn)
Add a command to those which can be invoked from the console.
string[] Resolve(string[] cmd)
delegate void OnOutputDelegate(string message)
OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString key
Definition: ICM_Api.cs:31
string[] FindNextOption(string[] cmd, bool term)
bool HasCommand(string command)
Has the given command already been registered?
override string ReadLine(string p, bool isCommand, bool e)
void Prompt()
Display a command prompt on the console and wait for user input
Interactive OpenSim region server
Definition: OpenSim.cs:55
void FromXml(XmlElement root, CommandDelegate fn)
XmlElement GetXml(XmlDocument doc)
List< string > GetHelp(string[] cmd)
Get help for the given help string
A console that processes commands internally