Paradox Game Engine  v1.0.0 beta06
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros Pages
ConsoleProgram.cs
Go to the documentation of this file.
1 // Copyright (c) 2014 Silicon Studio Corp. (http://siliconstudio.co.jp)
2 // This file is distributed under GPL v3. See LICENSE.md for details.
3 //
4 // Copyright (c) 2010-2013 SharpDX - Alexandre Mutel
5 //
6 // Permission is hereby granted, free of charge, to any person obtaining a copy
7 // of this software and associated documentation files (the "Software"), to deal
8 // in the Software without restriction, including without limitation the rights
9 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 // copies of the Software, and to permit persons to whom the Software is
11 // furnished to do so, subject to the following conditions:
12 //
13 // The above copyright notice and this permission notice shall be included in
14 // all copies or substantial portions of the Software.
15 //
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 // THE SOFTWARE.
23 
24 using System;
25 using System.Collections;
26 using System.Collections.Generic;
27 using System.ComponentModel;
28 using System.Diagnostics;
29 using System.IO;
30 using System.Reflection;
31 using System.Runtime.InteropServices;
32 
33 namespace SiliconStudio.Paradox
34 {
35  /// <summary>
36  /// Reusable, reflection based helper for parsing commandline options.
37  /// Greetings to Shawn Hargreaves, original code http://blogs.msdn.com/b/shawnhar/archive/2012/04/20/a-reusable-reflection-based-command-line-parser.aspx
38  /// This is a modified version of command line parser that adds:
39  /// - .NET 2.0 compatible
40  /// - Allow inheritance to simplify declaration
41  /// - Print exe banner, using AssemblyTitle and AssemblyCopyright.
42  /// - Better padding of options, add descriptor and value text overrides.
43  /// - Add support for - and / starting options.
44  /// - Remove usage of ":" to separate option from parsed option
45  /// - Add "&lt;options&gt;" to the Usage when options are defined
46  /// - Add Console Color handling
47  /// </summary>
48  /// <remarks>
49  /// This single file is intended to be directly included in the project that needs to handle command line without requiring any SharpDX assembly dependencies.
50  /// </remarks>
52  {
53  private const int STD_OUTPUT_HANDLE = -11;
54  private static int hConsoleHandle;
55  private static CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
56  private static int OriginalColors;
57  private Dictionary<string, FieldInfo> optionalOptions = new Dictionary<string, FieldInfo>();
58  private List<string> optionalUsageHelp = new List<string>();
59  private object optionsObject;
60  private string[] optionNames;
61 
62  private Queue<FieldInfo> requiredOptions = new Queue<FieldInfo>();
63 
64  private List<string> requiredUsageHelp = new List<string>();
65 
66  static ConsoleProgram()
67  {
68  ConsoleInfo = new CONSOLE_SCREEN_BUFFER_INFO();
69  hConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
70  GetConsoleScreenBufferInfo(hConsoleHandle, ref ConsoleInfo);
71  OriginalColors = ConsoleInfo.wAttributes;
72  }
73 
74  protected ConsoleProgram(int padOptions = 16) : this(null, padOptions)
75  {
76  }
77 
78  // Constructor.
79  private ConsoleProgram(object optionsObjectArg, int padOptions = 16)
80  {
81  this.optionsObject = optionsObjectArg ?? this;
82 
83  // Reflect to find what commandline options are available.
84  foreach (FieldInfo field in optionsObject.GetType().GetFields())
85  {
86  var option = GetOptionName(field);
87 
88  var optionName = option.Name;
89 
90  if (option.Required)
91  {
92  // Record a required option.
93  requiredOptions.Enqueue(field);
94 
95  requiredUsageHelp.Add(string.Format("{0}", option.Name));
96  }
97  else
98  {
99  // Record an optional option.
100  optionalOptions.Add(optionName, field);
101 
102  if (field.FieldType == typeof (bool))
103  {
104  optionalUsageHelp.Add(string.Format("/{0,-" + padOptions + "}{1}", optionName, option.Description ?? string.Empty));
105  }
106  else
107  {
108  optionalUsageHelp.Add(string.Format("/{0,-" + padOptions + "}{1}", string.Format("{0}{1}", optionName, option.Value ?? "<value>"), option.Description ?? string.Empty));
109  }
110  }
111  }
112 
113  optionNames = new string[optionalOptions.Count];
114  optionalOptions.Keys.CopyTo(optionNames, 0);
115  Array.Sort(optionNames, (left, right) => -string.Compare(left, right, StringComparison.Ordinal));
116 
117  if (optionalOptions.Count > 0)
118  {
119  requiredUsageHelp.Insert(0, "<options>");
120  }
121  }
122 
123  public static void PrintHeader()
124  {
125  Console.WriteLine("{0} - {1}", GetAssemblyTitle(), Assembly.GetEntryAssembly().GetName().Version);
126  Console.WriteLine("{0}", GetAssemblyCopyright());
127  Console.WriteLine();
128  }
129 
130  public static bool ParseCommandLine(object options, string[] args, int padOptions = 16)
131  {
132  PrintHeader();
133 
134  var cmdParser = new ConsoleProgram(options, padOptions);
135  return cmdParser.ParseCommandLine(args);
136  }
137 
138  protected bool ParseCommandLine(string[] args)
139  {
140  // Parse each argument in turn.
141  foreach (string arg in args)
142  {
143  if (!ParseArgument(arg.Trim()))
144  {
145  return false;
146  }
147  }
148 
149  // Make sure we got all the required options.
150  FieldInfo missingRequiredOption = null;
151 
152  foreach (var field in requiredOptions)
153  {
154  if (!IsList(field) || GetList(field).Count == 0)
155  {
156  missingRequiredOption = field;
157  break;
158  }
159  }
160 
161  if (missingRequiredOption != null)
162  {
163  ShowError("Missing argument '{0}'", GetOptionName(missingRequiredOption).Name);
164  return false;
165  }
166 
167  return true;
168  }
169 
170  private bool ParseArgument(string arg)
171  {
172  if (arg.StartsWith("/") || arg.StartsWith("-"))
173  {
174  string name = arg.Substring(1);
175 
176  string value = null;
177 
178  FieldInfo field = null;
179 
180 
181 
182  foreach (var registerName in optionNames)
183  {
184  if (name.StartsWith(registerName, StringComparison.InvariantCultureIgnoreCase))
185  {
186  field = optionalOptions[registerName];
187  value = name.Substring(registerName.Length);
188  break;
189  }
190  }
191 
192  if (field == null)
193  {
194  ShowError("Unknown option '{0}'", name);
195  return false;
196  }
197 
198  if (string.IsNullOrEmpty(value))
199  {
200  value = "true";
201  }
202 
203  return SetOption(field, value);
204  }
205  else
206  {
207  // Parse a required argument.
208  if (requiredOptions.Count == 0)
209  {
210  ShowError("Too many arguments");
211  return false;
212  }
213 
214  FieldInfo field = requiredOptions.Peek();
215 
216  if (!IsList(field))
217  {
218  requiredOptions.Dequeue();
219  }
220 
221  return SetOption(field, arg);
222  }
223  }
224 
225  private bool SetOption(FieldInfo field, string value)
226  {
227  try
228  {
229  if (IsList(field))
230  {
231  // Append this value to a list of options.
232  GetList(field).Add(ChangeType(value, ListElementType(field)));
233  }
234  else
235  {
236  // Set the value of a single option.
237  field.SetValue(optionsObject, ChangeType(value, field.FieldType));
238  }
239 
240  return true;
241  }
242  catch
243  {
244  ShowError("Invalid value '{0}' for option '{1}'", value, GetOptionName(field).Name);
245  return false;
246  }
247  }
248 
249 
250  private static object ChangeType(string value, Type type)
251  {
252  TypeConverter converter = TypeDescriptor.GetConverter(type);
253 
254  return converter.ConvertFromInvariantString(value);
255  }
256 
257 
258  private static bool IsList(FieldInfo field)
259  {
260  return typeof (IList).IsAssignableFrom(field.FieldType);
261  }
262 
263 
264  private IList GetList(FieldInfo field)
265  {
266  return (IList) field.GetValue(optionsObject);
267  }
268 
269 
270  private static Type ListElementType(FieldInfo field)
271  {
272  foreach (var fieldInterface in field.FieldType.GetInterfaces())
273  {
274  if (fieldInterface.IsGenericType && fieldInterface.GetGenericTypeDefinition() == typeof (IEnumerable<>))
275  {
276  return fieldInterface.GetGenericArguments()[0];
277  }
278  }
279 
280  return null;
281  }
282 
283  private static OptionAttribute GetOptionName(FieldInfo field)
284  {
285  return GetAttribute<OptionAttribute>(field) ?? new OptionAttribute(field.Name);
286  }
287 
288  public virtual string GetUsageFooter()
289  {
290  return string.Empty;
291  }
292 
293  public void ShowError(string message, params object[] args)
294  {
295  string name = Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().ProcessName);
296 
297  ErrorColor();
298  System.Console.Error.WriteLine(message, args);
299  ResetColor();
300  System.Console.Error.WriteLine();
301  System.Console.Out.WriteLine("Usage: {0} {1}", name, string.Join(" ", requiredUsageHelp.ToArray()));
302 
303  if (optionalUsageHelp.Count > 0)
304  {
305  System.Console.Out.WriteLine();
306  System.Console.Out.WriteLine("Options:");
307 
308  foreach (string optional in optionalUsageHelp)
309  {
310  System.Console.Out.WriteLine(" {0}", optional);
311  }
312  }
313 
314  System.Console.Out.WriteLine(GetUsageFooter());
315  }
316 
317  private static T GetAttribute<T>(ICustomAttributeProvider provider) where T : Attribute
318  {
319  var attributes = provider.GetCustomAttributes(typeof (T), false);
320  if (attributes.Length > 0)
321  {
322  return (T)attributes[0];
323  }
324  return null;
325  }
326 
327  /// <summary>
328  /// Gets the assembly title.
329  /// </summary>
330  /// <value>The assembly title.</value>
331  private static string GetAssemblyTitle()
332  {
333  var attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false);
334  if (attributes.Length > 0)
335  {
336  var titleAttribute = (AssemblyTitleAttribute)attributes[0];
337  if (!string.IsNullOrEmpty(titleAttribute.Title))
338  return titleAttribute.Title;
339  }
340  return Path.GetFileNameWithoutExtension(Assembly.GetExecutingAssembly().CodeBase);
341  }
342 
343  /// <summary>
344  /// Gets the assembly title.
345  /// </summary>
346  /// <value>The assembly title.</value>
347  private static string GetAssemblyCopyright()
348  {
349  var attributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false);
350  if (attributes.Length > 0)
351  {
352  var titleAttribute = (AssemblyCopyrightAttribute)attributes[0];
353  if (!string.IsNullOrEmpty(titleAttribute.Copyright))
354  return titleAttribute.Copyright;
355  }
356  return string.Empty;
357  }
358 
359  // Used on optionsObject fields to indicate which options are required.
360 
361  [DllImport("kernel32.dll", EntryPoint = "GetStdHandle", SetLastError = true,
362  CharSet = CharSet.Auto,
363  CallingConvention = CallingConvention.StdCall)]
364  private static extern int GetStdHandle(int nStdHandle);
365 
366  [DllImport("kernel32.dll", EntryPoint = "GetConsoleScreenBufferInfo",
367  SetLastError = true, CharSet = CharSet.Auto,
368  CallingConvention = CallingConvention.StdCall)]
369  private static extern int GetConsoleScreenBufferInfo(int hConsoleOutput,
370  ref CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo);
371 
372  [DllImport("kernel32.dll", EntryPoint = "SetConsoleTextAttribute",
373  SetLastError = true, CharSet = CharSet.Auto,
374  CallingConvention = CallingConvention.StdCall)]
375  private static extern int SetConsoleTextAttribute(int hConsoleOutput,
376  int wAttributes);
377 
378  public static void ErrorColor()
379  {
380  Color(ConsoleColor.Red | ConsoleColor.Intensity);
381  }
382 
383  public static void Color(ConsoleColor color)
384  {
385  SetConsoleTextAttribute(hConsoleHandle, (int) color);
386  }
387 
388  public static void ResetColor()
389  {
390  SetConsoleTextAttribute(hConsoleHandle, OriginalColors);
391  }
392 
393  #region Nested type: CONSOLE_SCREEN_BUFFER_INFO
394 
395  [StructLayout(LayoutKind.Sequential)]
396  private struct CONSOLE_SCREEN_BUFFER_INFO
397  {
398  public COORD dwSize;
399  public COORD dwCursorPosition;
400  public int wAttributes;
401  public SMALL_RECT srWindow;
402  public COORD dwMaximumWindowSize;
403  }
404 
405  #endregion
406 
407  #region Nested type: COORD
408 
409  [StructLayout(LayoutKind.Sequential)]
410  private struct COORD
411  {
412  private short X;
413  private short Y;
414  }
415 
416  #endregion
417 
418  #region Nested type: OptionAttribute
419 
420  [AttributeUsage(AttributeTargets.Field)]
421  public sealed class OptionAttribute : Attribute
422  {
423  public OptionAttribute(string name)
424  {
425  this.Name = name;
426  }
427 
428  public string Name { get; private set; }
429 
430  public string Description { get; set; }
431 
432  public string Value { get; set; }
433 
434  public bool Required { get; set; }
435  }
436 
437  #endregion
438 
439  #region Nested type: SMALL_RECT
440 
441  [StructLayout(LayoutKind.Sequential)]
442  private struct SMALL_RECT
443  {
444  private short Left;
445  private short Top;
446  private short Right;
447  private short Bottom;
448  }
449 
450  #endregion
451  }
452 
453  /// <summary>
454  /// Colors used by <see cref="ConsoleProgram.Color"/>
455  /// </summary>
457  enum ConsoleColor
458  {
459  /// <summary>
460  /// Blue foreground color.
461  /// </summary>
462  Blue = 0x00000001,
463 
464  /// <summary>
465  /// Green foreground color.
466  /// </summary>
467  Green = 0x00000002,
468 
469  /// <summary>
470  /// Red foreground color.
471  /// </summary>
472  Red = 0x00000004,
473 
474  /// <summary>
475  /// Intensity foreground color modifier.
476  /// </summary>
477  Intensity = 0x00000008,
478 
479  /// <summary>
480  /// Blue background color.
481  /// </summary>
482  BlueBackground = 0x00000010,
483 
484  /// <summary>
485  /// Green background color.
486  /// </summary>
487  GreenBackground = 0x00000020,
488 
489  /// <summary>
490  /// Red background color.
491  /// </summary>
492  RedBackground = 0x00000040,
493 
494  /// <summary>
495  /// Intensity background color modifier.
496  /// </summary>
497  IntensityBackground = 0x00000080,
498 
499 
500  Black = 0,
501 
502  Cyan = Green | Blue,
503 
504  Magenta = Red | Blue,
505 
506  Yellow = Red | Green,
507 
508  DarkGrey = Red | Green | Blue,
509 
510  LightGrey = Intensity,
511 
512  LightRed = Intensity | Red,
513 
514  LightGreen = Intensity | Green,
515 
516  LightBlue = Intensity | Blue,
517 
518  LightCyan = Intensity | Green | Blue,
519 
520  LightMagenta = Intensity | Red | Blue,
521 
522  LightYellow = Intensity | Red | Green,
523 
524  White = Intensity | Red | Green | Blue,
525  }
526 }
Intensity background color modifier.
void ShowError(string message, params object[] args)
static bool ParseCommandLine(object options, string[] args, int padOptions=16)
Flags
Enumeration of the new Assimp's flags.
static void Color(ConsoleColor color)
The type of the serialized type will be passed as a generic arguments of the serializer. Example: serializer of A becomes instantiated as Serializer{A}.
SiliconStudio.Core.Mathematics.Color Color
Definition: ColorPicker.cs:14
ConsoleColor
Colors used by ConsoleProgram.Color
Reusable, reflection based helper for parsing commandline options. Greetings to Shawn Hargreaves...