diff options
author | Melanie Thielker | 2009-05-04 12:15:55 +0000 |
---|---|---|
committer | Melanie Thielker | 2009-05-04 12:15:55 +0000 |
commit | 1b877234dada9f14cebc486cc426db892beae152 (patch) | |
tree | 762ee7172c66a78b643f4cef0eed946314816403 /OpenSim/Framework/Console/CommandConsole.cs | |
parent | Thanks BlueWall for Mantis #3578 - adding Hypergrid connection to JSON Stats (diff) | |
download | opensim-SC-1b877234dada9f14cebc486cc426db892beae152.zip opensim-SC-1b877234dada9f14cebc486cc426db892beae152.tar.gz opensim-SC-1b877234dada9f14cebc486cc426db892beae152.tar.bz2 opensim-SC-1b877234dada9f14cebc486cc426db892beae152.tar.xz |
Refactor. Make ConsoleBase a true base class. Create CommandConsole as a simple
console capable of processing commands. Create LocalConsole as a console
that uses cursor control and context help. Precursor to a distributed
console system for the new grid services. No functional change intended :)
Diffstat (limited to 'OpenSim/Framework/Console/CommandConsole.cs')
-rw-r--r-- | OpenSim/Framework/Console/CommandConsole.cs | 467 |
1 files changed, 467 insertions, 0 deletions
diff --git a/OpenSim/Framework/Console/CommandConsole.cs b/OpenSim/Framework/Console/CommandConsole.cs new file mode 100644 index 0000000..0c314b9 --- /dev/null +++ b/OpenSim/Framework/Console/CommandConsole.cs | |||
@@ -0,0 +1,467 @@ | |||
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 OpenSim 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.Collections.Generic; | ||
30 | using System.Diagnostics; | ||
31 | using System.Reflection; | ||
32 | using System.Text; | ||
33 | using System.Threading; | ||
34 | using log4net; | ||
35 | |||
36 | namespace OpenSim.Framework.Console | ||
37 | { | ||
38 | public delegate void CommandDelegate(string module, string[] cmd); | ||
39 | |||
40 | public class Commands | ||
41 | { | ||
42 | /// <summary> | ||
43 | /// Encapsulates a command that can be invoked from the console | ||
44 | /// </summary> | ||
45 | private class CommandInfo | ||
46 | { | ||
47 | /// <value> | ||
48 | /// The module from which this command comes | ||
49 | /// </value> | ||
50 | public string module; | ||
51 | |||
52 | /// <value> | ||
53 | /// Whether the module is shared | ||
54 | /// </value> | ||
55 | public bool shared; | ||
56 | |||
57 | /// <value> | ||
58 | /// Very short BNF description | ||
59 | /// </value> | ||
60 | public string help_text; | ||
61 | |||
62 | /// <value> | ||
63 | /// Longer one line help text | ||
64 | /// </value> | ||
65 | public string long_help; | ||
66 | |||
67 | /// <value> | ||
68 | /// Full descriptive help for this command | ||
69 | /// </value> | ||
70 | public string descriptive_help; | ||
71 | |||
72 | /// <value> | ||
73 | /// The method to invoke for this command | ||
74 | /// </value> | ||
75 | public List<CommandDelegate> fn; | ||
76 | } | ||
77 | |||
78 | /// <value> | ||
79 | /// Commands organized by keyword in a tree | ||
80 | /// </value> | ||
81 | private Dictionary<string, object> tree = | ||
82 | new Dictionary<string, object>(); | ||
83 | |||
84 | /// <summary> | ||
85 | /// Get help for the given help string | ||
86 | /// </summary> | ||
87 | /// <param name="helpParts">Parsed parts of the help string. If empty then general help is returned.</param> | ||
88 | /// <returns></returns> | ||
89 | public List<string> GetHelp(string[] cmd) | ||
90 | { | ||
91 | List<string> help = new List<string>(); | ||
92 | List<string> helpParts = new List<string>(cmd); | ||
93 | |||
94 | // Remove initial help keyword | ||
95 | helpParts.RemoveAt(0); | ||
96 | |||
97 | // General help | ||
98 | if (helpParts.Count == 0) | ||
99 | { | ||
100 | help.AddRange(CollectHelp(tree)); | ||
101 | help.Sort(); | ||
102 | } | ||
103 | else | ||
104 | { | ||
105 | help.AddRange(CollectHelp(helpParts)); | ||
106 | } | ||
107 | |||
108 | return help; | ||
109 | } | ||
110 | |||
111 | /// <summary> | ||
112 | /// See if we can find the requested command in order to display longer help | ||
113 | /// </summary> | ||
114 | /// <param name="helpParts"></param> | ||
115 | /// <returns></returns> | ||
116 | private List<string> CollectHelp(List<string> helpParts) | ||
117 | { | ||
118 | string originalHelpRequest = string.Join(" ", helpParts.ToArray()); | ||
119 | List<string> help = new List<string>(); | ||
120 | |||
121 | Dictionary<string, object> dict = tree; | ||
122 | while (helpParts.Count > 0) | ||
123 | { | ||
124 | string helpPart = helpParts[0]; | ||
125 | |||
126 | if (!dict.ContainsKey(helpPart)) | ||
127 | break; | ||
128 | |||
129 | //m_log.Debug("Found {0}", helpParts[0]); | ||
130 | |||
131 | if (dict[helpPart] is Dictionary<string, Object>) | ||
132 | dict = (Dictionary<string, object>)dict[helpPart]; | ||
133 | |||
134 | helpParts.RemoveAt(0); | ||
135 | } | ||
136 | |||
137 | // There was a command for the given help string | ||
138 | if (dict.ContainsKey(String.Empty)) | ||
139 | { | ||
140 | CommandInfo commandInfo = (CommandInfo)dict[String.Empty]; | ||
141 | help.Add(commandInfo.help_text); | ||
142 | help.Add(commandInfo.long_help); | ||
143 | help.Add(commandInfo.descriptive_help); | ||
144 | } | ||
145 | else | ||
146 | { | ||
147 | help.Add(string.Format("No help is available for {0}", originalHelpRequest)); | ||
148 | } | ||
149 | |||
150 | return help; | ||
151 | } | ||
152 | |||
153 | private List<string> CollectHelp(Dictionary<string, object> dict) | ||
154 | { | ||
155 | List<string> result = new List<string>(); | ||
156 | |||
157 | foreach (KeyValuePair<string, object> kvp in dict) | ||
158 | { | ||
159 | if (kvp.Value is Dictionary<string, Object>) | ||
160 | { | ||
161 | result.AddRange(CollectHelp((Dictionary<string, Object>)kvp.Value)); | ||
162 | } | ||
163 | else | ||
164 | { | ||
165 | if (((CommandInfo)kvp.Value).long_help != String.Empty) | ||
166 | result.Add(((CommandInfo)kvp.Value).help_text+" - "+ | ||
167 | ((CommandInfo)kvp.Value).long_help); | ||
168 | } | ||
169 | } | ||
170 | return result; | ||
171 | } | ||
172 | |||
173 | /// <summary> | ||
174 | /// Add a command to those which can be invoked from the console. | ||
175 | /// </summary> | ||
176 | /// <param name="module"></param> | ||
177 | /// <param name="command"></param> | ||
178 | /// <param name="help"></param> | ||
179 | /// <param name="longhelp"></param> | ||
180 | /// <param name="fn"></param> | ||
181 | public void AddCommand(string module, bool shared, string command, | ||
182 | string help, string longhelp, CommandDelegate fn) | ||
183 | { | ||
184 | AddCommand(module, shared, command, help, longhelp, | ||
185 | String.Empty, fn); | ||
186 | } | ||
187 | |||
188 | /// <summary> | ||
189 | /// Add a command to those which can be invoked from the console. | ||
190 | /// </summary> | ||
191 | /// <param name="module"></param> | ||
192 | /// <param name="command"></param> | ||
193 | /// <param name="help"></param> | ||
194 | /// <param name="longhelp"></param> | ||
195 | /// <param name="descriptivehelp"></param> | ||
196 | /// <param name="fn"></param> | ||
197 | public void AddCommand(string module, bool shared, string command, | ||
198 | string help, string longhelp, string descriptivehelp, | ||
199 | CommandDelegate fn) | ||
200 | { | ||
201 | string[] parts = Parser.Parse(command); | ||
202 | |||
203 | Dictionary<string, Object> current = tree; | ||
204 | |||
205 | foreach (string s in parts) | ||
206 | { | ||
207 | if (current.ContainsKey(s)) | ||
208 | { | ||
209 | if (current[s] is Dictionary<string, Object>) | ||
210 | { | ||
211 | current = (Dictionary<string, Object>)current[s]; | ||
212 | } | ||
213 | else | ||
214 | return; | ||
215 | } | ||
216 | else | ||
217 | { | ||
218 | current[s] = new Dictionary<string, Object>(); | ||
219 | current = (Dictionary<string, Object>)current[s]; | ||
220 | } | ||
221 | } | ||
222 | |||
223 | CommandInfo info; | ||
224 | |||
225 | if (current.ContainsKey(String.Empty)) | ||
226 | { | ||
227 | info = (CommandInfo)current[String.Empty]; | ||
228 | if (!info.shared && !info.fn.Contains(fn)) | ||
229 | info.fn.Add(fn); | ||
230 | |||
231 | return; | ||
232 | } | ||
233 | |||
234 | info = new CommandInfo(); | ||
235 | info.module = module; | ||
236 | info.shared = shared; | ||
237 | info.help_text = help; | ||
238 | info.long_help = longhelp; | ||
239 | info.descriptive_help = descriptivehelp; | ||
240 | info.fn = new List<CommandDelegate>(); | ||
241 | info.fn.Add(fn); | ||
242 | current[String.Empty] = info; | ||
243 | } | ||
244 | |||
245 | public string[] FindNextOption(string[] cmd, bool term) | ||
246 | { | ||
247 | Dictionary<string, object> current = tree; | ||
248 | |||
249 | int remaining = cmd.Length; | ||
250 | |||
251 | foreach (string s in cmd) | ||
252 | { | ||
253 | remaining--; | ||
254 | |||
255 | List<string> found = new List<string>(); | ||
256 | |||
257 | foreach (string opt in current.Keys) | ||
258 | { | ||
259 | if (remaining > 0 && opt == s) | ||
260 | { | ||
261 | found.Clear(); | ||
262 | found.Add(opt); | ||
263 | break; | ||
264 | } | ||
265 | if (opt.StartsWith(s)) | ||
266 | { | ||
267 | found.Add(opt); | ||
268 | } | ||
269 | } | ||
270 | |||
271 | if (found.Count == 1 && (remaining != 0 || term)) | ||
272 | { | ||
273 | current = (Dictionary<string, object>)current[found[0]]; | ||
274 | } | ||
275 | else if (found.Count > 0) | ||
276 | { | ||
277 | return found.ToArray(); | ||
278 | } | ||
279 | else | ||
280 | { | ||
281 | break; | ||
282 | // return new string[] {"<cr>"}; | ||
283 | } | ||
284 | } | ||
285 | |||
286 | if (current.Count > 1) | ||
287 | { | ||
288 | List<string> choices = new List<string>(); | ||
289 | |||
290 | bool addcr = false; | ||
291 | foreach (string s in current.Keys) | ||
292 | { | ||
293 | if (s == String.Empty) | ||
294 | { | ||
295 | CommandInfo ci = (CommandInfo)current[String.Empty]; | ||
296 | if (ci.fn.Count != 0) | ||
297 | addcr = true; | ||
298 | } | ||
299 | else | ||
300 | choices.Add(s); | ||
301 | } | ||
302 | if (addcr) | ||
303 | choices.Add("<cr>"); | ||
304 | return choices.ToArray(); | ||
305 | } | ||
306 | |||
307 | if (current.ContainsKey(String.Empty)) | ||
308 | return new string[] { "Command help: "+((CommandInfo)current[String.Empty]).help_text}; | ||
309 | |||
310 | return new string[] { new List<string>(current.Keys)[0] }; | ||
311 | } | ||
312 | |||
313 | public string[] Resolve(string[] cmd) | ||
314 | { | ||
315 | string[] result = cmd; | ||
316 | int index = -1; | ||
317 | |||
318 | Dictionary<string, object> current = tree; | ||
319 | |||
320 | foreach (string s in cmd) | ||
321 | { | ||
322 | index++; | ||
323 | |||
324 | List<string> found = new List<string>(); | ||
325 | |||
326 | foreach (string opt in current.Keys) | ||
327 | { | ||
328 | if (opt == s) | ||
329 | { | ||
330 | found.Clear(); | ||
331 | found.Add(opt); | ||
332 | break; | ||
333 | } | ||
334 | if (opt.StartsWith(s)) | ||
335 | { | ||
336 | found.Add(opt); | ||
337 | } | ||
338 | } | ||
339 | |||
340 | if (found.Count == 1) | ||
341 | { | ||
342 | result[index] = found[0]; | ||
343 | current = (Dictionary<string, object>)current[found[0]]; | ||
344 | } | ||
345 | else if (found.Count > 0) | ||
346 | { | ||
347 | return new string[0]; | ||
348 | } | ||
349 | else | ||
350 | { | ||
351 | break; | ||
352 | } | ||
353 | } | ||
354 | |||
355 | if (current.ContainsKey(String.Empty)) | ||
356 | { | ||
357 | CommandInfo ci = (CommandInfo)current[String.Empty]; | ||
358 | if (ci.fn.Count == 0) | ||
359 | return new string[0]; | ||
360 | foreach (CommandDelegate fn in ci.fn) | ||
361 | { | ||
362 | if (fn != null) | ||
363 | fn(ci.module, result); | ||
364 | else | ||
365 | return new string[0]; | ||
366 | } | ||
367 | return result; | ||
368 | } | ||
369 | |||
370 | return new string[0]; | ||
371 | } | ||
372 | } | ||
373 | |||
374 | public class Parser | ||
375 | { | ||
376 | public static string[] Parse(string text) | ||
377 | { | ||
378 | List<string> result = new List<string>(); | ||
379 | |||
380 | int index; | ||
381 | |||
382 | string[] unquoted = text.Split(new char[] {'"'}); | ||
383 | |||
384 | for (index = 0 ; index < unquoted.Length ; index++) | ||
385 | { | ||
386 | if (index % 2 == 0) | ||
387 | { | ||
388 | string[] words = unquoted[index].Split(new char[] {' '}); | ||
389 | foreach (string w in words) | ||
390 | { | ||
391 | if (w != String.Empty) | ||
392 | result.Add(w); | ||
393 | } | ||
394 | } | ||
395 | else | ||
396 | { | ||
397 | result.Add(unquoted[index]); | ||
398 | } | ||
399 | } | ||
400 | |||
401 | return result.ToArray(); | ||
402 | } | ||
403 | } | ||
404 | |||
405 | // A console that processes commands internally | ||
406 | // | ||
407 | public class CommandConsole : ConsoleBase | ||
408 | { | ||
409 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
410 | |||
411 | public Commands Commands = new Commands(); | ||
412 | |||
413 | public CommandConsole(string defaultPrompt) : base(defaultPrompt) | ||
414 | { | ||
415 | Commands.AddCommand("console", false, "help", "help [<command>]", | ||
416 | "Get general command list or more detailed help on a specific command", Help); | ||
417 | } | ||
418 | |||
419 | private void Help(string module, string[] cmd) | ||
420 | { | ||
421 | List<string> help = Commands.GetHelp(cmd); | ||
422 | |||
423 | foreach (string s in help) | ||
424 | Output(s); | ||
425 | } | ||
426 | |||
427 | public void Prompt() | ||
428 | { | ||
429 | string line = ReadLine(m_defaultPrompt, true, true); | ||
430 | |||
431 | if (line != String.Empty) | ||
432 | { | ||
433 | m_log.Info("Invalid command"); | ||
434 | } | ||
435 | } | ||
436 | |||
437 | public void RunCommand(string cmd) | ||
438 | { | ||
439 | string[] parts = Parser.Parse(cmd); | ||
440 | Commands.Resolve(parts); | ||
441 | } | ||
442 | |||
443 | public override string ReadLine(string p, bool isCommand, bool e) | ||
444 | { | ||
445 | System.Console.Write("{0}", prompt); | ||
446 | string cmdinput = System.Console.ReadLine(); | ||
447 | |||
448 | if (isCommand) | ||
449 | { | ||
450 | string[] cmd = Commands.Resolve(Parser.Parse(cmdinput)); | ||
451 | |||
452 | if (cmd.Length != 0) | ||
453 | { | ||
454 | int i; | ||
455 | |||
456 | for (i=0 ; i < cmd.Length ; i++) | ||
457 | { | ||
458 | if (cmd[i].Contains(" ")) | ||
459 | cmd[i] = "\"" + cmd[i] + "\""; | ||
460 | } | ||
461 | return String.Empty; | ||
462 | } | ||
463 | } | ||
464 | return cmdinput; | ||
465 | } | ||
466 | } | ||
467 | } | ||