#region Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 /* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #endregion using System; using System.Collections.Generic; using CSharpTest.Net.Utils; using System.IO; using System.Reflection; namespace CSharpTest.Net.Commands { /// /// A list of built-in commands that can be added to the interpreter /// [Flags] public enum DefaultCommands : uint { /// Not a command, indicates no default commands None = 0, /// Not a command, indicates the default commands added if not specified Default = Get | Set | Help, /// Not a command, indicates to use all default commands All = 0xFFFFFFFF, /// A command to get the value of an option Get = 0x00000001, /// A command to set the value of an option Set = 0x00000002, /// A command to display help about the commands and their options Help = 0x00000004, /// An option to set and get the environment error-level ErrorLevel = 0x00000008, /// An option that provides customization of the command prompt for interactive mode Prompt = 0x00000010, /// A command to echo back to std::out the arguments provided. Echo = 0x00000020, /// A command to read the input stream and show one screen at a time to standard output. More = 0x00000040, /// A command to search for a text string in a file or the standard input stream. Find = 0x00000080, /// A command filter that allows piping the output of one command into the input of another. PipeCommands = 0x00000100, /// A command filter that allows redirect of std in/out to files. IORedirect = 0x00000200, /// A command that allows the console application to be hosted as a restful HTTP server. HostHTTP = 0x00000400 } partial class CommandInterpreter { sealed class BuiltInCommands { readonly Dictionary _contents; internal BuiltInCommands(params IDisplayInfo[] all) { _contents = new Dictionary(); foreach (IDisplayInfo d in BuiltIn.Commands) _contents.Add((DefaultCommands)Enum.Parse(typeof(DefaultCommands), d.DisplayName, true), d); AddRange(all); } public void AddRange(params IDisplayInfo[] all) { foreach (IDisplayInfo d in all) _contents.Add((DefaultCommands)Enum.Parse(typeof(DefaultCommands), d.DisplayName, true), d); } public void Add(CommandInterpreter ci, DefaultCommands cmds) { foreach (DefaultCommands key in Enum.GetValues(typeof(DefaultCommands))) { IDisplayInfo item; if (key == (key & cmds) && _contents.TryGetValue(key, out item)) { if (item is ICommandFilter) ci.AddFilter(item as ICommandFilter); else if (item is ICommand) ci.AddCommand(item as ICommand); else if (item is IOption) ci.AddOption(item as IOption); } } } internal static class BuiltIn { public static ICommand[] Commands { get { Type t = typeof(BuiltIn); List cmds = new List(); foreach (MethodInfo mi in t.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.InvokeMethod)) if (!mi.IsSpecialName) cmds.Add(Command.Make(t, mi)); return cmds.ToArray(); } } [Command("Echo", Category = "Built-in", Description = "Writes the arguments to standard output.", Visible = true)] public static void Echo( [AllArguments, Argument(Category = "Built-in", Description = "The text to write to standard out.")] string[] args) { Console.WriteLine(ArgumentList.EscapeArguments(args)); } private static int NextRedirect(string[] args) { for (int ix = args.Length - 1; ix >= 0; ix--) if (args[ix].StartsWith("<") || args[ix].StartsWith(">")) return ix; return -1; } [CommandFilter('>', '<', Category = "Built-in", Description = "A command filter that allows redirect of std in/out to files.", Visible = false)] public static void IORedirect(ICommandInterpreter ci, ICommandChain chain, string[] args) { TextReader rin = null; TextWriter rout = null; int pos; try { while ((pos = NextRedirect(args)) > 0) { List cmd1 = new List(args); List cmd2 = new List(args); cmd1.RemoveRange(pos, cmd1.Count - pos); cmd2.RemoveRange(0, pos); args = cmd1.ToArray(); string file = String.Join(" ", cmd2.ToArray()); bool isout = file.StartsWith(">"); bool isappend = isout && file.StartsWith(">>"); file = file.TrimStart('>', '<').Trim(); if (!isout) rin = File.OpenText(file); else rout = isappend ? File.AppendText(file) : File.CreateText(file); } } catch { using (rin) using (rout) throw; } TextReader stdin = ConsoleInput.Capture(rin); TextWriter stdout = ConsoleOutput.Capture(rout); try { chain.Next(args); } finally { using (rin) ConsoleInput.Restore(rin, stdin); using (rout) ConsoleOutput.Restore(rout, stdout); } } [Command("Find", Category = "Built-in", Description = "Reads the input stream and shows any line containing the text specified.", Visible = true)] public static void Find( [Argument("text", Category = "Built-in", Description = "The text to search for in the input stream.")] string text, [Argument("filename", "f", Category = "Built-in", Description = "Specifies a file read and search, omit to use standard input.", DefaultValue = null)] string filename, [Argument("V", Category = "Built-in", Description = "Displays all lines NOT containing the specified string.", DefaultValue = false)] bool invert, [Argument("C", Category = "Built-in", Description = "Displays only the count of lines containing the string.", DefaultValue = false)] bool count, [Argument("I", Category = "Built-in", Description = "Ignores the case of characters when searching for the string.", DefaultValue = false)] bool ignoreCase ) { StringComparison cmp = StringComparison.Ordinal; if (ignoreCase) cmp = StringComparison.OrdinalIgnoreCase; StreamReader disposeMe = null; TextReader rdr = Console.In; if (!String.IsNullOrEmpty(filename)) rdr = disposeMe = File.OpenText(filename); try { int counter = 0; string line; while (null != (line = rdr.ReadLine())) { bool found = (line.IndexOf(text, cmp) >= 0); found = invert ? !found : found; if (found) { counter++; if (!count) Console.WriteLine(line); } } if (count) Console.WriteLine(counter); } finally { if (disposeMe != null) disposeMe.Dispose(); } } [Command("More", Category = "Built-in", Description = "Reads the input stream and shows one screen at a time to standard output.", Visible = true)] public static void More(ICommandInterpreter ci) { int pos = 2; int lines; try { lines = Console.WindowHeight; } catch (System.IO.IOException) { lines = 25; } string line; while (null != (line = Console.ReadLine())) { Console.WriteLine(line); if (++pos >= lines) { Console.Write("-- More --"); ci.ReadNextCharacter(); Console.WriteLine(); pos = 1; } } } private static int NextPipe(string[] args) { for (int ix = 0; ix < args.Length; ix++) if (args[ix].StartsWith("|")) return ix; return -1; } [CommandFilter('|', Category = "Built-in", Description = "A command filter that allows piping the output of one command into the input of another.", Visible = false)] public static void PipeCommands(ICommandInterpreter ci, ICommandChain chain, string[] args) { TextReader rdr = null, stdin = null; int pos; while ((pos = NextPipe(args)) > 0) { List cmd1 = new List(args); cmd1.RemoveRange(pos, cmd1.Count - pos); List cmd2 = new List(args); cmd2.RemoveRange(0, pos); cmd2[0] = cmd2[0].TrimStart('|'); if (cmd2[0].Length == 0) cmd2.RemoveAt(0); if (cmd2.Count == 0) { args = cmd1.ToArray(); break; } else args = cmd2.ToArray(); StringWriter wtr = new StringWriter(); TextWriter stdout = ConsoleOutput.Capture(wtr); stdin = ConsoleInput.Capture(rdr); try { chain.Next(cmd1.ToArray()); } finally { ConsoleInput.Restore(rdr, stdin); ConsoleOutput.Restore(wtr, stdout); } rdr = new StringReader(wtr.ToString()); } stdin = ConsoleInput.Capture(rdr); try { chain.Next(args); } finally { ConsoleInput.Restore(rdr, stdin); } } } } } }