/*
* Copyright (c) Contributors, http://opensimulator.org/
* See CONTRIBUTORS.TXT for a full list of copyright holders.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the OpenSimulator Project nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Xml;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Threading;
using log4net;
namespace OpenSim.Framework.Console
{
public delegate void CommandDelegate(string module, string[] cmd);
public class Commands
{
///
/// Encapsulates a command that can be invoked from the console
///
private class CommandInfo
{
///
/// The module from which this command comes
///
public string module;
///
/// Whether the module is shared
///
public bool shared;
///
/// Very short BNF description
///
public string help_text;
///
/// Longer one line help text
///
public string long_help;
///
/// Full descriptive help for this command
///
public string descriptive_help;
///
/// The method to invoke for this command
///
public List fn;
}
///
/// Commands organized by keyword in a tree
///
private Dictionary tree =
new Dictionary();
///
/// Get help for the given help string
///
/// Parsed parts of the help string. If empty then general help is returned.
///
public List GetHelp(string[] cmd)
{
List help = new List();
List helpParts = new List(cmd);
// Remove initial help keyword
helpParts.RemoveAt(0);
// General help
if (helpParts.Count == 0)
{
help.AddRange(CollectHelp(tree));
help.Sort();
}
else
{
help.AddRange(CollectHelp(helpParts));
}
return help;
}
///
/// See if we can find the requested command in order to display longer help
///
///
///
private List CollectHelp(List helpParts)
{
string originalHelpRequest = string.Join(" ", helpParts.ToArray());
List help = new List();
Dictionary dict = tree;
while (helpParts.Count > 0)
{
string helpPart = helpParts[0];
if (!dict.ContainsKey(helpPart))
break;
//m_log.Debug("Found {0}", helpParts[0]);
if (dict[helpPart] is Dictionary)
dict = (Dictionary)dict[helpPart];
helpParts.RemoveAt(0);
}
// There was a command for the given help string
if (dict.ContainsKey(String.Empty))
{
CommandInfo commandInfo = (CommandInfo)dict[String.Empty];
help.Add(commandInfo.help_text);
help.Add(commandInfo.long_help);
string descriptiveHelp = commandInfo.descriptive_help;
// If we do have some descriptive help then insert a spacing line before and after for readability.
if (descriptiveHelp != string.Empty)
help.Add(string.Empty);
help.Add(commandInfo.descriptive_help);
if (descriptiveHelp != string.Empty)
help.Add(string.Empty);
}
else
{
help.Add(string.Format("No help is available for {0}", originalHelpRequest));
}
return help;
}
private List CollectHelp(Dictionary dict)
{
List result = new List();
foreach (KeyValuePair kvp in dict)
{
if (kvp.Value is Dictionary)
{
result.AddRange(CollectHelp((Dictionary)kvp.Value));
}
else
{
if (((CommandInfo)kvp.Value).long_help != String.Empty)
result.Add(((CommandInfo)kvp.Value).help_text+" - "+
((CommandInfo)kvp.Value).long_help);
}
}
return result;
}
///
/// Add a command to those which can be invoked from the console.
///
///
///
///
///
///
public void AddCommand(string module, bool shared, string command,
string help, string longhelp, CommandDelegate fn)
{
AddCommand(module, shared, command, help, longhelp, String.Empty, fn);
}
///
/// Add a command to those which can be invoked from the console.
///
///
///
///
///
///
///
public void AddCommand(string module, bool shared, string command,
string help, string longhelp, string descriptivehelp,
CommandDelegate fn)
{
string[] parts = Parser.Parse(command);
Dictionary current = tree;
foreach (string s in parts)
{
if (current.ContainsKey(s))
{
if (current[s] is Dictionary)
{
current = (Dictionary)current[s];
}
else
return;
}
else
{
current[s] = new Dictionary();
current = (Dictionary)current[s];
}
}
CommandInfo info;
if (current.ContainsKey(String.Empty))
{
info = (CommandInfo)current[String.Empty];
if (!info.shared && !info.fn.Contains(fn))
info.fn.Add(fn);
return;
}
info = new CommandInfo();
info.module = module;
info.shared = shared;
info.help_text = help;
info.long_help = longhelp;
info.descriptive_help = descriptivehelp;
info.fn = new List();
info.fn.Add(fn);
current[String.Empty] = info;
}
public string[] FindNextOption(string[] cmd, bool term)
{
Dictionary current = tree;
int remaining = cmd.Length;
foreach (string s in cmd)
{
remaining--;
List found = new List();
foreach (string opt in current.Keys)
{
if (remaining > 0 && opt == s)
{
found.Clear();
found.Add(opt);
break;
}
if (opt.StartsWith(s))
{
found.Add(opt);
}
}
if (found.Count == 1 && (remaining != 0 || term))
{
current = (Dictionary)current[found[0]];
}
else if (found.Count > 0)
{
return found.ToArray();
}
else
{
break;
// return new string[] {""};
}
}
if (current.Count > 1)
{
List choices = new List();
bool addcr = false;
foreach (string s in current.Keys)
{
if (s == String.Empty)
{
CommandInfo ci = (CommandInfo)current[String.Empty];
if (ci.fn.Count != 0)
addcr = true;
}
else
choices.Add(s);
}
if (addcr)
choices.Add("");
return choices.ToArray();
}
if (current.ContainsKey(String.Empty))
return new string[] { "Command help: "+((CommandInfo)current[String.Empty]).help_text};
return new string[] { new List(current.Keys)[0] };
}
public string[] Resolve(string[] cmd)
{
string[] result = cmd;
int index = -1;
Dictionary current = tree;
foreach (string s in cmd)
{
index++;
List found = new List();
foreach (string opt in current.Keys)
{
if (opt == s)
{
found.Clear();
found.Add(opt);
break;
}
if (opt.StartsWith(s))
{
found.Add(opt);
}
}
if (found.Count == 1)
{
result[index] = found[0];
current = (Dictionary)current[found[0]];
}
else if (found.Count > 0)
{
return new string[0];
}
else
{
break;
}
}
if (current.ContainsKey(String.Empty))
{
CommandInfo ci = (CommandInfo)current[String.Empty];
if (ci.fn.Count == 0)
return new string[0];
foreach (CommandDelegate fn in ci.fn)
{
if (fn != null)
fn(ci.module, result);
else
return new string[0];
}
return result;
}
return new string[0];
}
public XmlElement GetXml(XmlDocument doc)
{
CommandInfo help = (CommandInfo)((Dictionary)tree["help"])[String.Empty];
((Dictionary)tree["help"]).Remove(string.Empty);
if (((Dictionary)tree["help"]).Count == 0)
tree.Remove("help");
CommandInfo quit = (CommandInfo)((Dictionary)tree["quit"])[String.Empty];
((Dictionary)tree["quit"]).Remove(string.Empty);
if (((Dictionary)tree["quit"]).Count == 0)
tree.Remove("quit");
XmlElement root = doc.CreateElement("", "HelpTree", "");
ProcessTreeLevel(tree, root, doc);
if (!tree.ContainsKey("help"))
tree["help"] = (object) new Dictionary();
((Dictionary)tree["help"])[String.Empty] = help;
if (!tree.ContainsKey("quit"))
tree["quit"] = (object) new Dictionary();
((Dictionary)tree["quit"])[String.Empty] = quit;
return root;
}
private void ProcessTreeLevel(Dictionary level, XmlElement xml, XmlDocument doc)
{
foreach (KeyValuePair kvp in level)
{
if (kvp.Value is Dictionary)
{
XmlElement next = doc.CreateElement("", "Level", "");
next.SetAttribute("Name", kvp.Key);
xml.AppendChild(next);
ProcessTreeLevel((Dictionary)kvp.Value, next, doc);
}
else
{
CommandInfo c = (CommandInfo)kvp.Value;
XmlElement cmd = doc.CreateElement("", "Command", "");
XmlElement e;
e = doc.CreateElement("", "Module", "");
cmd.AppendChild(e);
e.AppendChild(doc.CreateTextNode(c.module));
e = doc.CreateElement("", "Shared", "");
cmd.AppendChild(e);
e.AppendChild(doc.CreateTextNode(c.shared.ToString()));
e = doc.CreateElement("", "HelpText", "");
cmd.AppendChild(e);
e.AppendChild(doc.CreateTextNode(c.help_text));
e = doc.CreateElement("", "LongHelp", "");
cmd.AppendChild(e);
e.AppendChild(doc.CreateTextNode(c.long_help));
e = doc.CreateElement("", "Description", "");
cmd.AppendChild(e);
e.AppendChild(doc.CreateTextNode(c.descriptive_help));
xml.AppendChild(cmd);
}
}
}
public void FromXml(XmlElement root, CommandDelegate fn)
{
CommandInfo help = (CommandInfo)((Dictionary)tree["help"])[String.Empty];
((Dictionary)tree["help"]).Remove(string.Empty);
if (((Dictionary)tree["help"]).Count == 0)
tree.Remove("help");
CommandInfo quit = (CommandInfo)((Dictionary)tree["quit"])[String.Empty];
((Dictionary)tree["quit"]).Remove(string.Empty);
if (((Dictionary)tree["quit"]).Count == 0)
tree.Remove("quit");
tree.Clear();
ReadTreeLevel(tree, root, fn);
if (!tree.ContainsKey("help"))
tree["help"] = (object) new Dictionary();
((Dictionary)tree["help"])[String.Empty] = help;
if (!tree.ContainsKey("quit"))
tree["quit"] = (object) new Dictionary();
((Dictionary)tree["quit"])[String.Empty] = quit;
}
private void ReadTreeLevel(Dictionary level, XmlNode node, CommandDelegate fn)
{
Dictionary next;
string name;
XmlNodeList nodeL = node.ChildNodes;
XmlNodeList cmdL;
CommandInfo c;
foreach (XmlNode part in nodeL)
{
switch (part.Name)
{
case "Level":
name = ((XmlElement)part).GetAttribute("Name");
next = new Dictionary();
level[name] = next;
ReadTreeLevel(next, part, fn);
break;
case "Command":
cmdL = part.ChildNodes;
c = new CommandInfo();
foreach (XmlNode cmdPart in cmdL)
{
switch (cmdPart.Name)
{
case "Module":
c.module = cmdPart.InnerText;
break;
case "Shared":
c.shared = Convert.ToBoolean(cmdPart.InnerText);
break;
case "HelpText":
c.help_text = cmdPart.InnerText;
break;
case "LongHelp":
c.long_help = cmdPart.InnerText;
break;
case "Description":
c.descriptive_help = cmdPart.InnerText;
break;
}
}
c.fn = new List();
c.fn.Add(fn);
level[String.Empty] = c;
break;
}
}
}
}
public class Parser
{
public static string[] Parse(string text)
{
List result = new List();
int index;
string[] unquoted = text.Split(new char[] {'"'});
for (index = 0 ; index < unquoted.Length ; index++)
{
if (index % 2 == 0)
{
string[] words = unquoted[index].Split(new char[] {' '});
foreach (string w in words)
{
if (w != String.Empty)
result.Add(w);
}
}
else
{
result.Add(unquoted[index]);
}
}
return result.ToArray();
}
}
///
/// A console that processes commands internally
///
public class CommandConsole : ConsoleBase
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public Commands Commands = new Commands();
public CommandConsole(string defaultPrompt) : base(defaultPrompt)
{
Commands.AddCommand("console", false, "help", "help []",
"Get general command list or more detailed help on a specific command", Help);
}
private void Help(string module, string[] cmd)
{
List help = Commands.GetHelp(cmd);
foreach (string s in help)
Output(s);
}
///
/// Display a command prompt on the console and wait for user input
///
public void Prompt()
{
string line = ReadLine(m_defaultPrompt + "# ", true, true);
if (line != String.Empty)
{
m_log.Info("[CONSOLE] Invalid command");
}
}
public void RunCommand(string cmd)
{
string[] parts = Parser.Parse(cmd);
Commands.Resolve(parts);
}
public override string ReadLine(string p, bool isCommand, bool e)
{
System.Console.Write("{0}", p);
string cmdinput = System.Console.ReadLine();
if (isCommand)
{
string[] cmd = Commands.Resolve(Parser.Parse(cmdinput));
if (cmd.Length != 0)
{
int i;
for (i=0 ; i < cmd.Length ; i++)
{
if (cmd[i].Contains(" "))
cmd[i] = "\"" + cmd[i] + "\"";
}
return String.Empty;
}
}
return cmdinput;
}
}
}