├── .gitignore ├── CSharpTest.Net.Commands.nuspec ├── CSharpTest.Net.Commands.sln ├── README.md ├── packages └── repositories.config └── src ├── CSharpTest.Net.Commands ├── Argument.cs ├── ArgumentList.cs ├── Attributes.cs ├── CSharpTest.Net.Commands.csproj ├── Command.cs ├── CommandFilter.cs ├── CommandInterpreter.cs ├── DefaultCommands.cs ├── DisplayInfoBase.cs ├── Exceptions.cs ├── HelpDisplay.cs ├── Interfaces.cs ├── LICENSE-2.0.txt ├── Option.cs └── Properties │ ├── AssemblyInfo.cs │ └── Internal.cs ├── CSharpTest.Net.CommandsTest ├── CSharpTest.Net.CommandsTest.csproj ├── TestArgumentList.cs ├── TestCmdInterpreter.cs └── packages.config └── Example ├── Commands.cs ├── Example.csproj ├── Main.cs └── Properties └── AssemblyInfo.cs /.gitignore: -------------------------------------------------------------------------------- 1 | _ReSharper.* 2 | obj 3 | bin 4 | Bin 5 | Debug 6 | Release 7 | *.user 8 | *.pdb 9 | *.suo 10 | *.sln 11 | *.Generated.cs 12 | *.cache 13 | *.vspscc 14 | *.log 15 | *.ncb 16 | *.pch 17 | *.sdf 18 | *.zip 19 | *.mdf 20 | *.snk 21 | *.DotSettings 22 | *.nupkg 23 | 24 | packages/*/* 25 | depend/* -------------------------------------------------------------------------------- /CSharpTest.Net.Commands.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | $version$ 6 | CSharpTest.Net.Commands 7 | CSharpTest.Net.Commands 8 | Roger Knapp 9 | Roger Knapp 10 | en-US 11 | http://csharptest.net/src/LICENSE-2.0.txt 12 | https://github.com/csharptest/CSharpTest.Net.Commands 13 | http://csharptest.net/favicon.ico 14 | false 15 | Copyright 2008-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 16 | 17 | commandline parse args arguments options command-line command interpreter 18 | CSharpTest.Net.Commands provides a command-line console wrapper around a C# class 19 | See readme at https://github.com/csharptest/CSharpTest.Net.Commands for release notes. 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /CSharpTest.Net.Commands.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpTest.Net.Commands", "src\CSharpTest.Net.Commands\CSharpTest.Net.Commands.csproj", "{7BD5EDD1-445C-46D1-A0B2-4B68CB51EADB}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpTest.Net.CommandsTest", "src\CSharpTest.Net.CommandsTest\CSharpTest.Net.CommandsTest.csproj", "{097601FB-7E62-47DA-8E48-B56C9AD5DF20}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{5EE7948B-00F6-4B34-9463-8F6499265E24}" 9 | ProjectSection(SolutionItems) = preProject 10 | CSharpTest.Net.Commands.nuspec = CSharpTest.Net.Commands.nuspec 11 | EndProjectSection 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "src\Example\Example.csproj", "{E6931B7B-8A57-416E-BFDD-FFAEC7BAC5AE}" 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Release|Any CPU = Release|Any CPU 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {7BD5EDD1-445C-46D1-A0B2-4B68CB51EADB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 22 | {7BD5EDD1-445C-46D1-A0B2-4B68CB51EADB}.Debug|Any CPU.Build.0 = Debug|Any CPU 23 | {7BD5EDD1-445C-46D1-A0B2-4B68CB51EADB}.Release|Any CPU.ActiveCfg = Release|Any CPU 24 | {7BD5EDD1-445C-46D1-A0B2-4B68CB51EADB}.Release|Any CPU.Build.0 = Release|Any CPU 25 | {097601FB-7E62-47DA-8E48-B56C9AD5DF20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {097601FB-7E62-47DA-8E48-B56C9AD5DF20}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {097601FB-7E62-47DA-8E48-B56C9AD5DF20}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {097601FB-7E62-47DA-8E48-B56C9AD5DF20}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {E6931B7B-8A57-416E-BFDD-FFAEC7BAC5AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {E6931B7B-8A57-416E-BFDD-FFAEC7BAC5AE}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {E6931B7B-8A57-416E-BFDD-FFAEC7BAC5AE}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {E6931B7B-8A57-416E-BFDD-FFAEC7BAC5AE}.Release|Any CPU.Build.0 = Release|Any CPU 33 | EndGlobalSection 34 | GlobalSection(SolutionProperties) = preSolution 35 | HideSolutionNode = FALSE 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CSharpTest.Net.Commands 2 | ======================= 3 | 4 | CSharpTest.Net.Commands (moved from https://code.google.com/p/csharptest-net/) 5 | 6 | ## Changes ## 7 | 8 | 2014-03-09 Initial clone and extraction from existing library. 9 | 10 | ## Online Help ## 11 | 12 | See the online help for CSharpTest.Net.Commands.CommandInterpreter 13 | http://help.csharptest.net/?CSharpTest.Net.Library~CSharpTest.Net.Commands.CommandInterpreter_members.html 14 | 15 | ## Usage ## 16 | 17 | The nuget package installs both a reference to the compiled assembly as well as a copy of the source code. This allows users to either embed the source directly (stand-alone) and remove the reference, or to remove the source folder "Commands" and use the referenced library. 18 | 19 | ## Example ## 20 | 21 | The following example program exposes a command-line that supports the "Example" command to print "Hello World" to std::out, and a Help command that describes the commands available. See examples for more uses. 22 | 23 | ``` 24 | class Program 25 | { 26 | public static void Example() 27 | { 28 | Console.WriteLine("Hello World"); 29 | } 30 | 31 | [STAThread] 32 | static int Main(string[] args) 33 | { 34 | // Construct the CommandInterpreter and initialize 35 | ICommandInterpreter ci = new CommandInterpreter( 36 | DefaultCommands.Help, 37 | typeof(Program) 38 | ); 39 | 40 | ci.Run(args); 41 | 42 | return ci.ErrorLevel; 43 | } 44 | } 45 | ``` 46 | -------------------------------------------------------------------------------- /packages/repositories.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.Commands/Argument.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 2 | /* Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | #endregion 15 | using System; 16 | using System.Collections.Generic; 17 | using System.Reflection; 18 | using System.ComponentModel; 19 | using System.Text; 20 | 21 | namespace CSharpTest.Net.Commands 22 | { 23 | [System.Diagnostics.DebuggerDisplay("{Parameter}")] 24 | partial class Argument : DisplayInfoBase, IArgument 25 | { 26 | readonly object _default; 27 | readonly bool _required; 28 | readonly bool _allArguments; 29 | 30 | public Argument(object target, ParameterInfo mi) 31 | : base(target, mi) 32 | { 33 | _default = null; 34 | _required = true; 35 | _allArguments = Parameter.IsDefined(typeof(AllArgumentsAttribute), true); 36 | 37 | if (Parameter.DefaultValue != DBNull.Value) 38 | { 39 | _default = Parameter.DefaultValue; 40 | _required = false; 41 | } 42 | 43 | foreach (DefaultValueAttribute a in mi.GetCustomAttributes(typeof(DefaultValueAttribute), true)) 44 | { 45 | _default = a.Value; 46 | _required = false; 47 | } 48 | 49 | foreach (ArgumentAttribute a in mi.GetCustomAttributes(typeof(ArgumentAttribute), true)) 50 | { 51 | if (a.HasDefault) 52 | { 53 | _required = false; 54 | _default = a.DefaultValue; 55 | } 56 | } 57 | 58 | if (Visible && _description == mi.ToString()) 59 | { 60 | if (IsFlag) 61 | { 62 | if (Required) 63 | _description = String.Format("Required flag can be \"/{0}\" or \"/{0}:false\".", base.DisplayName); 64 | else 65 | _description = String.Format("Optional flag of \"/{0}\" or \"/{0}:false\".", base.DisplayName); 66 | } 67 | else 68 | { 69 | if (Required) 70 | _description = String.Format("Specifies the required value for \"{0}\" of type {1}.", base.DisplayName, UnderlyingType.Name); 71 | else 72 | _description = String.Format("Specifies an optional value for \"{0}\" of type {1}.", base.DisplayName, UnderlyingType.Name); 73 | } 74 | } 75 | } 76 | 77 | private ParameterInfo Parameter { get { return (ParameterInfo)base.Member; } } 78 | 79 | public Type Type { get { return Parameter.ParameterType; } } 80 | Type UnderlyingType { get { return Nullable.GetUnderlyingType(Parameter.ParameterType) ?? Parameter.ParameterType; } } 81 | 82 | public override bool Visible { get { return base.Visible && !IsInterpreter && !IsAllArguments; } } 83 | public bool Required { get { return _required; } } 84 | public bool IsFlag { get { return Parameter.ParameterType == typeof(bool); } } 85 | public bool IsInterpreter { get { return Parameter.ParameterType == typeof(ICommandInterpreter); } } 86 | public bool IsAllArguments { get { return _allArguments; } } 87 | public Object DefaultValue { get { return _default; } } 88 | 89 | internal Object GetArgumentValue(ICommandInterpreter interpreter, ArgumentList args, string[] allArguments) 90 | { 91 | object value = null; 92 | 93 | if (IsInterpreter) 94 | return interpreter; 95 | 96 | if (IsAllArguments) 97 | { 98 | args.Clear(); 99 | args.Unnamed.Clear(); 100 | return allArguments; 101 | } 102 | 103 | foreach (string name in AllNames) 104 | { 105 | ArgumentList.Item argitem; 106 | if (args.TryGetValue(name, out argitem)) 107 | { 108 | if (Parameter.ParameterType == typeof(string[])) 109 | value = argitem.Values; 110 | else if (IsFlag) 111 | { 112 | bool result; 113 | value = (String.IsNullOrEmpty(argitem.Value) || (bool.TryParse(argitem.Value, out result) && result)); 114 | } 115 | else 116 | value = argitem.Value; 117 | args.Remove(name); 118 | } 119 | } 120 | 121 | return base.ChangeType(value, Parameter.ParameterType, Required, DefaultValue); 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.Commands/ArgumentList.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2008-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 2 | /* Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | #endregion 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | using System.Text.RegularExpressions; 19 | using System.Text; 20 | 21 | namespace CSharpTest.Net.Commands 22 | { 23 | /// 24 | /// This is a private class as the means of sharing is to simply include the source file not 25 | /// reference a library. 26 | /// 27 | [System.Diagnostics.DebuggerNonUserCode] 28 | partial class ArgumentList : System.Collections.ObjectModel.KeyedCollection 29 | { 30 | #region Static Configuration Options 31 | static StringComparer _defaultCompare = StringComparer.OrdinalIgnoreCase; 32 | static char[] _prefix = new char[] { '/', '-' }; 33 | static char[] _delim = new char[] { ':', '=' }; 34 | static readonly string[] EmptyList = new string[0]; 35 | 36 | /// 37 | /// Controls the default string comparer used for this class 38 | /// 39 | public static StringComparer DefaultComparison 40 | { 41 | get { return _defaultCompare; } 42 | set 43 | { 44 | if (value == null) throw new ArgumentNullException(); 45 | _defaultCompare = value; 46 | } 47 | } 48 | 49 | /// 50 | /// Controls the allowable prefix characters that will preceed named arguments 51 | /// 52 | public static char[] PrefixChars 53 | { 54 | get { return (char[])_prefix.Clone(); } 55 | set 56 | { 57 | if (value == null) throw new ArgumentNullException(); 58 | if (value.Length == 0) throw new ArgumentOutOfRangeException(); 59 | _prefix = (char[])value.Clone(); 60 | } 61 | } 62 | /// 63 | /// Controls the allowable delimeter characters seperate argument names from values 64 | /// 65 | public static char[] NameDelimeters 66 | { 67 | get { return (char[])_delim.Clone(); } 68 | set 69 | { 70 | if (value == null) throw new ArgumentNullException(); 71 | if (value.Length == 0) throw new ArgumentOutOfRangeException(); 72 | _delim = (char[])value.Clone(); 73 | } 74 | } 75 | #endregion Static Configuration Options 76 | 77 | readonly List _unnamed; 78 | /// 79 | /// Initializes a new instance of the ArgumentList class using the argument list provided 80 | /// 81 | public ArgumentList(params string[] arguments) : this(DefaultComparison, arguments) { } 82 | /// 83 | /// Initializes a new instance of the ArgumentList class using the argument list provided 84 | /// and using the string comparer provided, by default this is case-insensitive 85 | /// 86 | public ArgumentList(StringComparer comparer, params string[] arguments) 87 | : base(comparer, 0) 88 | { 89 | _unnamed = new List(); 90 | this.AddRange(arguments); 91 | } 92 | 93 | /// 94 | /// Returns a list of arguments that did not start with a character in the PrefixChars 95 | /// static collection. These arguments can be modified by the methods on the returned 96 | /// collection, or you set this property to a new collection (a copy is made). 97 | /// 98 | public IList Unnamed 99 | { 100 | get { return _unnamed; } 101 | set 102 | { 103 | _unnamed.Clear(); 104 | if (value != null) 105 | _unnamed.AddRange(value); 106 | } 107 | } 108 | 109 | /// 110 | /// Parses the strings provided for switch names and optionally values, by default in one 111 | /// of the following forms: "/name=value", "/name:value", "-name=value", "-name:value" 112 | /// 113 | public void AddRange(params string[] arguments) 114 | { 115 | if (arguments == null) throw new ArgumentNullException(); 116 | 117 | foreach (string arg in arguments) 118 | { 119 | string name, value; 120 | if (TryParseNameValue(arg, out name, out value)) 121 | Add(name, value); 122 | else 123 | _unnamed.Add(CleanArgument(arg)); 124 | } 125 | } 126 | 127 | /// 128 | /// Adds a name/value pair to the collection of arguments, if value is null the name is 129 | /// added with no values. 130 | /// 131 | public void Add(string name, string value) 132 | { 133 | if (name == null) 134 | throw new ArgumentNullException(); 135 | 136 | Item item; 137 | if (!TryGetValue(name, out item)) 138 | base.Add(item = new Item(name)); 139 | 140 | if (value != null) 141 | item.Add(value); 142 | } 143 | 144 | /// 145 | /// A string collection of all keys in the arguments 146 | /// 147 | public string[] Keys 148 | { 149 | get 150 | { 151 | if (Dictionary == null) return new string[0]; 152 | List list = new List(Dictionary.Keys); 153 | list.Sort(); 154 | return list.ToArray(); 155 | } 156 | } 157 | 158 | /// 159 | /// Returns true if the value was found by that name and set the output value 160 | /// 161 | public bool TryGetValue(string name, out Item value) 162 | { 163 | if (name == null) 164 | throw new ArgumentNullException(); 165 | 166 | if (Dictionary != null) 167 | return Dictionary.TryGetValue(name, out value); 168 | value = null; 169 | return false; 170 | } 171 | 172 | /// 173 | /// Returns true if the value was found by that name and set the output value 174 | /// 175 | public bool TryGetValue(string name, out string value) 176 | { 177 | if (name == null) 178 | throw new ArgumentNullException(); 179 | 180 | Item test; 181 | if (Dictionary != null && Dictionary.TryGetValue(name, out test)) 182 | { 183 | value = test.Value; 184 | return true; 185 | } 186 | value = null; 187 | return false; 188 | } 189 | 190 | /// 191 | /// Returns an Item of name even if it does not exist 192 | /// 193 | public Item SafeGet(string name) 194 | { 195 | Item result; 196 | if (TryGetValue(name, out result)) 197 | return result; 198 | return new Item(name, null); 199 | } 200 | 201 | #region Protected / Private operations... 202 | 203 | static string CleanArgument(string argument) 204 | { 205 | if (argument == null) throw new ArgumentNullException(); 206 | if (argument.Length >= 2 && argument[0] == '"' && argument[argument.Length - 1] == '"') 207 | argument = argument.Substring(1, argument.Length - 2).Replace("\"\"", "\""); 208 | return argument; 209 | } 210 | 211 | /// 212 | /// Attempts to parse a name value pair from '/name=value' format 213 | /// 214 | public static bool TryParseNameValue(string argument, out string name, out string value) 215 | { 216 | argument = CleanArgument(argument);//strip quotes 217 | name = value = null; 218 | 219 | if (String.IsNullOrEmpty(argument) || 0 != argument.IndexOfAny(_prefix, 0, 1)) 220 | return false; 221 | 222 | name = argument.Substring(1); 223 | if (String.IsNullOrEmpty(name)) 224 | return false; 225 | 226 | int endName = name.IndexOfAny(_delim, 1); 227 | 228 | if (endName > 0) 229 | { 230 | value = name.Substring(endName + 1); 231 | name = name.Substring(0, endName); 232 | } 233 | 234 | return true; 235 | } 236 | 237 | /// 238 | /// Searches the arguments until it finds a switch or value by the name in find and 239 | /// if found it will: 240 | /// A) Remove the item from the arguments 241 | /// B) Set the out parameter value to any value found, or null if just '/name' 242 | /// C) Returns true that it was found and removed. 243 | /// 244 | public static bool Remove(ref string[] arguments, string find, out string value) 245 | { 246 | value = null; 247 | for (int i = 0; i < arguments.Length; i++) 248 | { 249 | string name, setting; 250 | if (TryParseNameValue(arguments[i], out name, out setting) && 251 | _defaultCompare.Equals(name, find)) 252 | { 253 | List args = new List(arguments); 254 | args.RemoveAt(i); 255 | arguments = args.ToArray(); 256 | value = setting; 257 | return true; 258 | } 259 | } 260 | return false; 261 | } 262 | 263 | /// 264 | /// Abract override for extracting key 265 | /// 266 | protected override string GetKeyForItem(ArgumentList.Item item) 267 | { 268 | return item.Name; 269 | } 270 | 271 | #endregion 272 | 273 | #region Item class used for collection 274 | /// 275 | /// This is a single named argument within an argument list collection, this 276 | /// can be implicitly assigned to a string, or a string[] array 277 | /// 278 | [System.Diagnostics.DebuggerNonUserCode] 279 | public class Item : System.Collections.ObjectModel.Collection 280 | { 281 | private readonly string _name; 282 | private readonly List _values; 283 | 284 | /// 285 | /// Constructs an item for the name and values provided. 286 | /// 287 | public Item(string name, params string[] items) 288 | : this(new List(), name, items) { } 289 | 290 | private Item(List impl, string name, string[] items) 291 | : base(impl) 292 | { 293 | if (name == null) 294 | throw new ArgumentNullException(); 295 | 296 | _name = name; 297 | _values = impl; 298 | if (items != null) 299 | _values.AddRange(items); 300 | } 301 | 302 | /// 303 | /// Returns the name of this item 304 | /// 305 | public string Name { get { return _name; } } 306 | 307 | /// 308 | /// Returns the first value of this named item or null if one doesn't exist 309 | /// 310 | public string Value 311 | { 312 | get { return _values.Count > 0 ? _values[0] : null; } 313 | set 314 | { 315 | _values.Clear(); 316 | if (value != null) 317 | _values.Add(value); 318 | } 319 | } 320 | 321 | /// 322 | /// Returns the collection of items in this named slot 323 | /// 324 | public string[] Values 325 | { 326 | get { return _values.ToArray(); } 327 | set 328 | { 329 | _values.Clear(); 330 | if (value != null) 331 | _values.AddRange(value); 332 | } 333 | } 334 | 335 | /// 336 | /// Same as the .Values property, returns the collection of items in this named slot 337 | /// 338 | /// 339 | public string[] ToArray() { return _values.ToArray(); } 340 | /// 341 | /// Add one or more values to this named item 342 | /// 343 | public void AddRange(IEnumerable items) { _values.AddRange(items); } 344 | 345 | /// 346 | /// Converts this item to key-value pair to rem to a dictionary 347 | /// 348 | public static implicit operator KeyValuePair(Item item) 349 | { 350 | if (item == null) throw new ArgumentNullException(); 351 | return new KeyValuePair(item.Name, item.Values); 352 | } 353 | 354 | /// 355 | /// Converts this item to a string by getting the first value or null if none 356 | /// 357 | public static implicit operator string(Item item) { return item == null ? null : item.Value; } 358 | 359 | /// 360 | /// Converts this item to array of strings 361 | /// 362 | public static implicit operator string[](Item item) { return item == null ? null : item.Values; } 363 | } 364 | 365 | #endregion Item class used for collection 366 | 367 | private class ArgReader 368 | { 369 | const char CharEmpty = (char)0; 370 | char[] _chars; 371 | int _pos; 372 | public ArgReader(string data) 373 | { 374 | _chars = data.ToCharArray(); 375 | _pos = 0; 376 | } 377 | 378 | public bool MoveNext() { _pos++; return _pos < _chars.Length; } 379 | public char Current { get { return (_pos < _chars.Length) ? _chars[_pos] : CharEmpty; } } 380 | public bool IsWhiteSpace { get { return Char.IsWhiteSpace(Current); } } 381 | public bool IsQuote { get { return (Current == '"'); } } 382 | public bool IsEOF { get { return _pos >= _chars.Length; } } 383 | } 384 | 385 | /// Parses the individual arguments from the given input string. 386 | public static string[] Parse(string rawtext) 387 | { 388 | List list = new List(); 389 | if (rawtext == null) 390 | throw new ArgumentNullException("rawtext"); 391 | ArgReader characters = new ArgReader(rawtext.Trim()); 392 | 393 | while (!characters.IsEOF) 394 | { 395 | if (characters.IsWhiteSpace) 396 | { 397 | characters.MoveNext(); 398 | continue; 399 | } 400 | 401 | StringBuilder sb = new StringBuilder(); 402 | 403 | if (characters.IsQuote) 404 | {//quoted string 405 | while (characters.MoveNext()) 406 | { 407 | if (characters.IsQuote) 408 | { 409 | if (!characters.MoveNext() || characters.IsWhiteSpace) 410 | break; 411 | } 412 | sb.Append(characters.Current); 413 | } 414 | } 415 | else 416 | { 417 | sb.Append(characters.Current); 418 | while (characters.MoveNext()) 419 | { 420 | if (characters.IsWhiteSpace) 421 | break; 422 | sb.Append(characters.Current); 423 | } 424 | } 425 | 426 | list.Add(sb.ToString()); 427 | } 428 | return list.ToArray(); 429 | } 430 | 431 | /// The inverse of Parse, joins the arguments together and properly escapes output 432 | [Obsolete("Consider migrating to EscapeArguments as it correctly escapes some situations that Join does not.")] 433 | public static string Join(params string[] arguments) 434 | { 435 | if (arguments == null) 436 | throw new ArgumentNullException("arguments"); 437 | char[] escaped = " \t\"&()[]{}^=;!'+,`~".ToCharArray(); 438 | 439 | StringBuilder sb = new StringBuilder(); 440 | foreach (string argument in arguments) 441 | { 442 | string arg = argument; 443 | 444 | if( arg.IndexOfAny(escaped) >= 0 ) 445 | sb.AppendFormat("\"{0}\"", arg.Replace("\"", "\"\"")); 446 | else 447 | sb.Append(arg); 448 | 449 | sb.Append(' '); 450 | } 451 | 452 | return sb.ToString(0, Math.Max(0, sb.Length-1)); 453 | } 454 | 455 | /// The 'more' correct escape/join for arguments 456 | public static string EscapeArguments(params string[] args) 457 | { 458 | StringBuilder arguments = new StringBuilder(); 459 | Regex invalidChar = new Regex("[\x00\x0a\x0d]");// these can not be escaped 460 | Regex needsQuotes = new Regex(@"\s|""");// contains whitespace or two quote characters 461 | Regex escapeQuote = new Regex(@"(\\*)(""|$)");// one or more '\' followed with a quote or end of string 462 | for (int carg = 0; args != null && carg < args.Length; carg++) 463 | { 464 | if (args[carg] == null) { throw new ArgumentNullException("args[" + carg + "]"); } 465 | if (invalidChar.IsMatch(args[carg])) { throw new ArgumentOutOfRangeException("args[" + carg + "]"); } 466 | if (args[carg] == String.Empty) { arguments.Append("\"\""); } 467 | else if (!needsQuotes.IsMatch(args[carg])) { arguments.Append(args[carg]); } 468 | else 469 | { 470 | arguments.Append('"'); 471 | arguments.Append(escapeQuote.Replace(args[carg], 472 | delegate(Match m) 473 | { 474 | return m.Groups[1].Value + m.Groups[1].Value + 475 | (m.Groups[2].Value == "\"" ? "\\\"" : ""); 476 | } 477 | )); 478 | arguments.Append('"'); 479 | } 480 | if (carg + 1 < args.Length) 481 | arguments.Append(' '); 482 | } 483 | return arguments.ToString(); 484 | } 485 | } 486 | } -------------------------------------------------------------------------------- /src/CSharpTest.Net.Commands/Attributes.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 2 | /* Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | #endregion 15 | using System; 16 | using System.Collections.Generic; 17 | using System.ComponentModel; 18 | 19 | namespace CSharpTest.Net.Commands 20 | { 21 | /// 22 | /// Defines an alias name for a command 23 | /// 24 | [Serializable] 25 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = true)] 26 | public class AliasNameAttribute : Attribute 27 | { 28 | private readonly string _alias; 29 | /// Constructs an AliasNameAttribute 30 | public AliasNameAttribute(string commandAlias) 31 | { 32 | _alias = commandAlias; 33 | } 34 | 35 | /// Returns the name of the alias 36 | public string Name { get { return _alias; } } 37 | } 38 | 39 | /// 40 | /// Instructs the CommandInterpreter to ignore a specific method/property 41 | /// 42 | [Serializable] 43 | [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = false)] 44 | public class IgnoreMemberAttribute : Attribute 45 | { 46 | /// Constructs an IgnoreMemberAttribute 47 | public IgnoreMemberAttribute() 48 | { 49 | } 50 | } 51 | /// 52 | /// Defines that the string[] argument accepts all arguments provided to the command, useage: 53 | /// void MyCommand([AllArguments] string[] arguments) 54 | /// or 55 | /// void MyCommand([AllArguments] string[] arguments, ICommandInterpreter ci) 56 | /// 57 | [Serializable] 58 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] 59 | public class AllArgumentsAttribute : Attribute 60 | { 61 | /// Constructs an AllArgumentsAttribute 62 | public AllArgumentsAttribute() 63 | { } 64 | } 65 | 66 | /// 67 | /// Provides all the display properties. 68 | /// 69 | public abstract class DisplayInfoAttribute : Attribute, IDisplayInfo 70 | { 71 | private string _displayName; 72 | private string[] _aliasNames; 73 | private string _category; 74 | private string _description; 75 | private bool _visible; 76 | 77 | /// Constructs the attribute 78 | protected DisplayInfoAttribute(string displayName, params string[] aliasNames) 79 | { 80 | _displayName = displayName; 81 | _aliasNames = aliasNames; 82 | _category = _description = null; 83 | _visible = true; 84 | } 85 | 86 | /// Returns the DisplayName 87 | public string DisplayName { get { return _displayName; } set { _displayName = value; } } 88 | /// Just the alias names 89 | public string[] AliasNames { get { return (string[])_aliasNames.Clone(); } set { _aliasNames = value; } } 90 | /// Returns the name list 91 | public string[] AllNames 92 | { 93 | get 94 | { 95 | List names = new List(); 96 | if (_displayName != null) names.Add(_displayName); 97 | names.AddRange(_aliasNames ?? new string[0]); 98 | return names.ToArray(); 99 | } 100 | } 101 | /// Returns the Category 102 | public string Category { get { return _category; } set { _category = value; } } 103 | /// Returns the Description 104 | public string Description { get { return _description; } set { _description = value; } } 105 | /// Returns the visibility of the command 106 | public virtual bool Visible { get { return _visible; } set { _visible = value; } } 107 | 108 | Type IDisplayInfo.ReflectedType { get { return null; } } 109 | void IDisplayInfo.AddAttribute(T attribute) { } 110 | bool IDisplayInfo.TryGetAttribute(out T found) 111 | { 112 | found = null; 113 | return false; 114 | } 115 | 116 | void IDisplayInfo.Help() { } 117 | } 118 | 119 | /// Contains display info and a default value 120 | public abstract class DisplayInfoAndValueAttribute : DisplayInfoAttribute 121 | { 122 | private object _defaultValue; 123 | private bool _hasDefault; 124 | 125 | /// Constructs the attribute 126 | protected DisplayInfoAndValueAttribute(string displayName, params string[] aliasNames) 127 | : base(displayName, aliasNames) 128 | { } 129 | 130 | /// Gets/sets the default value for the option 131 | public object DefaultValue { get { return _defaultValue; } set { _hasDefault = true; _defaultValue = value; } } 132 | 133 | /// Returns true if a default value was specified 134 | internal bool HasDefault { get { return _hasDefault; } } 135 | } 136 | 137 | /// 138 | /// Provides all the properties available for a command 'filter' that is 139 | /// called for every command invoked enabling custom processing of arguments 140 | /// and pre/post processing. The attribute is optional, the format of the 141 | /// the method prototype is not and must be: 142 | /// void (ICommandInterpreter interpreter, ICommandChain chain, string[] arguments); 143 | /// 144 | [Serializable] 145 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] 146 | public class CommandFilterAttribute : DisplayInfoAttribute 147 | { 148 | char[] _keys; 149 | 150 | /// Constructs the attribute 151 | public CommandFilterAttribute(char key) 152 | : this(new char[] { key}) 153 | { } 154 | 155 | /// Constructs the attribute 156 | public CommandFilterAttribute( params char[] keys ) 157 | : base(null, new string[0]) 158 | { 159 | _keys = keys; 160 | base.Visible = false; 161 | } 162 | 163 | /// Returns the keys associated with this filter 164 | public Char[] Keys { get { return _keys; } } 165 | 166 | /// Ignored. 167 | [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] 168 | public override bool Visible { get { return false; } set { } } 169 | } 170 | /// 171 | /// Provides all the properties available for a command. 172 | /// 173 | [Serializable] 174 | [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] 175 | public class CommandAttribute : DisplayInfoAttribute 176 | { 177 | /// Constructs the attribute 178 | public CommandAttribute() 179 | : base(null, new string[0]) 180 | { } 181 | /// Constructs the attribute 182 | public CommandAttribute(string displayName) 183 | : base(displayName, new string[0]) 184 | { } 185 | /// Constructs the attribute 186 | public CommandAttribute(string displayName, params string[] aliasNames) 187 | : base(displayName, aliasNames) 188 | { } 189 | } 190 | /// 191 | /// Provides all the properties available for an argument. 192 | /// 193 | [Serializable] 194 | [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false)] 195 | public class ArgumentAttribute : DisplayInfoAndValueAttribute 196 | { 197 | /// Constructs the attribute 198 | public ArgumentAttribute() 199 | : base(null, new string[0]) 200 | { } 201 | /// Constructs the attribute 202 | public ArgumentAttribute(string displayName) 203 | : base(displayName, new string[0]) 204 | { } 205 | /// Constructs the attribute 206 | public ArgumentAttribute(string displayName, params string[] aliasNames) 207 | : base(displayName, aliasNames) 208 | { } 209 | } 210 | /// 211 | /// Provides all the properties available for an argument. 212 | /// 213 | [Serializable] 214 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] 215 | public class OptionAttribute : DisplayInfoAndValueAttribute 216 | { 217 | /// Constructs the attribute 218 | public OptionAttribute() 219 | : base(null, new string[0]) 220 | { } 221 | /// Constructs the attribute 222 | public OptionAttribute(string displayName) 223 | : base(displayName, new string[0]) 224 | { } 225 | /// Constructs the attribute 226 | public OptionAttribute(string displayName, params string[] aliasNames) 227 | : base(displayName, aliasNames) 228 | { } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.Commands/CSharpTest.Net.Commands.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {7BD5EDD1-445C-46D1-A0B2-4B68CB51EADB} 9 | Library 10 | Properties 11 | CSharpTest.Net.Commands 12 | CSharpTest.Net.Commands 13 | v2.0 14 | 512 15 | 16 | 17 | 18 | True 19 | pdbonly 20 | false 21 | bin\ 22 | DEBUG;TRACE 23 | none 24 | 4 25 | true 26 | bin\CSharpTest.Net.Commands.XML 27 | 28 | 29 | True 30 | pdbonly 31 | true 32 | bin\ 33 | TRACE 34 | none 35 | 4 36 | true 37 | bin\CSharpTest.Net.Commands.XML 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | PreserveNewest 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.Commands/Command.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 2 | /* Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | #endregion 15 | using System; 16 | using System.Collections.Generic; 17 | using System.Reflection; 18 | using System.Diagnostics; 19 | using System.Text; 20 | using System.Runtime.Serialization.Formatters.Binary; 21 | using System.IO; 22 | using System.Runtime.Serialization; 23 | using System.Threading; 24 | 25 | namespace CSharpTest.Net.Commands 26 | { 27 | [System.Diagnostics.DebuggerDisplay("{Method}")] 28 | partial class Command : DisplayInfoBase, ICommand 29 | { 30 | Dictionary _names; 31 | Argument[] _arguments; 32 | 33 | public static ICommand Make(object target, MethodInfo mi) 34 | { 35 | ICommand cmd; 36 | if (CommandFilter.TryCreate(target, mi, out cmd)) 37 | return cmd; 38 | return new Command(target, mi); 39 | } 40 | 41 | protected Command(object target, MethodInfo mi) 42 | : base(target, mi) 43 | { 44 | ParameterInfo[] paramList = mi.GetParameters(); 45 | 46 | _names = new Dictionary(StringComparer.OrdinalIgnoreCase); 47 | List tempList = new List(); 48 | 49 | foreach (ParameterInfo pi in paramList) 50 | { 51 | Argument arg = new Argument(target, pi); 52 | foreach(string name in arg.AllNames) 53 | _names.Add(name, tempList.Count); 54 | tempList.Add(arg); 55 | } 56 | _arguments = tempList.ToArray(); 57 | 58 | if (base.Description == mi.ToString()) 59 | {//if no description provided, let's build a better one 60 | StringBuilder sb = new StringBuilder(); 61 | sb.AppendFormat("{0} ", base.DisplayName); 62 | foreach(Argument a in tempList) 63 | if(a.Visible) 64 | sb.AppendFormat("{0} ", a.FormatSyntax(a.DisplayName)); 65 | _description = sb.ToString(0, sb.Length - 1); 66 | } 67 | } 68 | 69 | public IArgument[] Arguments { get { return (Argument[])_arguments.Clone(); } } 70 | 71 | private MethodInfo Method { get { return (MethodInfo)base.Member; } } 72 | 73 | public virtual void Run(ICommandInterpreter interpreter, string[] arguments) 74 | { 75 | ArgumentList args = new ArgumentList(arguments); 76 | 77 | if (args.Count == 1 && args.Contains("?")) 78 | { Help(); return; } 79 | 80 | //translate ordinal referenced names 81 | Argument last = null; 82 | for (int i = 0; i < _arguments.Length && args.Unnamed.Count > 0; i++) 83 | { 84 | if (_arguments[i].Type == typeof (ICommandInterpreter)) 85 | break; 86 | last = _arguments[i]; 87 | args.Add(last.DisplayName, args.Unnamed[0]); 88 | args.Unnamed.RemoveAt(0); 89 | } 90 | 91 | if (last != null && args.Unnamed.Count > 0 && last.Type.IsArray) 92 | { 93 | for (int i = 0; i < _arguments.Length && args.Unnamed.Count > 0; i++) 94 | { 95 | args.Add(last.DisplayName, args.Unnamed[0]); 96 | args.Unnamed.RemoveAt(0); 97 | } 98 | } 99 | 100 | List invokeArgs = new List(); 101 | foreach (Argument arg in _arguments) 102 | { 103 | object argValue = arg.GetArgumentValue(interpreter, args, arguments); 104 | invokeArgs.Add(argValue); 105 | } 106 | 107 | //make sure we actually used all arguments. 108 | List names = new List(args.Keys); 109 | InterpreterException.Assert(names.Count == 0, "Unknown argument(s): {0}", String.Join(", ", names.ToArray())); 110 | InterpreterException.Assert(args.Unnamed.Count == 0, "Too many arguments supplied."); 111 | 112 | Invoke(Method, Target, invokeArgs.ToArray()); 113 | } 114 | 115 | [System.Diagnostics.DebuggerNonUserCode] 116 | [System.Diagnostics.DebuggerStepThrough] 117 | private static void Invoke(MethodInfo method, Object target, params Object[] invokeArgs) 118 | { 119 | try 120 | { 121 | method.Invoke(target, invokeArgs); 122 | } 123 | catch (TargetInvocationException te) 124 | { 125 | if (te.InnerException == null) 126 | throw; 127 | Exception innerException = te.InnerException; 128 | 129 | ThreadStart savestack = Delegate.CreateDelegate(typeof(ThreadStart), innerException, "InternalPreserveStackTrace", false, false) as ThreadStart; 130 | if(savestack != null) savestack(); 131 | throw innerException;// -- now we can re-throw without trashing the stack 132 | } 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.Commands/CommandFilter.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 2 | /* Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | #endregion 15 | using System; 16 | using System.Collections.Generic; 17 | using System.Reflection; 18 | 19 | namespace CSharpTest.Net.Commands 20 | { 21 | class CommandFilter : Command, ICommandFilter 22 | { 23 | static readonly char[] DefaultKey = new char[] { '*' }; 24 | readonly Char[] _keys; 25 | 26 | public static bool TryCreate(object target, MethodInfo mi, out ICommand command) 27 | { 28 | command = null; 29 | ParameterInfo[] args = mi.GetParameters(); 30 | if (args.Length == 3 && 31 | args[0].ParameterType == typeof(ICommandInterpreter) && 32 | args[1].ParameterType == typeof(ICommandChain) && 33 | args[2].ParameterType == typeof(string[])) 34 | { 35 | ExecuteFilter filter = (ExecuteFilter)Delegate.CreateDelegate(typeof(ExecuteFilter), target is Type ? null : target, mi, false); 36 | if (filter != null) 37 | command = new CommandFilter(target, mi, filter); 38 | } 39 | 40 | return command != null; 41 | } 42 | 43 | /// Returns the possible character keys for this filter when setting the precedence 44 | public Char[] Keys { get { return (Char[])_keys.Clone(); } } 45 | 46 | delegate void ExecuteFilter(ICommandInterpreter ci, ICommandChain chain, string[] args); 47 | readonly ExecuteFilter _filterProc; 48 | 49 | CommandFilter(object target, MethodInfo mi, ExecuteFilter filter) 50 | : base(target, mi) 51 | { 52 | _filterProc = filter; 53 | 54 | foreach (CommandFilterAttribute a in mi.GetCustomAttributes(typeof(CommandFilterAttribute), true)) 55 | _keys = a.Keys; 56 | 57 | if (_keys == null || _keys.Length == 0) 58 | _keys = DefaultKey; 59 | } 60 | 61 | public void Run(ICommandInterpreter ci, ICommandChain chain, string[] arguments) 62 | { 63 | _filterProc(ci, chain, arguments); 64 | } 65 | 66 | public override void Run(ICommandInterpreter interpreter, string[] arguments) 67 | { 68 | Run(interpreter, null, arguments); 69 | } 70 | } 71 | 72 | class FilterChainItem : ICommandChain 73 | { 74 | ICommandInterpreter _ci; 75 | ICommandFilter _filter; 76 | ICommandChain _next; 77 | 78 | public FilterChainItem(ICommandInterpreter ci, ICommandFilter filter, ICommandChain next) 79 | { 80 | _ci = ci; 81 | _filter = filter; 82 | _next = next; 83 | } 84 | 85 | public void Next(string[] arguments) 86 | { 87 | _filter.Run(_ci, _next, arguments); 88 | } 89 | } 90 | 91 | class LastFilter : ICommandChain 92 | { 93 | readonly CommandInterpreter _ci; 94 | public LastFilter(CommandInterpreter ci) 95 | { _ci = ci; } 96 | 97 | void ICommandChain.Next(string[] arguments) 98 | { 99 | _ci.ProcessCommand(arguments); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.Commands/CommandInterpreter.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 2 | /* Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | #endregion 15 | using System; 16 | using System.Collections.Generic; 17 | using System.ComponentModel; 18 | using System.Threading; 19 | using System.Reflection; 20 | using System.Text.RegularExpressions; 21 | using System.IO; 22 | using CommandTypes = global::CSharpTest.Net.Commands.DefaultCommands; 23 | using System.Diagnostics; 24 | 25 | namespace CSharpTest.Net.Commands 26 | { 27 | /// 28 | /// The primary class involved in providing a command-line interpreter. 29 | /// 30 | public partial class CommandInterpreter : ICommandInterpreter 31 | { 32 | readonly Dictionary _commands; 33 | readonly Dictionary _options; 34 | readonly List _filters; 35 | readonly BuiltInCommands _buildInCommands; 36 | 37 | private ReadNextCharacter _fnNextCh; 38 | private ICommandChain _head; 39 | private string _prompt; 40 | private string _filterPrecedence; 41 | 42 | /// 43 | /// Constructs a command-line interpreter from the objects and/or System.Types provided. 44 | /// 45 | public CommandInterpreter(params object[] handlers) 46 | : this(CommandTypes.Default, handlers) { } 47 | /// 48 | /// Constructs a command-line interpreter from the objects and/or System.Types provided. 49 | /// 50 | public CommandInterpreter(DefaultCommands defaultCmds, params object[] handlers) 51 | { 52 | _head = null; 53 | _prompt = "> "; 54 | _commands = new Dictionary(StringComparer.OrdinalIgnoreCase); 55 | _options = new Dictionary(StringComparer.OrdinalIgnoreCase); 56 | _filters = new List(); 57 | _fnNextCh = GetNextCharacter; 58 | 59 | //defaults to { Redirect, then Pipe, then everything else } 60 | _filterPrecedence = "<|*"; 61 | 62 | _buildInCommands = new BuiltInCommands( 63 | Command.Make(this, this.GetType().GetMethod("Get")), 64 | Command.Make(this, this.GetType().GetMethod("Set", new Type[] { typeof(string), typeof(object), typeof(bool) } )), 65 | Command.Make(this, this.GetType().GetMethod("Help", new Type[] { typeof(string), typeof(bool) } )), 66 | Option.Make(this, this.GetType().GetProperty("ErrorLevel")), 67 | Option.Make(this, this.GetType().GetProperty("Prompt")) 68 | ); 69 | 70 | _buildInCommands.Add(this, defaultCmds); 71 | 72 | foreach (object o in handlers) 73 | AddHandler(o); 74 | } 75 | 76 | #region AddHandler, AddCommand, AddOption 77 | 78 | /// 79 | /// Adds the static methods to the command list, and static properties to the list of 80 | /// global options (used with commands set/get) 81 | /// 82 | public void AddHandler(Type targetObject) 83 | { this.AddHandler(targetObject); } 84 | 85 | /// 86 | /// Adds the instance methods to the command list, and instance properties to the list of 87 | /// global options (used with commands set/get) 88 | /// 89 | public void AddHandler(T targetObject) where T : class 90 | { 91 | BindingFlags flags = BindingFlags.Public | BindingFlags.IgnoreCase; 92 | Type type = targetObject as Type; 93 | if (type == null) 94 | { 95 | flags |= BindingFlags.Instance; 96 | type = targetObject.GetType(); 97 | } 98 | else 99 | flags |= BindingFlags.Static; 100 | 101 | MethodInfo[] methods = type.GetMethods(flags | BindingFlags.InvokeMethod); 102 | foreach (MethodInfo method in methods) 103 | { 104 | if (method.IsSpecialName || method.DeclaringType == typeof(Object) || 105 | method.GetCustomAttributes(typeof(IgnoreMemberAttribute),true).Length > 0) 106 | continue; 107 | ICommand command = Command.Make(targetObject, method); 108 | if (command is ICommandFilter) 109 | AddFilter((ICommandFilter)command); 110 | else 111 | AddCommand(command); 112 | } 113 | PropertyInfo[] props = type.GetProperties(flags | BindingFlags.GetProperty | BindingFlags.SetProperty); 114 | foreach (PropertyInfo prop in props) 115 | { 116 | if (!prop.CanRead || !prop.CanWrite || prop.GetIndexParameters().Length > 0 || 117 | prop.GetCustomAttributes(typeof(IgnoreMemberAttribute), true).Length > 0) 118 | continue; 119 | AddOption(Option.Make(targetObject, prop)); 120 | } 121 | } 122 | 123 | /// Manually adds a command 124 | public void AddCommand(ICommand command) 125 | { 126 | foreach (string key in command.AllNames) 127 | { 128 | if (String.IsNullOrEmpty(key)) 129 | continue; 130 | 131 | InterpreterException.Assert(false == _commands.ContainsKey(key), "Command {0} already exists.", key); 132 | _commands.Add(key, command); 133 | } 134 | } 135 | 136 | /// Manually remove a command 137 | public void RemoveCommand(ICommand command) 138 | { 139 | foreach (string key in command.AllNames) 140 | { 141 | if (String.IsNullOrEmpty(key)) 142 | continue; 143 | _commands.Remove(key); 144 | } 145 | } 146 | 147 | /// 148 | /// Adds a command 'filter' that is called for every command invoked enabling custom processing 149 | /// of arguments and pre/post processing. 150 | /// 151 | public void AddFilter(ICommandFilter filter) 152 | { 153 | _filters.Remove(filter); 154 | _filters.Add(filter); 155 | _head = null; 156 | } 157 | 158 | /// Manually adds an option 159 | public void AddOption(IOption option) 160 | { 161 | foreach (string key in option.AllNames) 162 | { 163 | InterpreterException.Assert(false == _options.ContainsKey(key), "Option {0} already exists.", key); 164 | _options.Add(key, option); 165 | } 166 | } 167 | 168 | #endregion 169 | 170 | /// Gets/sets the exit code of the operation/process 171 | [Option(Category = "Built-in", Description = "Gets or sets the exit code of the operation.")] 172 | public int ErrorLevel { get { return Environment.ExitCode; } set { Environment.ExitCode = value; } } 173 | 174 | /// Gets/sets the prompt, use "$(OptionName)" to reference options 175 | [Option(Category = "Built-in", Description = "Gets or sets the text to display to prompt for input use \"$(OptionName)\" to reference options.")] 176 | public string Prompt { get { return _prompt; } set { _prompt = value ?? String.Empty; } } 177 | 178 | /// 179 | /// Lists all the commands that have been added to the interpreter 180 | /// 181 | public ICommand[] Commands 182 | { 183 | get 184 | { 185 | List cmds = new List(); 186 | foreach (ICommand item in _commands.Values) 187 | if (!cmds.Contains(item)) cmds.Add(item); 188 | cmds.Sort(new OrderByName()); 189 | return cmds.ToArray(); 190 | } 191 | } 192 | /// 193 | /// Lists all the options that have been added to the interpreter, use the set/get commands 194 | /// to modify their values. 195 | /// 196 | public IOption[] Options 197 | { 198 | get 199 | { 200 | List opts = new List(); 201 | foreach (IOption item in _options.Values) 202 | if (!opts.Contains(item)) opts.Add(item); 203 | opts.Sort(new OrderByName()); 204 | return opts.ToArray(); 205 | } 206 | } 207 | /// Lists all the filters that have been added to the interpreter 208 | public ICommandFilter[] Filters 209 | { 210 | get 211 | { 212 | List filters = new List(_filters); 213 | filters.Sort(new OrderByName()); 214 | return filters.ToArray(); 215 | } 216 | } 217 | 218 | /// 219 | /// Returns true if the command was found and cmd output parameter is set. 220 | /// 221 | public bool TryGetCommand(string name, out ICommand cmd) 222 | { 223 | return _commands.TryGetValue(name, out cmd); 224 | } 225 | 226 | /// 227 | /// Returns true if the command was found and cmd output parameter is set. 228 | /// 229 | public bool TryGetOption(string name, out IOption cmd) 230 | { 231 | return _options.TryGetValue(name, out cmd); 232 | } 233 | 234 | /// Command to get an option value 235 | [Command(Category = "Built-in", Description = "Gets a global option by name")] 236 | public object Get(string property) 237 | { 238 | IOption opt; 239 | InterpreterException.Assert(_options.TryGetValue(property, out opt), "The option {0} was not found.", property); 240 | object value = opt.Value; 241 | Console.Out.WriteLine("{0}", value); 242 | return value; 243 | } 244 | 245 | /// 246 | /// Sets all options to their defined DefaultValue if supplied. 247 | /// 248 | public void SetDefaults() 249 | { 250 | foreach (var opt in Options) 251 | { 252 | if (!ReferenceEquals(null, opt.DefaultValue)) 253 | opt.Value = opt.DefaultValue; 254 | } 255 | } 256 | 257 | /// Command to set the value of an option 258 | [IgnoreMember] 259 | public void Set(string property, object value) { Set(property, value, false); } 260 | 261 | /// Command to set the value of an option 262 | [Command(Category = "Built-in", Description = "Sets a global option by name or lists options available.")] 263 | public void Set([DefaultValue(null)] string property, [DefaultValue(null)] object value, 264 | [DefaultValue(false),Description("Read from std::in lines formatted as NAME=VALUE")]bool readInput) 265 | { 266 | if (readInput) 267 | { 268 | string line; 269 | while (null != (line = Console.In.ReadLine())) 270 | Set(line, null, false); 271 | return; 272 | } 273 | if (property == null) 274 | { 275 | foreach (IOption opt in Options) 276 | Console.WriteLine("{0}={1}", opt.DisplayName, opt.Value); 277 | return; 278 | } 279 | else if (value == null && property.IndexOf('=') < 0) 280 | { 281 | Get(property); 282 | return; 283 | } 284 | else if (value == null) 285 | { 286 | string[] args = property.Split(new char[] { '=' }, 2); 287 | property = args[0].TrimEnd(); 288 | value = args[1].TrimStart(); 289 | } 290 | 291 | IOption option; 292 | InterpreterException.Assert(_options.TryGetValue(property, out option), "The option {0} was not found.", property); 293 | option.Value = value; 294 | } 295 | 296 | /// 297 | /// The last link in the command chain 298 | /// 299 | internal void ProcessCommand(string[] arguments) 300 | { 301 | if (arguments == null || arguments.Length == 0) 302 | { 303 | Help(null); 304 | return; 305 | } 306 | 307 | string commandName = arguments[0]; 308 | 309 | ICommand command; 310 | InterpreterException.Assert(_commands.TryGetValue(commandName, out command), "Invalid command name: {0}", commandName); 311 | 312 | List args = new List(); 313 | for (int i = 1; i < arguments.Length; i++) 314 | args.Add(ExpandOptions(arguments[i])); 315 | 316 | command.Run(this, args.ToArray()); 317 | } 318 | 319 | /// Used to stop running the interpreter 320 | public sealed class QuitException : OperationCanceledException { } 321 | 322 | [Command("Quit", "Exit", Visible = false)] 323 | private void Quit() { throw new QuitException(); } 324 | 325 | /// called to handle error events durring processing 326 | protected virtual void OnError(Exception error) 327 | { 328 | if(error is OperationCanceledException) 329 | {/* Silent */} 330 | else 331 | Console.Error.WriteLine(error is ApplicationException ? error.Message : error.ToString()); 332 | 333 | if (ErrorLevel == 0) 334 | ErrorLevel = 1; 335 | } 336 | 337 | /// Defines the filter precedence by appearance order of key character 338 | public string FilterPrecedence 339 | { 340 | get { return _filterPrecedence; } 341 | set { _filterPrecedence = value ?? String.Empty; _head = null; } 342 | } 343 | 344 | /// returns the chained filters 345 | private ICommandChain GetHead() 346 | { 347 | ICommandChain chain = _head; 348 | if (chain == null) 349 | { 350 | chain = new LastFilter(this); 351 | List filters = new List(_filters); 352 | filters.Sort(PrecedenceOrder); 353 | filters.Reverse();//add in reverse order 354 | foreach (ICommandFilter filter in filters) 355 | chain = new FilterChainItem(this, filter, chain); 356 | _head = chain; 357 | } 358 | return chain; 359 | } 360 | 361 | /// Compares the command filters in order of precendence 362 | private int PrecedenceOrder(ICommandFilter x, ICommandFilter y) 363 | { 364 | int posX = _filterPrecedence.IndexOfAny(x.Keys); 365 | posX = posX >= 0 ? posX : int.MaxValue; 366 | int posY = _filterPrecedence.IndexOfAny(y.Keys); 367 | posY = posY >= 0 ? posY : int.MaxValue; 368 | return posX.CompareTo(posY); 369 | } 370 | 371 | /// 372 | /// Run the command whos name is the first argument with the remaining arguments provided to the command 373 | /// as needed. 374 | /// 375 | public void Run(params string[] arguments) 376 | { 377 | try 378 | { 379 | GetHead().Next(arguments ?? new string[0]); 380 | } 381 | catch (System.Threading.ThreadAbortException) { throw; } 382 | catch (QuitException) { throw; } 383 | catch (Exception e) 384 | { 385 | OnError(e); 386 | } 387 | } 388 | 389 | /// 390 | /// Run the command whos name is the first argument with the remaining arguments provided to the command 391 | /// as needed. 392 | /// 393 | public void Run(string[] arguments, TextWriter mapstdout, TextWriter mapstderr, TextReader mapstdin) 394 | { 395 | TextWriter stdout = ConsoleOutput.Capture(mapstdout); 396 | TextWriter stderr = ConsoleError.Capture(mapstderr); 397 | TextReader stdin = ConsoleInput.Capture(mapstdin); 398 | try 399 | { 400 | GetHead().Next(arguments ?? new string[0]); 401 | } 402 | finally 403 | { 404 | ConsoleOutput.Restore(mapstdout, stdout); 405 | ConsoleError.Restore(mapstderr, stderr); 406 | ConsoleInput.Restore(mapstdin, stdin); 407 | } 408 | } 409 | 410 | /// 411 | /// Runs each line from the reader until EOF, can be used with Console.In 412 | /// 413 | public void Run(System.IO.TextReader input) 414 | { 415 | ICommand quit = Command.Make(this, this.GetType().GetMethod("Quit", BindingFlags.NonPublic| BindingFlags.Instance | BindingFlags.InvokeMethod)); 416 | AddCommand(quit); 417 | 418 | try 419 | { 420 | while (true) 421 | { 422 | try 423 | { 424 | Console.Write(ExpandOptions(Prompt)); 425 | 426 | string nextLine = input.ReadLine(); 427 | if (nextLine == null) 428 | break; 429 | 430 | string[] arguments = ArgumentList.Parse(nextLine); 431 | Run(arguments); 432 | } 433 | catch (System.Threading.ThreadAbortException) { throw; } 434 | catch (QuitException) { break; } 435 | catch (Exception e) 436 | { 437 | OnError(e); 438 | return; 439 | } 440 | Console.WriteLine(); 441 | } 442 | } 443 | finally 444 | { 445 | RemoveCommand(quit); 446 | } 447 | } 448 | 449 | static readonly Regex _optionName = new Regex(@"(?[\w]+)\)"); 450 | /// 451 | /// Expands '$(OptionName)' within the input string to the named option's value. 452 | /// 453 | public string ExpandOptions(string input) 454 | { 455 | // replaces $(OptionName) with value of OptionName 456 | return _optionName.Replace(input, 457 | delegate(Match m) 458 | { 459 | string optionName = m.Groups["Name"].Value; 460 | InterpreterException.Assert(_options.ContainsKey(optionName), "Unknown option specified: {0}", optionName); 461 | return String.Format("{0}", _options[optionName].Value); 462 | } 463 | ).Replace("$$", "$"); 464 | } 465 | 466 | /// Default inplementation of get keystroke 467 | private Char GetNextCharacter() 468 | { 469 | return Console.ReadKey(true).KeyChar; 470 | } 471 | 472 | /// 473 | /// Reads a keystroke, not from the std:in stream, rather from the console or ui. 474 | /// 475 | public ReadNextCharacter ReadNextCharacter 476 | { 477 | get { return _fnNextCh; } 478 | set 479 | { 480 | if (value == null) throw new ArgumentNullException(); 481 | _fnNextCh = value; 482 | } 483 | } 484 | 485 | /// 486 | /// Adds the specified attribute to every command argument by the given name. 487 | /// 488 | public void AddGlobalArgumentAttribute(string argumentName, Attribute attribute) 489 | { 490 | foreach (var command in _commands.Values) 491 | { 492 | foreach (var argument in command.Arguments) 493 | { 494 | if (argument.DisplayName == argumentName) 495 | argument.AddAttribute(attribute); 496 | } 497 | } 498 | } 499 | 500 | #region ConsoleWriter/ConsoleOutput/ConsoleError/ConsoleInput 501 | private abstract class ConsoleWriter : TextWriter 502 | { 503 | protected abstract TextWriter Writer { get; } 504 | public override void Close() { Writer.Close(); } 505 | protected override void Dispose(bool disposing) { } 506 | public override void Flush() { Writer.Flush(); } 507 | public override void Write(char value) { Writer.Write(value); } 508 | public override void Write(char[] buffer) { Writer.Write(buffer); } 509 | public override void Write(char[] buffer, int index, int count) { Writer.Write(buffer, index, count); } 510 | public override void Write(string value) { Writer.Write(value); } 511 | public override System.Text.Encoding Encoding { get { return Writer.Encoding; } } 512 | } 513 | private sealed class ConsoleOutput : ConsoleWriter 514 | { 515 | private static readonly ConsoleOutput _instance = new ConsoleOutput(); 516 | private static TextWriter _default, _expected; 517 | private static TextWriter _global; 518 | private static int _referenceCount = 0; 519 | [ThreadStatic] 520 | private static TextWriter _writer; 521 | public static TextWriter Capture(TextWriter output) 522 | { 523 | if (output == null) return null; 524 | lock (typeof (Console)) 525 | { 526 | if (1 == Interlocked.Increment(ref _referenceCount)) 527 | { 528 | _default = Console.Out; 529 | Console.SetOut(_instance); 530 | _expected = Console.Out; 531 | } 532 | else if (!ReferenceEquals(_expected, Console.Out)) 533 | { 534 | Console.SetOut(_instance); 535 | _expected = Console.Out; 536 | } 537 | 538 | Interlocked.Exchange(ref _global, output); 539 | return Interlocked.Exchange(ref _writer, output); 540 | } 541 | } 542 | public static void Restore(TextWriter replaced, TextWriter original) 543 | { 544 | if (replaced == null) return; 545 | lock (typeof (Console)) 546 | { 547 | Interlocked.CompareExchange(ref _writer, original, replaced); 548 | Interlocked.CompareExchange(ref _global, original, replaced); 549 | 550 | if (0 == Interlocked.Decrement(ref _referenceCount)) 551 | { 552 | Console.SetOut(_default); 553 | _default = null; 554 | } 555 | else if (!ReferenceEquals(_expected, Console.Out)) 556 | { 557 | Console.SetOut(_instance); 558 | _expected = Console.Out; 559 | } 560 | } 561 | } 562 | protected override TextWriter Writer { get { return _writer ?? _global ?? _default; } } 563 | } 564 | private sealed class ConsoleError : ConsoleWriter 565 | { 566 | private static readonly ConsoleError _instance = new ConsoleError(); 567 | private static TextWriter _default, _expected; 568 | private static TextWriter _global; 569 | private static int _referenceCount = 0; 570 | private ConsoleError() { } 571 | [ThreadStatic] 572 | private static TextWriter _writer; 573 | public static TextWriter Capture(TextWriter output) 574 | { 575 | if (output == null) return null; 576 | lock (typeof(Console)) 577 | { 578 | if (1 == Interlocked.Increment(ref _referenceCount)) 579 | { 580 | _default = Console.Error; 581 | Console.SetError(_instance); 582 | _expected = Console.Error; 583 | } 584 | else if (!ReferenceEquals(_expected, Console.Error)) 585 | { 586 | Console.SetError(_instance); 587 | _expected = Console.Error; 588 | } 589 | 590 | Interlocked.Exchange(ref _global, output); 591 | return Interlocked.Exchange(ref _writer, output); 592 | } 593 | } 594 | public static void Restore(TextWriter replaced, TextWriter original) 595 | { 596 | if (replaced == null) return; 597 | lock (typeof(Console)) 598 | { 599 | Interlocked.CompareExchange(ref _writer, original, replaced); 600 | Interlocked.CompareExchange(ref _global, original, replaced); 601 | 602 | if (0 == Interlocked.Decrement(ref _referenceCount)) 603 | { 604 | Console.SetError(_default); 605 | _default = null; 606 | } 607 | else if (!ReferenceEquals(_expected, Console.Error)) 608 | { 609 | Console.SetError(_instance); 610 | _expected = Console.Error; 611 | } 612 | } 613 | } 614 | protected override TextWriter Writer { get { return _writer ?? _global ?? _default; } } 615 | } 616 | private sealed class ConsoleInput : TextReader 617 | { 618 | private static readonly ConsoleInput _instance = new ConsoleInput(); 619 | private static TextReader _default, _expected; 620 | private static TextReader _global; 621 | private static int _referenceCount = 0; 622 | private ConsoleInput() { } 623 | [ThreadStatic] 624 | private static TextReader _reader; 625 | public static TextReader Capture(TextReader output) 626 | { 627 | if (output == null) return null; 628 | lock (typeof(Console)) 629 | { 630 | if (1 == Interlocked.Increment(ref _referenceCount)) 631 | { 632 | _default = Console.In; 633 | Console.SetIn(_instance); 634 | _expected = Console.In; 635 | } 636 | else if (!ReferenceEquals(_expected, Console.In)) 637 | { 638 | Console.SetIn(_instance); 639 | _expected = Console.In; 640 | } 641 | 642 | Interlocked.Exchange(ref _global, output); 643 | return Interlocked.Exchange(ref _reader, output); 644 | } 645 | } 646 | public static void Restore(TextReader replaced, TextReader original) 647 | { 648 | if (replaced == null) return; 649 | lock (typeof(Console)) 650 | { 651 | Interlocked.CompareExchange(ref _reader, original, replaced); 652 | Interlocked.CompareExchange(ref _global, original, replaced); 653 | 654 | if (0 == Interlocked.Decrement(ref _referenceCount)) 655 | { 656 | Console.SetIn(_default); 657 | _default = null; 658 | } 659 | else if (!ReferenceEquals(_expected, Console.In)) 660 | { 661 | Console.SetIn(_instance); 662 | _expected = Console.In; 663 | } 664 | } 665 | } 666 | private TextReader Reader { get { return _reader ?? _global ?? _default; } } 667 | public override void Close() { Reader.Close(); } 668 | protected override void Dispose(bool disposing) { } 669 | public override int Peek() { return Reader.Peek(); } 670 | public override int Read() { return Reader.Read(); } 671 | public override int Read(char[] buffer, int index, int count) { return Reader.Read(buffer, index, count); } 672 | public override string ReadToEnd() { return Reader.ReadToEnd(); } 673 | public override int ReadBlock(char[] buffer, int index, int count) { return Reader.ReadBlock(buffer, index, count); } 674 | public override string ReadLine() { return Reader.ReadLine(); } 675 | } 676 | #endregion 677 | } 678 | 679 | } 680 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.Commands/DefaultCommands.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 2 | /* Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | #endregion 15 | using System; 16 | using System.Collections.Generic; 17 | using System.IO; 18 | using System.Reflection; 19 | 20 | namespace CSharpTest.Net.Commands 21 | { 22 | /// 23 | /// A list of built-in commands that can be added to the interpreter 24 | /// 25 | [Flags] 26 | public enum DefaultCommands : uint 27 | { 28 | /// Not a command, indicates no default commands 29 | None = 0, 30 | /// Not a command, indicates the default commands added if not specified 31 | Default = Get | Set | Help, 32 | /// Not a command, indicates to use all default commands 33 | All = 0xFFFFFFFF, 34 | 35 | /// A command to get the value of an option 36 | Get = 0x00000001, 37 | /// A command to set the value of an option 38 | Set = 0x00000002, 39 | /// A command to display help about the commands and their options 40 | Help = 0x00000004, 41 | /// An option to set and get the environment error-level 42 | ErrorLevel = 0x00000008, 43 | /// An option that provides customization of the command prompt for interactive mode 44 | Prompt = 0x00000010, 45 | /// A command to echo back to std::out the arguments provided. 46 | Echo = 0x00000020, 47 | /// A command to read the input stream and show one screen at a time to standard output. 48 | More = 0x00000040, 49 | /// A command to search for a text string in a file or the standard input stream. 50 | Find = 0x00000080, 51 | /// A command filter that allows piping the output of one command into the input of another. 52 | PipeCommands = 0x00000100, 53 | /// A command filter that allows redirect of std in/out to files. 54 | IORedirect = 0x00000200, 55 | } 56 | 57 | partial class CommandInterpreter 58 | { 59 | sealed class BuiltInCommands 60 | { 61 | readonly Dictionary _contents; 62 | 63 | internal BuiltInCommands(params IDisplayInfo[] all) 64 | { 65 | _contents = new Dictionary(); 66 | foreach (IDisplayInfo d in BuiltIn.Commands) 67 | _contents.Add((DefaultCommands)Enum.Parse(typeof(DefaultCommands), d.DisplayName, true), d); 68 | AddRange(all); 69 | } 70 | 71 | public void AddRange(params IDisplayInfo[] all) 72 | { 73 | foreach (IDisplayInfo d in all) 74 | _contents.Add((DefaultCommands)Enum.Parse(typeof(DefaultCommands), d.DisplayName, true), d); 75 | } 76 | 77 | public void Add(CommandInterpreter ci, DefaultCommands cmds) 78 | { 79 | foreach (DefaultCommands key in Enum.GetValues(typeof(DefaultCommands))) 80 | { 81 | IDisplayInfo item; 82 | if (key == (key & cmds) && _contents.TryGetValue(key, out item)) 83 | { 84 | if (item is ICommandFilter) 85 | ci.AddFilter(item as ICommandFilter); 86 | else if (item is ICommand) 87 | ci.AddCommand(item as ICommand); 88 | else if (item is IOption) 89 | ci.AddOption(item as IOption); 90 | } 91 | } 92 | } 93 | 94 | internal static class BuiltIn 95 | { 96 | public static ICommand[] Commands 97 | { 98 | get 99 | { 100 | Type t = typeof(BuiltIn); 101 | List cmds = new List(); 102 | foreach (MethodInfo mi in t.GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.InvokeMethod)) 103 | if (!mi.IsSpecialName) 104 | cmds.Add(Command.Make(t, mi)); 105 | return cmds.ToArray(); 106 | } 107 | } 108 | 109 | [Command("Echo", Category = "Built-in", Description = "Writes the arguments to standard output.", Visible = true)] 110 | public static void Echo( 111 | [AllArguments, Argument(Category = "Built-in", Description = "The text to write to standard out.")] 112 | string[] args) 113 | { 114 | Console.WriteLine(ArgumentList.EscapeArguments(args)); 115 | } 116 | 117 | private static int NextRedirect(string[] args) 118 | { 119 | for (int ix = args.Length - 1; ix >= 0; ix--) 120 | if (args[ix].StartsWith("<") || args[ix].StartsWith(">")) return ix; 121 | return -1; 122 | } 123 | 124 | [CommandFilter('>', '<', Category = "Built-in", Description = "A command filter that allows redirect of std in/out to files.", Visible = false)] 125 | public static void IORedirect(ICommandInterpreter ci, ICommandChain chain, string[] args) 126 | { 127 | TextReader rin = null; 128 | TextWriter rout = null; 129 | int pos; 130 | try 131 | { 132 | while ((pos = NextRedirect(args)) > 0) 133 | { 134 | List cmd1 = new List(args); 135 | List cmd2 = new List(args); 136 | cmd1.RemoveRange(pos, cmd1.Count - pos); 137 | cmd2.RemoveRange(0, pos); 138 | 139 | args = cmd1.ToArray(); 140 | string file = String.Join(" ", cmd2.ToArray()); 141 | bool isout = file.StartsWith(">"); 142 | bool isappend = isout && file.StartsWith(">>"); 143 | file = file.TrimStart('>', '<').Trim(); 144 | 145 | if (!isout) 146 | rin = File.OpenText(file); 147 | else 148 | rout = isappend ? File.AppendText(file) : File.CreateText(file); 149 | } 150 | } 151 | catch 152 | { 153 | using (rin) 154 | using (rout) 155 | throw; 156 | } 157 | TextReader stdin = ConsoleInput.Capture(rin); 158 | TextWriter stdout = ConsoleOutput.Capture(rout); 159 | try 160 | { 161 | chain.Next(args); 162 | } 163 | finally 164 | { 165 | using (rin) 166 | ConsoleInput.Restore(rin, stdin); 167 | using (rout) 168 | ConsoleOutput.Restore(rout, stdout); 169 | } 170 | } 171 | 172 | [Command("Find", Category = "Built-in", Description = "Reads the input stream and shows any line containing the text specified.", Visible = true)] 173 | public static void Find( 174 | [Argument("text", Category = "Built-in", Description = "The text to search for in the input stream.")] 175 | string text, 176 | [Argument("filename", "f", Category = "Built-in", Description = "Specifies a file read and search, omit to use standard input.", DefaultValue = null)] 177 | string filename, 178 | [Argument("V", Category = "Built-in", Description = "Displays all lines NOT containing the specified string.", DefaultValue = false)] 179 | bool invert, 180 | [Argument("C", Category = "Built-in", Description = "Displays only the count of lines containing the string.", DefaultValue = false)] 181 | bool count, 182 | [Argument("I", Category = "Built-in", Description = "Ignores the case of characters when searching for the string.", DefaultValue = false)] 183 | bool ignoreCase 184 | ) 185 | { 186 | StringComparison cmp = StringComparison.Ordinal; 187 | if (ignoreCase) cmp = StringComparison.OrdinalIgnoreCase; 188 | 189 | StreamReader disposeMe = null; 190 | TextReader rdr = Console.In; 191 | if (!String.IsNullOrEmpty(filename)) 192 | rdr = disposeMe = File.OpenText(filename); 193 | try 194 | { 195 | int counter = 0; 196 | string line; 197 | while (null != (line = rdr.ReadLine())) 198 | { 199 | bool found = (line.IndexOf(text, cmp) >= 0); 200 | found = invert ? !found : found; 201 | if (found) 202 | { 203 | counter++; 204 | if (!count) Console.WriteLine(line); 205 | } 206 | } 207 | 208 | if (count) 209 | Console.WriteLine(counter); 210 | } 211 | finally 212 | { 213 | if (disposeMe != null) 214 | disposeMe.Dispose(); 215 | } 216 | } 217 | 218 | [Command("More", Category = "Built-in", Description = "Reads the input stream and shows one screen at a time to standard output.", Visible = true)] 219 | public static void More(ICommandInterpreter ci) 220 | { 221 | int pos = 2; 222 | int lines; 223 | try 224 | { 225 | lines = Console.WindowHeight; 226 | } 227 | catch (System.IO.IOException) 228 | { 229 | lines = 25; 230 | } 231 | 232 | string line; 233 | while (null != (line = Console.ReadLine())) 234 | { 235 | Console.WriteLine(line); 236 | if (++pos >= lines) 237 | { 238 | Console.Write("-- More --"); 239 | ci.ReadNextCharacter(); 240 | Console.WriteLine(); 241 | pos = 1; 242 | } 243 | } 244 | } 245 | 246 | private static int NextPipe(string[] args) 247 | { 248 | for (int ix = 0; ix < args.Length; ix++) 249 | if (args[ix].StartsWith("|")) return ix; 250 | return -1; 251 | } 252 | 253 | [CommandFilter('|', Category = "Built-in", Description = "A command filter that allows piping the output of one command into the input of another.", Visible = false)] 254 | public static void PipeCommands(ICommandInterpreter ci, ICommandChain chain, string[] args) 255 | { 256 | TextReader rdr = null, stdin = null; 257 | int pos; 258 | while ((pos = NextPipe(args)) > 0) 259 | { 260 | List cmd1 = new List(args); 261 | cmd1.RemoveRange(pos, cmd1.Count - pos); 262 | List cmd2 = new List(args); 263 | cmd2.RemoveRange(0, pos); 264 | cmd2[0] = cmd2[0].TrimStart('|'); 265 | if (cmd2[0].Length == 0) 266 | cmd2.RemoveAt(0); 267 | if (cmd2.Count == 0) 268 | { 269 | args = cmd1.ToArray(); 270 | break; 271 | } 272 | else 273 | args = cmd2.ToArray(); 274 | 275 | StringWriter wtr = new StringWriter(); 276 | TextWriter stdout = ConsoleOutput.Capture(wtr); 277 | stdin = ConsoleInput.Capture(rdr); 278 | 279 | try { chain.Next(cmd1.ToArray()); } 280 | finally 281 | { 282 | ConsoleInput.Restore(rdr, stdin); 283 | ConsoleOutput.Restore(wtr, stdout); 284 | } 285 | 286 | rdr = new StringReader(wtr.ToString()); 287 | } 288 | 289 | stdin = ConsoleInput.Capture(rdr); 290 | try { chain.Next(args); } 291 | finally 292 | { 293 | ConsoleInput.Restore(rdr, stdin); 294 | } 295 | } 296 | } 297 | } 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.Commands/DisplayInfoBase.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 2 | /* Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | #endregion 15 | using System; 16 | using System.Collections.Generic; 17 | using System.Reflection; 18 | using System.ComponentModel; 19 | 20 | namespace CSharpTest.Net.Commands 21 | { 22 | abstract class DisplayInfoBase 23 | { 24 | readonly string _name; 25 | readonly string[] _allNames; 26 | readonly Object _target; 27 | readonly ICustomAttributeProvider _member; 28 | private bool _visible; 29 | readonly string _category; 30 | object[] _attributes; 31 | protected string _description; 32 | readonly Type _reflectedType; 33 | 34 | public DisplayInfoBase(object target, ICustomAttributeProvider mi) 35 | { 36 | _target = target; 37 | _member = mi; 38 | 39 | if (mi is MethodInfo) 40 | _name = ((MethodInfo)mi).Name; 41 | else if (mi is ParameterInfo) 42 | _name = ((ParameterInfo)mi).Name; 43 | else if( mi is PropertyInfo) 44 | _name = ((PropertyInfo)mi).Name; 45 | 46 | InterpreterException.Assert(_name != null, "Unknown type " + mi.ToString()); 47 | 48 | _reflectedType = target == null ? null : target is Type ? (Type)target : target.GetType(); 49 | _description = _member.ToString(); 50 | _category = _target.GetType().Name; 51 | _visible = true; 52 | 53 | foreach (DisplayNameAttribute a in _member.GetCustomAttributes(typeof(DisplayNameAttribute), true)) 54 | _name = a.DisplayName; 55 | 56 | foreach (DescriptionAttribute a in _member.GetCustomAttributes(typeof(DescriptionAttribute), true)) 57 | _description = String.Format("{0}", a.Description); 58 | 59 | foreach (CategoryAttribute a in _member.GetCustomAttributes(typeof(CategoryAttribute), true)) 60 | _category = String.Format("{0}", a.Category); 61 | 62 | foreach (BrowsableAttribute a in _member.GetCustomAttributes(typeof(BrowsableAttribute), true)) 63 | _visible = a.Browsable; 64 | 65 | List names = new List(); 66 | 67 | foreach (DisplayInfoAttribute a in mi.GetCustomAttributes(typeof(DisplayInfoAttribute), true)) 68 | { 69 | if (!String.IsNullOrEmpty(a.DisplayName)) 70 | _name = a.DisplayName; 71 | names.AddRange(a.AliasNames); 72 | if (!String.IsNullOrEmpty(a.Description)) 73 | _description = a.Description; 74 | if (!String.IsNullOrEmpty(a.Category)) 75 | _category = a.Category; 76 | _visible &= a.Visible; 77 | } 78 | 79 | names.Insert(0, _name); 80 | foreach (AliasNameAttribute a in _member.GetCustomAttributes(typeof(AliasNameAttribute), true)) 81 | names.Add(a.Name); 82 | _allNames = names.ToArray(); 83 | 84 | try { _attributes = mi.GetCustomAttributes(true); } 85 | catch { _attributes = new object[0]; } 86 | } 87 | 88 | protected Object Target { get { return _target; } } 89 | protected ICustomAttributeProvider Member { get { return _member; } } 90 | 91 | public virtual Type ReflectedType { get { return _reflectedType; } } 92 | public virtual bool Visible { get { return _visible; } set { _visible = value; } } 93 | public virtual string DisplayName { get { return _name; } } 94 | public virtual string[] AllNames { get { return (string[])_allNames.Clone(); } } 95 | public virtual string Category { get { return _category; } } 96 | public virtual string Description { get { return _description; } } 97 | 98 | public void AddAttribute(T attribute) where T : Attribute 99 | { 100 | for (int i=0; i < _attributes.Length; i++) 101 | { 102 | if (_attributes[i] is T) 103 | { 104 | _attributes[i] = attribute; 105 | return; 106 | } 107 | } 108 | List all = new List(_attributes); 109 | all.Add(attribute); 110 | _attributes = all.ToArray(); 111 | } 112 | 113 | public bool TryGetAttribute(out T found) where T : Attribute 114 | { 115 | foreach (object attr in _attributes) 116 | { 117 | if (attr is T) 118 | { 119 | found = (T)attr; 120 | return true; 121 | } 122 | } 123 | found = null; 124 | return false; 125 | } 126 | 127 | /// Provides the standard type cohersion between types 128 | protected Object ChangeType(Object value, Type type, bool required, Object defaultValue) 129 | { 130 | if (value == null) 131 | { 132 | InterpreterException.Assert(required == false, "The value for {0} is required.", this.DisplayName); 133 | value = defaultValue; 134 | } 135 | InterpreterException.Assert(value != null || 136 | !type.IsValueType || Nullable.GetUnderlyingType(type) != null, 137 | "Can not set value of type {0} to null in {1}.", type, this.DisplayName); 138 | 139 | if (value != null) 140 | { 141 | try 142 | { 143 | var targetType = !type.IsValueType ? type : (Nullable.GetUnderlyingType(type) ?? type); 144 | 145 | if (targetType.IsEnum && value is string) 146 | value = Enum.Parse(targetType, value as string, true); 147 | if (targetType.IsEnum && Enum.IsDefined(targetType, value)) 148 | value = Enum.ToObject(targetType, value); 149 | else if (!targetType.IsAssignableFrom(value.GetType())) 150 | value = Convert.ChangeType(value, targetType); 151 | } 152 | catch (FormatException fe) 153 | { 154 | throw new InterpreterException(fe.Message, fe); 155 | } 156 | catch (ArgumentException ae) 157 | { 158 | throw new InterpreterException(ae.Message, ae); 159 | } 160 | } 161 | return value; 162 | } 163 | } 164 | 165 | } 166 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.Commands/Exceptions.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 2 | /* Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | #endregion 15 | using System; 16 | using System.Collections.Generic; 17 | 18 | namespace CSharpTest.Net.Commands 19 | { 20 | /// 21 | /// Base exception for assertions and errors encountered while processing commands 22 | /// 23 | [Serializable] 24 | [System.Diagnostics.DebuggerNonUserCode] 25 | public class InterpreterException : ApplicationException 26 | { 27 | /// 28 | /// Constructs an exception 29 | /// 30 | public InterpreterException(string text, params object[] format) 31 | : base(format.Length == 0 ? text : String.Format(text, format)) 32 | { } 33 | /// 34 | /// Constructs an exception 35 | /// 36 | public InterpreterException(string text, Exception innerException) 37 | : base(text, innerException) 38 | { } 39 | /// 40 | /// Constructs an exception durring deserialization 41 | /// 42 | protected InterpreterException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) 43 | : base(info, context) 44 | { } 45 | /// 46 | /// Asserts the condition and throws on failure 47 | /// 48 | internal static void Assert(bool cond, string text, params object[] format) 49 | { if (!cond) throw new InterpreterException(text, format); } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.Commands/HelpDisplay.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 2 | /* Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | #endregion 15 | using System; 16 | using System.Collections.Generic; 17 | using System.Diagnostics; 18 | using System.IO; 19 | using System.Net; 20 | using System.Reflection; 21 | using System.Text; 22 | using System.Xml; 23 | 24 | namespace CSharpTest.Net.Commands 25 | { 26 | partial class CommandInterpreter 27 | { 28 | /// Display the Help text to Console.Out 29 | [IgnoreMember] 30 | public void Help(string name) { Help(name, false); } 31 | 32 | /// Returns the Help as HTML text 33 | [IgnoreMember] 34 | public string GetHtmlHelp(string name) 35 | { 36 | ICommand cmd; 37 | IOption opt; 38 | if (name != null && _commands.TryGetValue(name, out cmd)) 39 | return GenerateHtml(cmd); 40 | else if (name != null && _options.TryGetValue(name, out opt)) 41 | return GenerateHtml(opt); 42 | else 43 | { 44 | List list = new List(Options); 45 | list.AddRange(Commands); 46 | return GenerateHtml(list.ToArray()); 47 | } 48 | } 49 | 50 | /// Display the Help text to Console.Out 51 | [Command("Help", "-?", "/?", "?", "--help", Category = "Built-in", Description = "Gets the help for a specific command or lists available commands.")] 52 | public void Help( 53 | [Argument("name", "command", "c", "option", "o", Description = "The name of the command or option to show help for.", DefaultValue = null)] 54 | string name, 55 | [Argument("html", DefaultValue = false, Description = "Output the full help content to HTML and view in the local browser.")] 56 | bool viewAsHtml 57 | ) 58 | { 59 | ICommand cmd; 60 | IOption opt; 61 | if (name != null && _commands.TryGetValue(name, out cmd)) 62 | { 63 | DisplayHelp(viewAsHtml, cmd); 64 | } 65 | else if (name != null && _options.TryGetValue(name, out opt)) 66 | { 67 | DisplayHelp(viewAsHtml, opt); 68 | } 69 | else 70 | { 71 | List list = new List(Options); 72 | list.AddRange(Commands); 73 | DisplayHelp(viewAsHtml, list.ToArray()); 74 | } 75 | } 76 | 77 | private void DisplayHelp(bool viewAsHtml, params IDisplayInfo[] items) 78 | { 79 | if (!viewAsHtml) 80 | { 81 | ShowHelp(items); 82 | } 83 | else 84 | { 85 | var path = Path.Combine(Path.GetTempPath(), AppDomain.CurrentDomain.FriendlyName + ".htm"); 86 | File.WriteAllText(path, GenerateHtml(items)); 87 | System.Diagnostics.Process.Start(path); 88 | } 89 | } 90 | 91 | private string GenerateHtml(params IDisplayInfo[] items) 92 | { 93 | Assembly exec = Assembly.GetEntryAssembly() 94 | ?? Assembly.GetCallingAssembly(); 95 | 96 | var programName = Process.GetCurrentProcess().ProcessName; 97 | 98 | using (StringWriter sw = new StringWriter()) 99 | using (XmlTextWriter w = new XmlTextWriter(sw)) 100 | { 101 | w.Formatting = System.Xml.Formatting.Indented; 102 | w.WriteStartElement("html"); 103 | w.WriteStartElement("head"); 104 | { 105 | w.WriteElementString("title", programName + " Help"); 106 | 107 | w.WriteStartElement("link"); 108 | w.WriteAttributeString("rel", "stylesheet"); 109 | w.WriteAttributeString("href", "http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css"); 110 | w.WriteEndElement(); 111 | 112 | //w.WriteStartElement("link"); 113 | //w.WriteAttributeString("rel", "stylesheet"); 114 | //w.WriteAttributeString("href", "http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap-theme.min.css"); 115 | //w.WriteEndElement(); 116 | 117 | w.WriteStartElement("style"); 118 | w.WriteAttributeString("type", "text/css"); 119 | w.WriteString(@" 120 | body { padding: 20px; } 121 | blockquote { font-size: inherit; } 122 | "); 123 | w.WriteEndElement(); 124 | } 125 | w.WriteEndElement(); 126 | w.WriteStartElement("body"); 127 | { 128 | w.WriteElementString("h1", "Usage:"); 129 | w.WriteStartElement("p"); 130 | w.WriteElementString("pre", String.Format("C:\\> {0} COMMAND [arguments]", programName)); 131 | w.WriteEndElement(); 132 | w.WriteElementString("p", System.Diagnostics.FileVersionInfo.GetVersionInfo(exec.Location).Comments); 133 | 134 | var options = new List(); 135 | var commands = new List(); 136 | 137 | foreach (IDisplayInfo info in items) 138 | { 139 | if (info.Visible && info is IOption) 140 | options.Add((IOption)info); 141 | else if (info.Visible && info is ICommand) 142 | commands.Add((ICommand)info); 143 | } 144 | items = null; 145 | 146 | if (options.Count > 0) 147 | { 148 | w.WriteElementString("h1", "Options:"); 149 | w.WriteStartElement("ul"); 150 | foreach (IOption option in options) 151 | { 152 | w.WriteStartElement("li"); 153 | w.WriteElementString("strong", String.Format("/{0}={1}", option.AllNames[0], option.Type.Name)); 154 | w.WriteRaw(" "); 155 | w.WriteString(option.Description.TrimEnd('.')); 156 | w.WriteString("."); 157 | w.WriteEndElement(); 158 | 159 | } 160 | w.WriteEndElement(); 161 | } 162 | if (commands.Count > 0) 163 | { 164 | w.WriteElementString("h1", "Commands:"); 165 | foreach (ICommand command in commands) 166 | { 167 | w.WriteElementString("h3", command.DisplayName); 168 | int argCount = 0; 169 | w.WriteStartElement("blockquote"); 170 | { 171 | w.WriteElementString("p", command.Description.TrimEnd('.') + "."); 172 | w.WriteElementString("strong", "Usage:"); 173 | w.WriteStartElement("p"); 174 | w.WriteStartElement("pre"); 175 | w.WriteString(String.Format("C:\\> {0} {1} ", programName, command.AllNames[0].ToUpper())); 176 | foreach (IArgument arg in command.Arguments) 177 | { 178 | if (arg.Visible == false || arg.IsInterpreter) 179 | continue; 180 | if (arg.IsAllArguments) 181 | { 182 | w.WriteString("[argument1] [argument2] [etc]"); 183 | continue; 184 | } 185 | argCount++; 186 | w.WriteString(String.Format("{0} ", arg.FormatSyntax(arg.DisplayName))); 187 | } 188 | w.WriteEndElement(); 189 | w.WriteEndElement(); 190 | } 191 | if (argCount > 0) 192 | { 193 | w.WriteStartElement("p"); 194 | w.WriteElementString("strong", "Arguments:"); 195 | w.WriteEndElement(); 196 | w.WriteStartElement("ul"); 197 | foreach (IArgument arg in command.Arguments) 198 | { 199 | if (arg.Visible == false || arg.IsInterpreter) 200 | continue; 201 | if (arg.IsAllArguments) 202 | continue; 203 | w.WriteStartElement("li"); 204 | w.WriteElementString("strong", arg.FormatSyntax(arg.DisplayName)); 205 | if (!arg.Required && arg.DefaultValue != null) 206 | w.WriteString(String.Format(" = ({0})", arg.DefaultValue)); 207 | w.WriteString(" - " + arg.Description.TrimEnd('.') + "."); 208 | w.WriteEndElement(); 209 | } 210 | w.WriteEndElement(); 211 | } 212 | 213 | w.WriteEndElement(); 214 | //w.WriteStartElement("hr"); 215 | //w.WriteEndElement(); 216 | } 217 | w.WriteEndElement(); 218 | } 219 | w.WriteStartElement("p"); 220 | w.WriteStartElement("hr"); 221 | w.WriteEndElement(); 222 | 223 | var entryAssembly = Assembly.GetEntryAssembly(); 224 | if (entryAssembly != null) 225 | { 226 | w.WriteStartElement("div"); 227 | w.WriteAttributeString("class", "text-muted"); 228 | { 229 | w.WriteString(String.Format("{0}", entryAssembly.GetName())); 230 | w.WriteStartElement("br"); 231 | w.WriteEndElement(); 232 | foreach (AssemblyCopyrightAttribute a in entryAssembly.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false)) 233 | w.WriteString(a.Copyright); 234 | w.WriteStartElement("br"); 235 | w.WriteEndElement(); 236 | } 237 | w.WriteEndElement(); 238 | } 239 | w.WriteEndElement(); 240 | } 241 | w.WriteEndElement(); 242 | w.Flush(); 243 | return sw.ToString(); 244 | } 245 | } 246 | 247 | /// 248 | /// Can be overridden to control or rewrite help output 249 | /// 250 | protected virtual void ShowHelp(IDisplayInfo[] items) 251 | { 252 | if (items.Length == 1) 253 | items[0].Help(); 254 | else 255 | ShowHelpFor(items); 256 | } 257 | 258 | private void ShowHelpFor(IDisplayInfo[] items) 259 | { 260 | Dictionary> found = new Dictionary>(StringComparer.OrdinalIgnoreCase); 261 | foreach (IDisplayInfo item in items) 262 | { 263 | if (!item.Visible) 264 | continue; 265 | 266 | List list; 267 | string group = item is Option ? "Options" : "Commands"/*item.Category*/; 268 | 269 | if (!found.TryGetValue(group, out list)) 270 | found.Add(group, list = new List()); 271 | if (!list.Contains(item)) 272 | list.Add(item); 273 | } 274 | 275 | List categories = new List(found.Keys); 276 | categories.Sort(); 277 | foreach (string cat in categories) 278 | { 279 | Console.Out.WriteLine("{0}:", cat); 280 | found[cat].Sort(new OrderByName()); 281 | 282 | int indent = 6; 283 | foreach (IDisplayInfo info in found[cat]) 284 | { 285 | if (info.DisplayName.Length > indent) 286 | indent = info.DisplayName.Length; 287 | } 288 | string fmt = " {0," + indent + "}: {1}"; 289 | foreach (IDisplayInfo info in found[cat]) 290 | { 291 | Console.Out.WriteLine(fmt, info.DisplayName.ToUpper(), info.Description); 292 | } 293 | Console.WriteLine(); 294 | } 295 | } 296 | } 297 | 298 | partial class Command 299 | { 300 | public void Help() 301 | { 302 | Console.WriteLine(); 303 | foreach (string name in this.AllNames) 304 | { 305 | Console.Write("{0} ", name.ToUpper()); 306 | foreach (IArgument arg in Arguments) 307 | { 308 | if (arg.Visible == false || arg.IsInterpreter) 309 | continue; 310 | if (arg.IsAllArguments) 311 | { 312 | Console.Write("[argument1] [argument2] [etc]"); 313 | continue; 314 | } 315 | 316 | Console.Write("{0} ", arg.FormatSyntax(arg.DisplayName)); 317 | } 318 | 319 | Console.WriteLine(); 320 | } 321 | 322 | //Console.WriteLine(); 323 | //Console.WriteLine("Category: {0}", this.Category); 324 | //Console.WriteLine("Type: {0}", this.target); 325 | //Console.WriteLine("Prototype: {0}", this.method); 326 | Console.WriteLine(); 327 | Console.WriteLine(this.Description); 328 | Console.WriteLine(); 329 | 330 | bool startedArgs = false; 331 | foreach (IArgument arg in Arguments) 332 | { 333 | if (arg.Visible == false || arg.IsInterpreter || arg.IsAllArguments) 334 | continue; 335 | if (!startedArgs) 336 | { 337 | Console.WriteLine("Arguments:"); 338 | Console.WriteLine(); 339 | startedArgs = true; 340 | } 341 | arg.Help(); 342 | } 343 | } 344 | } 345 | 346 | partial class Argument 347 | { 348 | public string FormatSyntax(string name) 349 | { 350 | StringBuilder sb = new StringBuilder(); 351 | if (!Required) sb.Append('['); 352 | if (!IsFlag) sb.Append('['); 353 | sb.Append('/'); 354 | sb.Append(name); 355 | if (!IsFlag) sb.AppendFormat("=]{0}", UnderlyingType.Name); 356 | if (!Required) sb.Append(']'); 357 | return sb.ToString(); 358 | } 359 | 360 | public void Help() 361 | { 362 | Console.Write(" {0}", FormatSyntax(DisplayName)); 363 | 364 | if (!Required && !IsFlag && DefaultValue != null) 365 | Console.Write(" ({0})", this.DefaultValue); 366 | 367 | List alt = new List(AllNames); 368 | alt.Remove(DisplayName); 369 | if( alt.Count > 0 ) 370 | Console.Write(" [/{0}{1}]", String.Join("=|/", alt.ToArray()), IsFlag ? String.Empty : "="); 371 | 372 | Console.Write(" {0}", this.Description); 373 | Console.WriteLine(); 374 | } 375 | } 376 | 377 | partial class Option 378 | { 379 | public void Help() 380 | { 381 | Console.WriteLine(); 382 | foreach (string name in this.AllNames) 383 | { 384 | Console.WriteLine("GET {0}", name.ToUpper()); 385 | Console.WriteLine("SET {0} [value]", name.ToUpper()); 386 | } 387 | 388 | //Console.WriteLine(); 389 | //Console.WriteLine("Category: {0}", this.Category); 390 | //Console.WriteLine("Type: {0}", this.target); 391 | //Console.WriteLine("Prototype: {0}", this.Property); 392 | Console.WriteLine(); 393 | Console.WriteLine(this.Description); 394 | Console.WriteLine(); 395 | } 396 | } 397 | 398 | } 399 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.Commands/Interfaces.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 2 | /* Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | #endregion 15 | using System; 16 | using System.Collections.Generic; 17 | using System.IO; 18 | 19 | namespace CSharpTest.Net.Commands 20 | { 21 | /// 22 | /// Used for obtaining input directly from user rather than from the std:in stream 23 | /// 24 | public delegate Char ReadNextCharacter(); 25 | 26 | /// 27 | /// Defines the interface for the command interpreter. If you use this as a parameter 28 | /// it will be provided auto-magically to your command. To avoid conflicts with ordinal 29 | /// argument matching, make this your last argument. 30 | /// 31 | public interface ICommandInterpreter 32 | { 33 | /// 34 | /// Gets/sets the exit code of the operation/process 35 | /// 36 | int ErrorLevel { get; set; } 37 | 38 | /// 39 | /// Gets/sets the prompt, use "$(OptionName)" to reference options 40 | /// 41 | string Prompt { get; set; } 42 | 43 | /// 44 | /// Lists all the commands that have been added to the interpreter 45 | /// 46 | ICommand[] Commands { get; } 47 | 48 | /// 49 | /// Returns true if the command was found and cmd output parameter is set. 50 | /// 51 | bool TryGetCommand(string name, out ICommand cmd); 52 | 53 | /// 54 | /// Lists all the options that have been added to the interpreter, use the set/get commands 55 | /// to modify their values. 56 | /// 57 | IOption[] Options { get; } 58 | 59 | /// 60 | /// Returns true if the command was found and cmd output parameter is set. 61 | /// 62 | bool TryGetOption(string name, out IOption cmd); 63 | 64 | /// 65 | /// Command to get an option value by name 66 | /// 67 | object Get(string property); 68 | 69 | /// 70 | /// Command to set the value of an option value by name 71 | /// 72 | void Set(string property, object value); 73 | 74 | /// 75 | /// Run the command whos name is the first argument with the remaining arguments provided to the command 76 | /// as needed. 77 | /// 78 | void Run(params string[] arguments); 79 | 80 | /// 81 | /// Run the command whos name is the first argument with the remaining arguments provided to the command 82 | /// as needed. 83 | /// 84 | void Run(string[] arguments, TextWriter mapstdout, TextWriter mapstderr, TextReader mapstdin); 85 | 86 | /// 87 | /// Runs each line from the reader until EOF, can be used with Console.In 88 | /// 89 | void Run(System.IO.TextReader input); 90 | 91 | /// 92 | /// Expands '$(OptionName)' within the input string to the named option's value. 93 | /// 94 | string ExpandOptions(string input); 95 | 96 | /// 97 | /// Reads a keystroke, not from the std:in stream, rather from the console or ui. 98 | /// 99 | ReadNextCharacter ReadNextCharacter { get; } 100 | 101 | /// 102 | /// Returns an HTML document for help on all items (when item == null) or a specific item. 103 | /// 104 | string GetHtmlHelp(string item); 105 | 106 | /// 107 | /// Sets all options to their defined DefaultValue if supplied. 108 | /// 109 | void SetDefaults(); 110 | } 111 | 112 | /// 113 | /// Defines an interface that allows a command filter to call to next filter in the chain 114 | /// 115 | public interface ICommandChain 116 | { 117 | /// 118 | /// Calls the next command filter in the chain, eventually processing the command 119 | /// 120 | void Next(string[] arguments); 121 | } 122 | 123 | /// A base interface that provides name and display information 124 | public interface IDisplayInfo 125 | { 126 | /// Returns the type this was reflected from, or null if created without reflection 127 | Type ReflectedType { get; } 128 | /// Returns the display name of the item 129 | string DisplayName { get; } 130 | /// Returns the name of the item 131 | string[] AllNames { get; } 132 | /// Returns the category if defined, or the type name if not 133 | string Category { get; } 134 | /// Returns the description of the item 135 | string Description { get; } 136 | /// Returns true if the items should be displayed. 137 | bool Visible { get; set; } 138 | /// Dynamically adds an attribute to the item. 139 | void AddAttribute(T attribute) where T : Attribute; 140 | /// Returns true if the attribute was found 141 | bool TryGetAttribute(out T found) where T : Attribute; 142 | /// Renders the help information to Console.Out 143 | void Help(); 144 | } 145 | 146 | /// 147 | /// Represents a static or instance method that will be invoked as a command 148 | /// 149 | public interface IArgument : IDisplayInfo 150 | { 151 | /// Returns true if the argument is required 152 | bool Required { get; } 153 | /// Returns the default value if Required == false 154 | object DefaultValue { get; } 155 | /// Returns the type of the argument 156 | Type Type { get; } 157 | /// Returns true if the property is a boolean switch 158 | bool IsFlag { get; } 159 | /// Returns true if this parameter is of type ICommandInterpreter 160 | bool IsInterpreter { get; } 161 | /// Returns true if this parameter is decorated with the [AllArguments] attribute 162 | bool IsAllArguments { get; } 163 | /// Writes the default syntax formatting for the argument using the provided name/alias 164 | string FormatSyntax(string name); 165 | } 166 | 167 | /// 168 | /// Represents a static or instance method that will be invoked as a command 169 | /// 170 | public interface ICommand : IDisplayInfo 171 | { 172 | /// Returns the arguments defined on this command. 173 | IArgument[] Arguments { get; } 174 | 175 | /// Runs this command with the supplied arguments 176 | void Run(ICommandInterpreter interpreter, string[] arguments); 177 | } 178 | 179 | /// 180 | /// Represents a static or instance method that is used to filter or pre/post process commands 181 | /// 182 | public interface ICommandFilter : ICommand 183 | { 184 | /// Returns the possible character keys for this filter when setting the precedence 185 | Char[] Keys { get; } 186 | 187 | /// 188 | /// Used to run a command through a set of filters, call chain.Next() to continue processing 189 | /// 190 | /// The command interpreter running the command 191 | /// The next link in the chain of filters 192 | /// The input arguments to the command-line 193 | void Run(ICommandInterpreter interpreter, ICommandChain chain, string[] arguments); 194 | } 195 | 196 | /// 197 | /// Defines an Option that can be configued/set independantly of the commands. Used with the set/get 198 | /// commands defined by the interpreter. 199 | /// 200 | public interface IOption : IDisplayInfo 201 | { 202 | /// 203 | /// Gets/sets the value of the option 204 | /// 205 | object Value { get; set; } 206 | /// Returns the type of the option value 207 | Type Type { get; } 208 | /// Returns the default value or NULL if undefined 209 | object DefaultValue { get; } 210 | } 211 | 212 | // Internal sorter for display name 213 | class OrderByName : IComparer 214 | where T : IDisplayInfo 215 | { 216 | int IComparer.Compare(T a, T b) 217 | { return StringComparer.OrdinalIgnoreCase.Compare(a.DisplayName, b.DisplayName); } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.Commands/LICENSE-2.0.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.Commands/Option.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 2 | /* Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | #endregion 15 | using System; 16 | using System.Collections.Generic; 17 | using System.Reflection; 18 | using System.ComponentModel; 19 | 20 | namespace CSharpTest.Net.Commands 21 | { 22 | [System.Diagnostics.DebuggerDisplay("{Property}")] 23 | partial class Option : DisplayInfoBase, IOption 24 | { 25 | readonly bool _required; 26 | readonly object _default; 27 | 28 | public static IOption Make(object target, PropertyInfo mi) 29 | { return new Option(target, mi); } 30 | 31 | Option(object target, PropertyInfo mi) 32 | : base(target, mi) 33 | { 34 | _default = null; 35 | _required = true; 36 | 37 | foreach (DefaultValueAttribute a in mi.GetCustomAttributes(typeof(DefaultValueAttribute), true)) 38 | { 39 | _required = false; 40 | this.Value = _default = a.Value; 41 | } 42 | 43 | foreach (OptionAttribute a in mi.GetCustomAttributes(typeof(OptionAttribute), true)) 44 | { 45 | if (a.HasDefault) 46 | { 47 | _required = false; 48 | _default = a.DefaultValue; 49 | } 50 | } 51 | } 52 | 53 | private PropertyInfo Property { get { return (PropertyInfo)base.Member; } } 54 | 55 | public Type Type { get { return Property.PropertyType; } } 56 | 57 | public bool Required { get { return _required; } } 58 | public Object DefaultValue { get { return _default; } } 59 | 60 | public Object Value 61 | { 62 | get { return Property.GetValue(base.Target, null); } 63 | set 64 | { 65 | Property.SetValue(base.Target, ChangeType(value, Property.PropertyType, Required, DefaultValue), null); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.Commands/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 2 | /* Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | #endregion 15 | using System.Reflection; 16 | using System.Runtime.InteropServices; 17 | 18 | [assembly: AssemblyTitle("CSharpTest.Net.Commands.dll")] 19 | [assembly: AssemblyDescription("Library of command-line parser and interpreter.")] 20 | [assembly: AssemblyProduct("http://CSharpTest.Net/Projects")] 21 | [assembly: AssemblyConfiguration("Debug")] 22 | 23 | [assembly: AssemblyCompany("Roger Knapp")] 24 | [assembly: AssemblyCopyright("Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0")] 25 | 26 | [assembly: AssemblyVersion("1.0.0.0")] 27 | [assembly: AssemblyFileVersion("1.0.0.0")] 28 | 29 | [assembly: ObfuscateAssembly(false)] 30 | [assembly: ComVisibleAttribute(false)] 31 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.Commands/Properties/Internal.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 2 | /* Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | #endregion 15 | namespace CSharpTest.Net.Commands 16 | { 17 | public partial class ArgumentList { } 18 | } 19 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.CommandsTest/CSharpTest.Net.CommandsTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 9.0.30729 7 | 2.0 8 | {097601FB-7E62-47DA-8E48-B56C9AD5DF20} 9 | Library 10 | CSharpTest.Net.CommandsTest 11 | CSharpTest.Net.Commands.Test 12 | v2.0 13 | True 14 | pdbonly 15 | none 16 | True 17 | 4 18 | 19 | 20 | false 21 | TRACE 22 | bin\ 23 | 24 | 25 | True 26 | TRACE 27 | bin\ 28 | 29 | 30 | 31 | ..\..\packages\NUnit.2.6.3\lib\nunit.framework.dll 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | {7bd5edd1-445c-46d1-a0b2-4b68cb51eadb} 42 | CSharpTest.Net.Commands 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.CommandsTest/TestArgumentList.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2008-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 2 | /* Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | #endregion 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | using CSharpTest.Net.Commands; 19 | using NUnit.Framework; 20 | 21 | #pragma warning disable 618 //CSharpTest.Net.Utils.ArgumentList.Join(params string[])' is obsolete 22 | #pragma warning disable 1591 23 | namespace CSharpTest.Net.CommandsTest 24 | { 25 | [TestFixture] 26 | [Category("TestArgumentList")] 27 | public partial class TestArgumentList 28 | { 29 | #region TestFixture SetUp/TearDown 30 | [TestFixtureSetUp] 31 | public virtual void Setup() 32 | { 33 | } 34 | 35 | [TestFixtureTearDown] 36 | public virtual void Teardown() 37 | { 38 | } 39 | #endregion 40 | 41 | [Test] 42 | public void Test() 43 | { 44 | ArgumentList args = new ArgumentList("-test=value", "/Test", "\"/other:value\""); 45 | Assert.AreEqual(2, args.Count); 46 | 47 | Assert.AreEqual(1, args[0].Count); 48 | Assert.AreEqual("test", args[0].Name); 49 | Assert.AreEqual("value", args[1].Value); 50 | 51 | Assert.AreEqual(1, args[1].Count); 52 | Assert.AreEqual("other", args[1].Name); 53 | Assert.AreEqual("value", args[1].Value); 54 | 55 | string[] keys = args.Keys; 56 | Assert.AreEqual(2, keys.Length); 57 | Assert.AreEqual("other", keys[0]);//alpha-sorted 58 | Assert.AreEqual("test", keys[1]); 59 | Assert.AreEqual(0, new ArgumentList("unnamed").Keys.Length); 60 | Assert.AreEqual(0, new ArgumentList(/*empty*/).Keys.Length); 61 | 62 | ArgumentList.DefaultComparison = StringComparer.Ordinal; 63 | Assert.AreEqual(StringComparer.Ordinal, ArgumentList.DefaultComparison); 64 | 65 | ArgumentList.NameDelimeters = new char[] { '=' }; 66 | Assert.AreEqual('=', ArgumentList.NameDelimeters[0]); 67 | 68 | ArgumentList.PrefixChars = new char[] { '/' }; 69 | Assert.AreEqual('/' , ArgumentList.PrefixChars[0]); 70 | 71 | args = new ArgumentList("-test=value", "/Test", "\"/other:value\""); 72 | Assert.AreEqual(2, args.Count); 73 | Assert.AreEqual(0, args[0].Count); 74 | Assert.AreEqual("Test", args[0].Name); 75 | Assert.AreEqual(null, args[1].Value); 76 | 77 | Assert.AreEqual(1, args.Unnamed.Count); 78 | foreach(string sval in args.Unnamed) 79 | Assert.AreEqual("-test=value", sval); 80 | 81 | Assert.AreEqual(0, args[1].Count); 82 | Assert.AreEqual("other:value", args[1].Name); 83 | Assert.AreEqual(null, args[1].Value); 84 | 85 | args.Unnamed = new string[0]; 86 | Assert.AreEqual(0, args.Unnamed.Count); 87 | 88 | args.Add("other", "value"); 89 | Assert.AreEqual(null, (string)args["Test"]); 90 | Assert.AreEqual("value", (string)args["other"]); 91 | Assert.AreEqual("value", (string)args.SafeGet("other")); 92 | Assert.IsNotNull(args.SafeGet("other-not-existing")); 93 | Assert.AreEqual(null, (string)args.SafeGet("other-not-existing")); 94 | 95 | string test; 96 | ArgumentList.Item item; 97 | 98 | args = new ArgumentList(); 99 | Assert.AreEqual(0, args.Count); 100 | Assert.IsFalse(args.TryGetValue(String.Empty, out item)); 101 | args.Add(String.Empty, null); 102 | Assert.IsTrue(args.TryGetValue(String.Empty, out item)); 103 | 104 | args = new ArgumentList(); 105 | Assert.AreEqual(0, args.Count); 106 | Assert.IsFalse(args.TryGetValue(String.Empty, out test)); 107 | args.Add(String.Empty, null); 108 | Assert.IsTrue(args.TryGetValue(String.Empty, out test)); 109 | 110 | test = item; 111 | Assert.IsNull(test); 112 | 113 | string[] testarry = item; 114 | Assert.IsNotNull(testarry); 115 | Assert.AreEqual(0, testarry.Length); 116 | 117 | item.Value = "roger"; 118 | Assert.AreEqual("roger", item.Value); 119 | Assert.AreEqual(1, item.Values.Length); 120 | Assert.AreEqual("roger", item.Values[0]); 121 | 122 | Assert.Contains("roger", item.ToArray()); 123 | Assert.AreEqual(1, item.ToArray().Length); 124 | 125 | item.AddRange(new string[] { "wuz", "here" }); 126 | Assert.AreEqual(3, item.Values.Length); 127 | Assert.AreEqual("roger wuz here", String.Join(" ", item)); 128 | 129 | item.Values = new string[] { "roger", "was", "here" }; 130 | Assert.AreEqual("roger was here", String.Join(" ", item)); 131 | 132 | KeyValuePair testkv = item; 133 | Assert.AreEqual(String.Empty, testkv.Key); 134 | Assert.AreEqual(3, testkv.Value.Length); 135 | Assert.AreEqual("roger was here", String.Join(" ", testkv.Value)); 136 | } 137 | 138 | [Test] 139 | public void TestUnnamed() 140 | { 141 | ArgumentList args = new ArgumentList("some", "/thing", "else"); 142 | Assert.AreEqual(2, args.Unnamed.Count); 143 | Assert.AreEqual(1, args.Count); 144 | Assert.IsTrue(args.Contains("thing")); 145 | Assert.AreEqual("some", args.Unnamed[0]); 146 | Assert.AreEqual("else", args.Unnamed[1]); 147 | 148 | args.Unnamed.RemoveAt(0); 149 | Assert.AreEqual(1, args.Unnamed.Count); 150 | Assert.AreEqual("else", args.Unnamed[0]); 151 | 152 | args.Clear(); 153 | Assert.AreEqual(0, args.Count); 154 | Assert.AreEqual(1, args.Unnamed.Count); 155 | 156 | args.Unnamed.Clear(); 157 | Assert.AreEqual(0, args.Unnamed.Count); 158 | } 159 | 160 | [Test] 161 | public void TestParseRemove() 162 | { 163 | //reset 164 | ArgumentList.PrefixChars = new char[] { '/', '-' }; 165 | ArgumentList.NameDelimeters = new char[] { '=', ':' }; 166 | 167 | string[] arguments = new string[] { "bla", "/one=1", "/two", "-", "/", "", "-three:3", "/four : 4", "/5", "/5:" }; 168 | 169 | int count = arguments.Length; 170 | string value; 171 | Assert.IsTrue(ArgumentList.Remove(ref arguments, "one", out value), "found item in array"); 172 | Assert.AreEqual(--count, arguments.Length, "was removed from array?"); 173 | Assert.AreEqual("1", value, "Extracted value correctly?"); 174 | 175 | Assert.IsTrue(ArgumentList.Remove(ref arguments, "two", out value), "found item in array"); 176 | Assert.AreEqual(--count, arguments.Length, "was removed from array?"); 177 | Assert.IsNull(value, "Extracted value correctly?"); 178 | 179 | Assert.IsTrue(ArgumentList.Remove(ref arguments, "three", out value), "found item in array"); 180 | Assert.AreEqual(--count, arguments.Length, "was removed from array?"); 181 | Assert.AreEqual("3", value, "Extracted value correctly?"); 182 | 183 | Assert.IsFalse(ArgumentList.Remove(ref arguments, "four", out value), "not found in array"); 184 | Assert.IsTrue(ArgumentList.Remove(ref arguments, "four ", out value), "found item in array"); 185 | Assert.AreEqual(--count, arguments.Length, "was removed from array?"); 186 | Assert.AreEqual(" 4", value, "Extracted value correctly?"); 187 | 188 | Assert.IsTrue(ArgumentList.Remove(ref arguments, "5", out value), "found item in array"); 189 | Assert.AreEqual(--count, arguments.Length, "was removed from array?"); 190 | Assert.IsNull(value, "Extracted value correctly?"); 191 | Assert.IsTrue(ArgumentList.Remove(ref arguments, "5", out value), "found item in array"); 192 | Assert.AreEqual(--count, arguments.Length, "was removed from array?"); 193 | Assert.AreEqual("", value, "Extracted value correctly?"); 194 | } 195 | 196 | [Test] 197 | public void TestParseJoin() 198 | { 199 | // all of these result in three argument values and should re-join exactly as appears 200 | string[] test_valid_strings = new string[] { 201 | "a b c", 202 | "a b \"c c\"", 203 | "a b \" c \"", 204 | "a \"b\"\"b\" c", 205 | "a \"\"\"b\"\"\" c", 206 | }; 207 | 208 | foreach (string testinput in test_valid_strings) 209 | { 210 | string[] result = ArgumentList.Parse(testinput); 211 | Assert.AreEqual(3, result.Length, "failed to find three values"); 212 | string joined = ArgumentList.Join(result); 213 | Assert.AreEqual(testinput, joined, "failed to parse/join correctly"); 214 | } 215 | 216 | //the following do not re-join exactly: 217 | Assert.AreEqual("a b c", ArgumentList.Join(ArgumentList.Parse("a \"b\" c")), "failed to parse/join correctly"); 218 | Assert.AreEqual("a \"b\"\"b\" c", ArgumentList.Join(ArgumentList.Parse("a b\"b c")), "failed to parse/join correctly"); 219 | Assert.AreEqual("a b c", ArgumentList.Join(ArgumentList.Parse("a b \"c")), "failed to parse/join correctly"); 220 | } 221 | } 222 | 223 | [TestFixture] 224 | [Category("TestArgumentList")] 225 | public partial class TestArgumentListNegative 226 | { 227 | [Test] 228 | [ExpectedException(ExpectedException = typeof(ArgumentNullException))] 229 | public void TestCTor() 230 | { 231 | new ArgumentList((string[])null); 232 | } 233 | [Test] 234 | [ExpectedException(ExpectedException = typeof(ArgumentNullException))] 235 | public void TestParseNull() 236 | { 237 | ArgumentList.Parse(null); 238 | } 239 | [Test] 240 | [ExpectedException(ExpectedException = typeof(ArgumentNullException))] 241 | public void TestJoinNull() 242 | { 243 | ArgumentList.Join(null); 244 | } 245 | [Test] 246 | [ExpectedException(ExpectedException = typeof(ArgumentNullException))] 247 | public void TestDefaultComparison() 248 | { 249 | ArgumentList.DefaultComparison = null; 250 | } 251 | 252 | [Test] 253 | [ExpectedException(ExpectedException = typeof(ArgumentNullException))] 254 | public void TestNameDelimeters() 255 | { 256 | ArgumentList.NameDelimeters = null; 257 | } 258 | [Test] 259 | [ExpectedException(ExpectedException = typeof(ArgumentNullException))] 260 | public void TestPrefixChars() 261 | { 262 | ArgumentList.PrefixChars = null; 263 | } 264 | 265 | [Test] 266 | [ExpectedException(ExpectedException = typeof(ArgumentOutOfRangeException))] 267 | public void TestNameDelimeters2() 268 | { 269 | ArgumentList.NameDelimeters = new char[0]; 270 | } 271 | [Test] 272 | [ExpectedException(ExpectedException = typeof(ArgumentOutOfRangeException))] 273 | public void TestPrefixChars2() 274 | { 275 | ArgumentList.PrefixChars = new char[0]; 276 | } 277 | 278 | [Test] 279 | [ExpectedException(ExpectedException = typeof(ArgumentNullException))] 280 | public void TestAddRange() 281 | { 282 | new ArgumentList().AddRange(null); 283 | } 284 | [Test] 285 | [ExpectedException(ExpectedException = typeof(ArgumentNullException))] 286 | public void TestAddRange2() 287 | { 288 | new ArgumentList().AddRange(new string[] { "1", null, "2" }); 289 | } 290 | [Test] 291 | [ExpectedException(ExpectedException = typeof(ArgumentNullException))] 292 | public void TestAdd() 293 | { 294 | new ArgumentList().Add(null, null); 295 | } 296 | [Test] 297 | [ExpectedException(ExpectedException = typeof(ArgumentNullException))] 298 | public void TestTryGetValue() 299 | { 300 | ArgumentList.Item item; 301 | new ArgumentList().TryGetValue(null, out item); 302 | } 303 | [Test] 304 | [ExpectedException(ExpectedException = typeof(ArgumentNullException))] 305 | public void TestTryGetValue2() 306 | { 307 | string item; 308 | new ArgumentList().TryGetValue(null, out item); 309 | } 310 | [Test] 311 | [ExpectedException(ExpectedException = typeof(ArgumentNullException))] 312 | public void TestValueAssignment() 313 | { 314 | ArgumentList.Item item = null; 315 | KeyValuePair kv = item; 316 | } 317 | [Test] 318 | [ExpectedException(ExpectedException = typeof(ArgumentNullException))] 319 | public void TestItemNameNull() 320 | { 321 | ArgumentList.Item item = new ArgumentList.Item(null, null); 322 | } 323 | } 324 | } 325 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.CommandsTest/TestCmdInterpreter.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 2 | /* Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | #endregion 15 | 16 | using System; 17 | using System.Collections.Generic; 18 | using NUnit.Framework; 19 | using CSharpTest.Net.Commands; 20 | using System.IO; 21 | using System.ComponentModel; 22 | using System.Runtime.Serialization.Formatters.Binary; 23 | using System.Diagnostics; 24 | 25 | #pragma warning disable 1591 26 | 27 | namespace CSharpTest.Net.CommandsTest 28 | { 29 | [TestFixture] 30 | public partial class TestCmdInterpreter 31 | { 32 | delegate void Action(); 33 | #region TestFixture SetUp/TearDown 34 | 35 | [SetUp] 36 | public void BeforeTest() 37 | { 38 | Environment.ExitCode = 0; 39 | } 40 | 41 | [TearDown] 42 | public void AfterTest() 43 | { 44 | Environment.ExitCode = 0; 45 | } 46 | 47 | [TestFixtureSetUp] 48 | public virtual void Setup() 49 | { 50 | } 51 | 52 | [TestFixtureTearDown] 53 | public virtual void Teardown() 54 | { 55 | Environment.ExitCode = 0; 56 | } 57 | #endregion 58 | 59 | [DebuggerNonUserCode] 60 | private static int WindowHeight 61 | { 62 | get 63 | { 64 | int windowHeight; 65 | try 66 | { 67 | windowHeight = Console.WindowHeight; 68 | } 69 | catch (System.IO.IOException) 70 | { 71 | windowHeight = 25; 72 | } 73 | return windowHeight; 74 | } 75 | } 76 | 77 | /// Used to provide a set of test commands 78 | class TestCommands 79 | { 80 | string _data; 81 | [Option("SomeData", "SD", DefaultValue = "", Description = "Stores some data.")] 82 | public string SomeData 83 | { 84 | get { return _data; } 85 | set { _data = value; } 86 | } 87 | 88 | [Option("Other"), IgnoreMember] 89 | public string ThisIsIgnored { get { throw new NotImplementedException(); } set { } } 90 | 91 | [Command("Hidden"), IgnoreMember] 92 | public void ThisIsAlsoIgnored() { } 93 | 94 | int _otherdata = 0; 95 | [Option("Other")] 96 | [AliasName("alias")] 97 | //OptionAttribute takes precedence, but the following also works. 98 | [System.ComponentModel.DisplayName("ingored-due-to-OptionAttribute")] 99 | [System.ComponentModel.Description("description")] 100 | [System.ComponentModel.Category("category")] 101 | [System.ComponentModel.Browsable(false)] 102 | [System.ComponentModel.DefaultValue(-1)] 103 | public int OtherData 104 | { 105 | get { return _otherdata; } 106 | set { _otherdata = value; } 107 | } 108 | 109 | public string ReadOnlyDoesntAppear { get { return _data; } } 110 | public string WriteOnlyDoesntAppear { set { _data = value; } } 111 | 112 | [Command("Hidden", Visible = false)] 113 | [AliasName("myhiddencommand")] 114 | [AliasName("")] // <= ignored if null or empty 115 | public void Hidden(ICommandInterpreter ci, [AllArguments] string[] args) 116 | { 117 | Console.WriteLine("Hidden Runs: {0}", String.Join(" ", args)); 118 | ci.Run(args); 119 | } 120 | 121 | [Command( 122 | DisplayName="Count", 123 | AliasNames = new string[0], 124 | Category="", 125 | Description = "Count Description.", 126 | Visible = true 127 | )] 128 | public void Count( 129 | [Argument("number", "n", Description = "The number to count to or from.")] 130 | int number, 131 | [Argument("backwards", DefaultValue=false, Description="Count backwards")] 132 | bool backwards, 133 | // Arguments that are of type string[] can be specified more than once on a command-line 134 | // for example /t:x /t:y /t:z will result in the array string[3] { "x", "y", "z" } 135 | [Argument("text", "t", DefaultValue = new string[0], Description = "A piece of text to append")] 136 | string[] text, 137 | // any method can recieve an ICommandInterpreter, once encountered in args all remaining 138 | // must be qualified with /name= format. 139 | ICommandInterpreter ci, 140 | // any method can also take the complete argument list, however, it should always appear 141 | // after all other arguments since. It must always be decorated with [AllArugments]. Any 142 | // command with this parameter will not complain about unknown arguments. 143 | [AllArguments] string[] allargs 144 | ) 145 | { 146 | int st, end, offset; 147 | if (!backwards) 148 | { st = 1; end = number; offset = 1; } 149 | else 150 | { st = number; end = 1; offset = -1; } 151 | 152 | for (int i = st; true; i += offset) 153 | { 154 | if( text.Length == 0 ) 155 | Console.WriteLine("{0}", i); 156 | else 157 | Console.WriteLine("{0} {1}", i, text[(i-1) % text.Length]); 158 | 159 | if (i == end) 160 | break; 161 | } 162 | } 163 | 164 | public void BlowUp([DefaultValue(false)] bool apperror) 165 | { 166 | if( apperror ) 167 | throw new ApplicationException("BlowUp"); 168 | throw new Exception("BlowUp"); 169 | } 170 | 171 | //undecorated should work just fine 172 | public void ForXtoYbyZ(int start, int end, int increment) 173 | { 174 | for (int i = start; true; i += increment) 175 | { 176 | Console.WriteLine("{0}", i); 177 | if (i == end) 178 | break; 179 | } 180 | } 181 | 182 | public void NullableDefaultArgs(long lval, int? ival = 0, TraceLevel? enumVal = null, [Argument(Visible = false)]string sval = "SomeText") 183 | { 184 | } 185 | } 186 | 187 | class StaticTestFilter 188 | { 189 | static int lineNo = 0; 190 | // implements a filter than runs for all commands... 191 | [CommandFilter] // <= implied by method signature, exact signature required for all filters 192 | public static void AddLineNumbers(ICommandInterpreter ci, ICommandChain chain, string[] args) 193 | { 194 | string line; 195 | 196 | if (chain == null) 197 | { 198 | // not possible UNLESS you add this filter to the list of commands which is not recommended 199 | // since it would generally be easier to just add another method to handle this if/else branch 200 | // for you. However, since it is technically possible to do so this will be tested. 201 | Console.WriteLine("{0}", lineNo); 202 | } 203 | else 204 | { 205 | bool addLineNumbers = ArgumentList.Remove(ref args, "linenumbers", out line); 206 | 207 | TextWriter stdout = Console.Out; 208 | StringWriter swout = new StringWriter(); 209 | 210 | if( addLineNumbers ) 211 | Console.SetOut(swout); 212 | 213 | chain.Next(args); // <= Unless we want to prevent this command from executing, we must call next() 214 | 215 | if(addLineNumbers) 216 | { 217 | StringReader r = new StringReader(swout.ToString()); 218 | 219 | while (null != (line = r.ReadLine())) 220 | stdout.WriteLine("{0}: {1}", ++lineNo, line); 221 | } 222 | } 223 | } 224 | } 225 | 226 | private static string Capture(CommandInterpreter ci, string input) 227 | { 228 | TextWriter stdout = Console.Out, stderr = Console.Error; 229 | TextReader stdin = Console.In; 230 | try 231 | { 232 | StringWriter sw = new StringWriter(); 233 | Console.SetOut(sw); 234 | StringWriter swe = new StringWriter(); 235 | Console.SetError(swe); 236 | Console.SetIn(new StringReader(input)); 237 | 238 | ci.Prompt = String.Empty; 239 | ci.Run(Console.In); 240 | sw.WriteLine(swe.ToString()); 241 | return sw.ToString().Trim(); 242 | } 243 | finally 244 | { 245 | Console.SetOut(stdout); 246 | Console.SetError(stderr); 247 | Console.SetIn(stdin); 248 | } 249 | } 250 | 251 | [Test] 252 | public void TestAddCommands() 253 | { 254 | CommandInterpreter ci = new CommandInterpreter(DefaultCommands.None, new TestCommands()); 255 | Assert.AreEqual(2, ci.Options.Length); 256 | Assert.AreEqual("Other", ci.Options[0].DisplayName); 257 | Assert.AreEqual("SomeData", ci.Options[1].DisplayName); 258 | 259 | Assert.AreEqual(5, ci.Commands.Length); 260 | Assert.AreEqual("BlowUp", ci.Commands[0].DisplayName); 261 | Assert.AreEqual("Count", ci.Commands[1].DisplayName); // <= alpha-sorted 262 | Assert.AreEqual("ForXtoYbyZ", ci.Commands[2].DisplayName); 263 | Assert.AreEqual("Hidden", ci.Commands[3].DisplayName); 264 | 265 | foreach (ICommand c in ci.Commands) 266 | ci.RemoveCommand(c); 267 | Assert.AreEqual(0, ci.Commands.Length); 268 | 269 | ci = new CommandInterpreter(DefaultCommands.None); 270 | Assert.AreEqual(0, ci.Options.Length); 271 | Assert.AreEqual(0, ci.Commands.Length); 272 | 273 | ci.AddHandler(typeof(StaticTestFilter)); 274 | Assert.AreEqual(0, ci.Options.Length); // the type StaticTestFilter contains filters and no commands/options 275 | Assert.AreEqual(0, ci.Commands.Length); 276 | 277 | ci.AddHandler(new TestCommands()); 278 | Assert.AreEqual(2, ci.Options.Length); 279 | Assert.AreEqual(5, ci.Commands.Length); 280 | } 281 | 282 | [Test] 283 | public void TestHtmlHelp() 284 | { 285 | CommandInterpreter ci = new CommandInterpreter(DefaultCommands.Help, new TestCommands()); 286 | string helptext = ci.GetHtmlHelp(null); 287 | Assert.IsTrue(0 == helptext.IndexOf("")); 288 | Assert.IsTrue(helptext.Contains("COMMAND")); 289 | Assert.IsTrue(helptext.IndexOf("SOMEDATA",StringComparison.OrdinalIgnoreCase) >= 0); 290 | } 291 | 292 | [Test] 293 | public void TestHelpText() 294 | { 295 | CommandInterpreter ci = new CommandInterpreter(DefaultCommands.None, new TestCommands()); 296 | string helptext = Capture(ci, "Help"); 297 | Assert.AreNotEqual(0, Environment.ExitCode); 298 | Assert.IsTrue(helptext.Contains("Invalid")); 299 | 300 | Environment.ExitCode = 0; 301 | ci = new CommandInterpreter(DefaultCommands.Help, new TestCommands()); 302 | helptext = Capture(ci, "Help"); 303 | Assert.AreEqual(0, Environment.ExitCode); 304 | Assert.IsFalse(helptext.Contains("Invalid")); 305 | Assert.IsFalse(helptext.Contains("HIDDEN")); // <= not listed 306 | Assert.IsTrue(helptext.Contains("COUNT")); 307 | Assert.IsTrue(helptext.Contains("SOMEDATA")); 308 | Assert.IsTrue(helptext.Contains("FORXTOYBYZ")); 309 | 310 | //empty command-string displays help, the EXIT/QUIT are always available when running 311 | //interactive mode via .Run(TextReader), which is what Capture(...) does. 312 | Assert.AreEqual(helptext, Capture(ci, Environment.NewLine + "EXIT")); 313 | 314 | helptext = Capture(ci, "Help hidden"); // <= still has detailed help 315 | Assert.AreEqual(0, Environment.ExitCode); 316 | Assert.IsTrue(helptext.Contains("HIDDEN")); 317 | Assert.IsTrue(helptext.Contains("MYHIDDENCOMMAND")); // <= alias names display for details 318 | Assert.IsFalse(helptext.Contains("COUNT")); 319 | Assert.IsFalse(helptext.Contains("SOMEDATA")); 320 | Assert.IsFalse(helptext.Contains("FORXTOYBYZ")); 321 | 322 | helptext = Capture(ci, "Help Help"); 323 | Assert.AreEqual(0, Environment.ExitCode); 324 | Assert.IsTrue(helptext.Contains("HELP")); 325 | Assert.IsTrue(helptext.Contains("[/name=]String")); 326 | 327 | helptext = Capture(ci, "Help SOMEDATA"); 328 | Assert.AreEqual(0, Environment.ExitCode); 329 | Assert.IsTrue(helptext.Contains("SOMEDATA")); 330 | Assert.IsTrue(helptext.Contains("SD")); 331 | } 332 | [Test] 333 | public void TestSetPersistOption() 334 | { 335 | Assert.AreEqual(DefaultCommands.Get | DefaultCommands.Set | DefaultCommands.Help, DefaultCommands.Default); 336 | TestCommands cmds = new TestCommands(); 337 | CommandInterpreter ci = new CommandInterpreter(cmds); 338 | cmds.OtherData = 42; 339 | Assert.AreEqual("42", Capture(ci, "GET Other")); 340 | cmds.SomeData = "one-two-three"; 341 | Assert.AreEqual("one-two-three", Capture(ci, "GET SomeData")); 342 | 343 | string options = Capture(ci, "SET"); 344 | cmds.OtherData = 0; 345 | cmds.SomeData = String.Empty; 346 | Assert.AreEqual("0", Capture(ci, "GET Other")); 347 | Assert.AreEqual(String.Empty, Capture(ci, "GET SomeData")); 348 | 349 | TextReader input = Console.In; 350 | try 351 | { 352 | Console.SetIn(new StringReader(options));//feed the output of SET back to SET 353 | ci.Run("SET", "/readInput"); 354 | } 355 | finally { Console.SetIn(input); } 356 | 357 | //should now be restored 358 | Assert.AreEqual("42", Capture(ci, "GET Other")); 359 | Assert.AreEqual("one-two-three", Capture(ci, "GET SomeData")); 360 | } 361 | 362 | [Test] 363 | public void TestGetSetOption() 364 | { 365 | Assert.AreEqual(DefaultCommands.Get | DefaultCommands.Set | DefaultCommands.Help, DefaultCommands.Default); 366 | //defaults the DefaultCommands to Get/Set/Help via the enum value of DefaultCommands.Default 367 | CommandInterpreter ci = new CommandInterpreter(new TestCommands()); 368 | 369 | Assert.AreEqual(2, ci.Options.Length); 370 | Assert.AreEqual("Other", ci.Options[0].DisplayName); 371 | Assert.AreEqual(typeof(int), ci.Options[0].Type); 372 | Assert.AreEqual("SomeData", ci.Options[1].DisplayName); 373 | Assert.AreEqual(typeof(String), ci.Options[1].Type); 374 | 375 | TextWriter stdout = Console.Out; 376 | try 377 | { 378 | Console.SetOut(new StringWriter());// <= Get will also write to console 379 | 380 | Assert.AreEqual(-1, ci.Get("other"));//default was applied 381 | ci.Set("other", 1); 382 | Assert.AreEqual(1, ci.Get("other")); 383 | 384 | Assert.AreNotEqual("abc", ci.Get("somedata")); 385 | ci.Set("somedata", "abc"); 386 | Assert.AreEqual("abc", ci.Get("somedata")); 387 | } 388 | finally 389 | { Console.SetOut(stdout); } 390 | 391 | string result; 392 | result = Capture(ci, "GET Somedata"); 393 | Assert.AreEqual("abc", result); 394 | //Set without args lists options 395 | result = Capture(ci, "SET"); 396 | Assert.IsTrue(result.ToUpper().Contains("somedata".ToUpper())); 397 | //Set without value returns the current value 398 | result = Capture(ci, "SET somedata"); 399 | Assert.AreEqual("abc", result); 400 | result = Capture(ci, "SET somedata 123"); 401 | Assert.AreEqual("", result); 402 | result = Capture(ci, "GET somedata"); 403 | Assert.AreEqual("123", result); 404 | } 405 | 406 | [Test] 407 | public void TestCommandRun() 408 | { 409 | string result; 410 | CommandInterpreter ci = new CommandInterpreter(DefaultCommands.Get | DefaultCommands.Set, new TestCommands()); 411 | 412 | result = Capture(ci, "Count 2"); 413 | Assert.AreEqual("1\r\n2", result); 414 | 415 | result = Capture(ci, "Count /backwards 2"); 416 | Assert.AreEqual("2\r\n1", result); 417 | result = Capture(ci, "Count 2 /backwards"); 418 | Assert.AreEqual("2\r\n1", result); 419 | result = Capture(ci, "Count -n:2 /backwards:true"); 420 | Assert.AreEqual("2\r\n1", result); 421 | 422 | result = Capture(ci, "Count 2 /t:a /t:b"); 423 | Assert.AreEqual("1 a\r\n2 b", result); 424 | 425 | //Argument not found: 426 | result = Capture(ci, "Count"); 427 | Assert.AreEqual("The value for number is required.", result); 428 | 429 | //#warning Broken? 430 | //Non-ApplicationExcpetion dumps stack: 431 | ci.ErrorLevel = 0; 432 | result = Capture(ci, "BlowUp false"); 433 | Assert.AreNotEqual(0, ci.ErrorLevel); 434 | Assert.IsTrue(result.Contains("System.Exception: BlowUp"), "Expected \"System.Exception: BlowUp\" in {0}", result); 435 | 436 | //ApplicationExcpetion dumps message only: 437 | ci.ErrorLevel = 0; 438 | result = Capture(ci, "BlowUp true"); 439 | Assert.AreNotEqual(0, ci.ErrorLevel); 440 | Assert.AreEqual("BlowUp", result); 441 | } 442 | 443 | [Test] 444 | public void TestMacroExpand() 445 | { 446 | string result; 447 | CommandInterpreter ci = new CommandInterpreter(DefaultCommands.Echo | DefaultCommands.Prompt, new TestCommands()); 448 | 449 | ci.Set("SomeData", "TEST_Data"); 450 | result = Capture(ci, "ECHO $(SOMEDATA)"); 451 | Assert.AreEqual("TEST_Data", result); 452 | 453 | ci.Set("SomeData", "TEST Data"); 454 | result = Capture(ci, "ECHO $(SOMEDATA)"); 455 | Assert.AreEqual("\"TEST Data\"", result); // <= Echo will quote & escape while-space and quotes " 456 | 457 | result = Capture(ci, "ECHO $(MissingProperty)"); 458 | Assert.AreEqual("Unknown option specified: MissingProperty", result); 459 | 460 | result = Capture(ci, "ECHO $$(MissingProperty) $$(xx x$$y $$ abc"); // <= escape '$' with '$$' 461 | Assert.AreEqual("$(MissingProperty) $(xx x$y $ abc", result); // <= extra '$' was removed. 462 | } 463 | 464 | class ErrorReader : TextReader 465 | { 466 | public override string ReadLine() 467 | { throw new NotImplementedException(); } 468 | } 469 | 470 | [Test] 471 | public void TestLoopErrors() 472 | { 473 | TextWriter stdout = Console.Out, stderr = Console.Error; 474 | try 475 | { 476 | StringWriter sw = new StringWriter(); 477 | Console.SetError(sw); 478 | Console.SetOut(sw); 479 | 480 | string result; 481 | CommandInterpreter ci = new CommandInterpreter(new TestCommands()); 482 | 483 | ci.Prompt = "$(MissingProperty)"; 484 | ci.Run(new StringReader("EXIT")); 485 | result = sw.ToString(); 486 | Assert.IsTrue(result.StartsWith("Unknown option specified: MissingProperty")); 487 | ci.Prompt = String.Empty; 488 | 489 | sw.GetStringBuilder().Length = 0;//clear 490 | ci.Run(new ErrorReader()); 491 | 492 | result = sw.ToString(); 493 | Assert.IsTrue(result.StartsWith(typeof(NotImplementedException).FullName)); 494 | } 495 | finally 496 | { 497 | Console.SetOut(stdout); 498 | Console.SetError(stderr); 499 | } 500 | } 501 | 502 | [Test] 503 | public void TestCommandFilters() 504 | { 505 | string result; 506 | CommandInterpreter ci = new CommandInterpreter(DefaultCommands.None, new TestCommands(), typeof(StaticTestFilter)); 507 | Assert.AreEqual(1, ci.Filters.Length); 508 | Assert.AreEqual("AddLineNumbers", ci.Filters[0].DisplayName); 509 | 510 | result = Capture(ci, "Count 2 /linenumbers"); 511 | Assert.AreEqual("1: 1\r\n2: 2", result); 512 | 513 | int cmds = ci.Commands.Length; 514 | ci.AddCommand(ci.Filters[0]); 515 | Assert.AreEqual(cmds + 1, ci.Commands.Length); 516 | 517 | result = Capture(ci, "AddLineNumbers"); 518 | Assert.AreEqual("2", result); 519 | } 520 | 521 | private Char GetSpace() { return ' '; } 522 | 523 | [Test] 524 | public void TestBuiltInMore() 525 | { 526 | string result; 527 | CommandInterpreter ci = new CommandInterpreter( 528 | DefaultCommands.More | DefaultCommands.PipeCommands, 529 | new TestCommands()); 530 | 531 | //replace the keystroke wait 532 | ci.ReadNextCharacter = GetSpace; 533 | 534 | string input = String.Format("Count {0} | MORE", (int)(WindowHeight * 1.5)); 535 | 536 | result = Capture(ci, input); 537 | 538 | StringReader sr = new StringReader(result); 539 | int moreFound = 0; 540 | int index = 0; 541 | string line; 542 | while( null != (line = sr.ReadLine())) 543 | { 544 | if( line == "-- More --" ) 545 | moreFound++; 546 | else 547 | Assert.AreEqual((++index).ToString(), line); 548 | } 549 | 550 | Assert.AreEqual(1, moreFound); 551 | } 552 | 553 | [Test] 554 | public void TestBuiltInFind() 555 | { 556 | string result; 557 | CommandInterpreter ci = new CommandInterpreter( 558 | DefaultCommands.Echo | DefaultCommands.Find | DefaultCommands.PipeCommands, 559 | new TestCommands()); 560 | 561 | result = Capture(ci, "Count 220 |FIND \"1\" |FIND \"0\" | FIND /V \"3\" | FIND /V \"4\" | FIND /V \"5\" | FIND /V \"6\" | FIND /V \"7\" | FIND /V \"8\" | FIND /V \"9\""); 562 | Assert.AreEqual("10\r\n100\r\n101\r\n102\r\n110\r\n120\r\n201\r\n210", result); 563 | 564 | result = Capture(ci, "ECHO ABC | FIND \"abc\" |"); 565 | Assert.AreEqual(String.Empty, result); 566 | 567 | result = Capture(ci, "ECHO ABC | FIND /I \"abc\" |"); 568 | Assert.AreEqual("ABC", result); 569 | } 570 | 571 | [Test] 572 | public void TestBuiltInRedirect() 573 | { 574 | string tempPath = Path.GetTempFileName(); 575 | string tempPath2 = Path.GetTempFileName(); 576 | try 577 | { 578 | string result; 579 | CommandInterpreter ci = new CommandInterpreter( 580 | DefaultCommands.Find | DefaultCommands.PipeCommands | DefaultCommands.IORedirect, 581 | new TestCommands()); 582 | 583 | //Redirect output: 584 | result = Capture(ci, "Count 100 > " + tempPath); 585 | Assert.AreEqual(String.Empty, result); 586 | Assert.AreEqual(100, File.ReadAllLines(tempPath).Length); 587 | 588 | result = Capture(ci, "Find \"1\" -f:" + tempPath + " |Find \"0\" > " + tempPath2); 589 | Assert.AreEqual(String.Empty, result); 590 | Assert.AreEqual("10\r\n100", File.ReadAllText(tempPath2).Trim()); 591 | 592 | //Redirect input: 593 | result = Capture(ci, "Find \"1\" |Find \"0\" <" + tempPath + " >" + tempPath2); 594 | Assert.AreEqual(String.Empty, result); 595 | Assert.AreEqual("10\r\n100", File.ReadAllText(tempPath2).Trim()); 596 | 597 | //Change precedence and watch it fail: 598 | Assert.IsTrue(ci.FilterPrecedence.StartsWith("<") || ci.FilterPrecedence.StartsWith(">")); 599 | ci.FilterPrecedence = ci.FilterPrecedence.TrimStart('<', '>'); 600 | 601 | result = Capture(ci, "Find \"1\" |Find \"0\" <" + tempPath + " >" + tempPath2); 602 | Assert.AreEqual(String.Empty, result); 603 | result = File.ReadAllText(tempPath2).Trim(); 604 | Assert.AreEqual("10\r\n20\r\n30\r\n40\r\n50\r\n60\r\n70\r\n80\r\n90\r\n100", result); 605 | } 606 | finally 607 | { 608 | File.Delete(tempPath); 609 | File.Delete(tempPath2); 610 | } 611 | } 612 | 613 | [Test] 614 | public void TestAttributes() 615 | { 616 | CommandInterpreter ci = new CommandInterpreter(new TestCommands()); 617 | 618 | IOption option = ci.Options[0]; 619 | 620 | //[Option("Other")] 621 | Assert.AreEqual("Other", option.DisplayName); 622 | Assert.AreEqual(typeof(int), option.Type); 623 | //[AliasName("alias")] 624 | //[System.ComponentModel.DisplayName("ingored-due-to-OptionAttribute")] 625 | Assert.AreEqual(2, option.AllNames.Length); 626 | Assert.IsTrue(new List(option.AllNames).Contains("Other")); 627 | Assert.IsTrue(new List(option.AllNames).Contains("alias")); 628 | //[System.ComponentModel.Description("description")] 629 | Assert.AreEqual("description", option.Description); 630 | //[System.ComponentModel.Category("category")] 631 | Assert.AreEqual("category", option.Category); 632 | //[System.ComponentModel.Browsable(false)] 633 | Assert.AreEqual(false, option.Visible); 634 | //[System.ComponentModel.DefaultValue(-1)] 635 | Assert.AreEqual(-1, option.Value); 636 | 637 | { 638 | CommandFilterAttribute a = new CommandFilterAttribute(); 639 | Assert.IsFalse(a.Visible); 640 | a.Visible = true; 641 | Assert.IsFalse(a.Visible); 642 | } 643 | { 644 | CommandAttribute a = new CommandAttribute(); 645 | a.DisplayName = "test"; 646 | a.AliasNames = new string[] { "alias" }; 647 | Assert.AreEqual("test,alias", String.Join(",", a.AllNames)); 648 | IDisplayInfo di = a; 649 | di.Help();//no-op 650 | } 651 | { 652 | AllArgumentsAttribute a = new AllArgumentsAttribute(); 653 | Assert.AreEqual(typeof(AllArgumentsAttribute), a.GetType()); 654 | } 655 | } 656 | 657 | [Test] 658 | public void TestNullableDefaultArgs() 659 | { 660 | Environment.ExitCode = 0; 661 | CommandInterpreter ci = new CommandInterpreter(new TestCommands()); 662 | 663 | ICommand cmd; 664 | Assert.IsTrue(ci.TryGetCommand("NullableDefaultArgs", out cmd)); 665 | 666 | var args = cmd.Arguments; 667 | Assert.AreEqual(4, args.Length); 668 | 669 | Assert.AreEqual("lval", args[0].DisplayName); 670 | Assert.AreEqual(true, args[0].Required); 671 | 672 | Assert.AreEqual("ival", args[1].DisplayName); 673 | Assert.AreEqual(false, args[1].Required); 674 | Assert.AreEqual(0, args[1].DefaultValue); 675 | 676 | Assert.AreEqual("enumVal", args[2].DisplayName); 677 | Assert.AreEqual(false, args[2].Required); 678 | Assert.AreEqual(null, args[2].DefaultValue); 679 | 680 | Assert.AreEqual("sval", args[3].DisplayName); 681 | Assert.AreEqual(false, args[3].Required); 682 | Assert.AreEqual(false, args[3].Visible); 683 | Assert.AreEqual("SomeText", args[3].DefaultValue); 684 | 685 | ci.Run("NullableDefaultArgs", "1"); 686 | Assert.AreEqual(0, ci.ErrorLevel); 687 | } 688 | 689 | [Test] 690 | public void EnsureSerializationOfException() 691 | { 692 | InterpreterException ex = null; 693 | try { throw new InterpreterException("TEST"); } 694 | catch (InterpreterException e) { ex = e; } 695 | 696 | Assert.IsNotNull(ex); 697 | BinaryFormatter bf = new BinaryFormatter(); 698 | 699 | using( MemoryStream ms = new MemoryStream() ) 700 | { 701 | bf.Serialize(ms, ex); 702 | 703 | ms.Position = 0; 704 | ex = (InterpreterException)bf.Deserialize(ms); 705 | Assert.AreEqual("TEST", ex.Message); 706 | } 707 | } 708 | 709 | [Test][ExpectedException(typeof(InvalidOperationException))] 710 | public void FailConsoleIO() 711 | { 712 | CommandInterpreter ci = new CommandInterpreter(); 713 | ci.ReadNextCharacter(); 714 | } 715 | } 716 | } 717 | -------------------------------------------------------------------------------- /src/CSharpTest.Net.CommandsTest/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Example/Commands.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using CSharpTest.Net.Commands; 5 | 6 | /// 7 | /// Add the commands and options to this class, you can move the namespace as needed with 8 | /// most refactoring tools. The class is referenced from Main.cs 9 | /// 10 | partial class Commands 11 | { 12 | [Option("name", DefaultValue = "Foo", Description = "Set or get my name.")] 13 | public string MyName { get; set; } 14 | 15 | [Command("hello", Description = "Say hello world.")] 16 | public void Hello(string from = null, int? repeated = null) 17 | { 18 | for (int i = 0; i < repeated.GetValueOrDefault(1); i++) 19 | Console.WriteLine("Hello world, from {0}", from ?? MyName); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Example/Example.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {E6931B7B-8A57-416E-BFDD-FFAEC7BAC5AE} 8 | Exe 9 | Properties 10 | Example 11 | Example 12 | v2.0 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | {7bd5edd1-445c-46d1-a0b2-4b68cb51eadb} 47 | CSharpTest.Net.Commands 48 | 49 | 50 | 51 | 58 | -------------------------------------------------------------------------------- /src/Example/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reflection; 4 | using CSharpTest.Net.Commands; 5 | 6 | /// 7 | /// The actual program class can be simplified, but the following demostrates most of the capability 8 | /// that can be used in the CommandInterpreter as well as default implementations for some standard 9 | /// options: /nologo, /verbose, and /wait 10 | /// 11 | class Program 12 | { 13 | [STAThread] 14 | static int Main(string[] args) 15 | { 16 | int result = -1; 17 | 18 | string temp; 19 | var wait = ArgumentList.Remove(ref args, "wait", out temp); 20 | var nologo = ArgumentList.Remove(ref args, "nologo", out temp); 21 | 22 | try 23 | { 24 | // Display logo/header information 25 | var entryAssembly = Assembly.GetEntryAssembly(); 26 | if (entryAssembly != null && nologo == false) 27 | { 28 | Console.WriteLine("{0}", entryAssembly.GetName()); 29 | foreach (AssemblyCopyrightAttribute a in entryAssembly.GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false)) 30 | Console.WriteLine("{0}", a.Copyright); 31 | Console.WriteLine(); 32 | } 33 | 34 | // If verbose output was specified, attach a trace listener 35 | if (ArgumentList.Remove(ref args, "verbose", out temp) || ArgumentList.Remove(ref args, "verbosity", out temp)) 36 | { 37 | SourceLevels traceLevel; 38 | try { traceLevel = (SourceLevels)Enum.Parse(typeof(SourceLevels), temp); } 39 | catch { traceLevel = SourceLevels.All; } 40 | 41 | Trace.Listeners.Add(new ConsoleTraceListener() 42 | { 43 | Filter = new EventTypeFilter(traceLevel), 44 | IndentLevel = 0, 45 | TraceOutputOptions = TraceOptions.None 46 | }); 47 | } 48 | 49 | // Construct the CommandInterpreter and initialize 50 | ICommandInterpreter ci = new CommandInterpreter( 51 | DefaultCommands.Help | 52 | // Allows a simple ECHO command to output text to std::out 53 | DefaultCommands.Echo | 54 | // Allows getting/setting properties, like prompt and errorlevel below 55 | DefaultCommands.Get | DefaultCommands.Set | 56 | DefaultCommands.Prompt | 57 | DefaultCommands.ErrorLevel | 58 | // uncomment to allow cmd > C:\output.txt or cmd < C:\input.txt 59 | //DefaultCommands.IORedirect | 60 | // uncomment to use aaa | bbb | FIND "abc" | MORE 61 | //DefaultCommands.PipeCommands | DefaultCommands.Find | DefaultCommands.More | 62 | 0, 63 | // Add the types to use static members 64 | typeof(Filters), 65 | // Add classes to use instance members 66 | new Commands() 67 | ); 68 | 69 | // If you want to hide some options/commands at runtime you can: 70 | foreach (var name in new[] {"prompt", "errorlevel"}) 71 | { 72 | IOption opt; 73 | if (ci.TryGetOption(name, out opt)) 74 | opt.Visible = false; 75 | } 76 | foreach (var name in new[] {"echo", "set", "get"}) 77 | { 78 | ICommand opt; 79 | if (ci.TryGetCommand(name, out opt)) 80 | opt.Visible = false; 81 | } 82 | 83 | // Apply all DefaultValue values to properties 84 | ci.SetDefaults(); 85 | 86 | // If we have arguments, just run with those arguments... 87 | if (args.Length > 0) 88 | ci.Run(args); 89 | else 90 | { 91 | //When run without arguments you can either display help: 92 | //ci.Help(null); 93 | //... or run the interpreter: 94 | ci.Run(Console.In); 95 | } 96 | 97 | result = ci.ErrorLevel; 98 | } 99 | catch (OperationCanceledException) 100 | { result = 3; } 101 | catch (ApplicationException ex) 102 | { 103 | Trace.TraceError("{0}", ex); 104 | Console.Error.WriteLine(ex.Message); 105 | result = 1; 106 | } 107 | catch (Exception ex) 108 | { 109 | Trace.TraceError("{0}", ex); 110 | Console.Error.WriteLine(ex.ToString()); 111 | result = 2; 112 | } 113 | finally 114 | { 115 | if (wait) 116 | { 117 | Console.WriteLine("Exited with result = {0}, Press Enter to quit.", result); 118 | Console.ReadLine(); 119 | } 120 | } 121 | 122 | return Environment.ExitCode = result; 123 | } 124 | 125 | /// 126 | /// Internally defined static command filters provide pre-post processing... 127 | /// 128 | private static class Filters 129 | { 130 | /// Augment the default help description 131 | [CommandFilter('?', Visible = false)] 132 | public static void HelpFilter(ICommandInterpreter ci, ICommandChain chain, string[] args) 133 | { 134 | if (args.Length == 1 && StringComparer.OrdinalIgnoreCase.Equals("help", args[0]) || args[0] == "?") 135 | { 136 | chain.Next(args); 137 | Console.WriteLine(@"Global Options: 138 | /nologo: Suppress the logo/copyright message 139 | /verbosity: [All] Verbosity level: Off, Error, Warning, Information, Verbose, or All 140 | "); 141 | } 142 | else 143 | { 144 | chain.Next(args); 145 | } 146 | } 147 | 148 | /// Ensure exceptions are captured in the trace output 149 | [CommandFilter('\x0000', Visible = false)] 150 | public static void ExceptionFilter(ICommandInterpreter ci, ICommandChain chain, string[] args) 151 | { 152 | try { chain.Next(args); } 153 | catch (System.Threading.ThreadAbortException) { throw; } 154 | catch (CommandInterpreter.QuitException) { throw; } 155 | catch (OperationCanceledException) { throw; } 156 | catch (InterpreterException) 157 | { 158 | // Incorrect useage or bad command name... 159 | throw; 160 | } 161 | catch (Exception ex) 162 | { 163 | if (args.Length > 0) 164 | Trace.TraceError("[{0}]: {1}", args[0], ex); 165 | else 166 | Trace.TraceError("{0}", ex); 167 | throw; 168 | } 169 | } 170 | } 171 | } -------------------------------------------------------------------------------- /src/Example/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | #region Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0 2 | /* Licensed under the Apache License, Version 2.0 (the "License"); 3 | * you may not use this file except in compliance with the License. 4 | * You may obtain a copy of the License at 5 | * 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software 9 | * distributed under the License is distributed on an "AS IS" BASIS, 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 | * See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | #endregion 15 | using System.Reflection; 16 | using System.Runtime.InteropServices; 17 | 18 | [assembly: AssemblyTitle("Example.exe")] 19 | [assembly: AssemblyDescription("Example usages.")] 20 | [assembly: AssemblyProduct("http://CSharpTest.Net/Projects")] 21 | [assembly: AssemblyConfiguration("Debug")] 22 | 23 | [assembly: AssemblyCompany("Roger Knapp")] 24 | [assembly: AssemblyCopyright("Copyright 2009-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0")] 25 | 26 | [assembly: AssemblyVersion("1.0.0.0")] 27 | [assembly: AssemblyFileVersion("1.0.0.0")] 28 | 29 | [assembly: ObfuscateAssembly(false)] 30 | [assembly: ComVisibleAttribute(false)] 31 | --------------------------------------------------------------------------------