Skip to content

Instantly share code, notes, and snippets.

@Alois-xx
Created December 2, 2022 13:03
Show Gist options
  • Save Alois-xx/28a751118e0ca8bc7932a5ff049335fd to your computer and use it in GitHub Desktop.
Save Alois-xx/28a751118e0ca8bc7932a5ff049335fd to your computer and use it in GitHub Desktop.
ColorConsole Template from old VS Console Extension written by Alois Kraus
// Console template from old VS template from 2010 https://marketplace.visualstudio.com/items?itemName=Alois.ColoredConsoleApplicationTemplate
// Original announcement: https://web.archive.org/web/20101115040531/http://geekswithblogs.net/akraus1/archive/2010/11/11/142685.aspx
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace $safeprojectname$
{
/// <summary>
/// Severity levels for each parser message. Used by <see cref="ArgParser.AddFormat"/>
/// </summary>
public enum Levels
{
Info,
Warning,
Error
}
public class ArgParser
{
/// Dictionary of command line switches. The action is called when the switch was found in the command line.
Dictionary<string, Action> mySwitches;
/// Dictionary of command line switches which expect one parameter. The action is called when the switch was found in the command line
Dictionary<string, Action<string>> mySwitchesWithArg;
// If at the end of the command line parameters are left this delegate is called.
Action<List<string>> myOtherArgs;
// List of info, warning, error messages that happened while parsing and verification of the command line switches
// They are printed via the PrintHelpWithMessages method.
List<Message> myMessages = new List<Message>();
// Supported command line switch tags
char[] myDelimiters = new char[] { '/', '-' };
/// <summary>
/// Construct a command line argument parser which can parse command line switches of the form /xxx -xxx.
/// </summary>
/// <param name="switches">Key is the command line switch. Value is an action that normally sets some boolean flag to true.</param>
/// <exception cref="ArgumentNullExcepton">switches must not be null.</exception>
public ArgParser(Dictionary<string, Action> switches) : this(switches, null, null)
{
}
/// <summary>
/// Construct a command line argument parser which can parse command line switches of the form /xxx -xxx and
/// switches with one parameter like -x argument.
/// </summary>
/// <param name="switches">Key is the command line switch. Value is an action that normally sets some boolean flag to true.</param>
/// <param name="switcheswithArg">Key is the command line switch. Value is an action that normally sets the passed parameter for this switch to a member variable. E.g. test.exe /files test.txt will call the delegate with the name files in the dictionary with test.txt as parameter. Can be null.</param>
/// <exception cref="ArgumentNullExcepton">switches must not be null.</exception>
public ArgParser(Dictionary<string, Action> switches, Dictionary<string, Action<string>> switcheswithArg) : this(switches, switcheswithArg, null)
{
}
/// <summary>
/// Construct a command line argument parser which can parse command line switches of the form /xxx -xxx and
/// switches with one parameter like -x argument.
/// </summary>
/// <param name="switches">Key is the command line switch. Value is an action that normally sets some boolean flag to true.</param>
/// <param name="switcheswithArg">Key is the command line switch. Value is an action that normally sets the passed parameter for this switch to a member variable. E.g. test.exe /files test.txt will call the delegate with the name files in the dictionary with test.txt as parameter. Can be null.</param>
/// <param name="otherArgs">Callback which is called with a list of the command line arguments which do not belong to a specific command line parameter. E.g. test.exe aaa bbb ccc will call this method with an array with aaa,bbb,ccc. Can be null.</param>
/// <exception cref="ArgumentNullExcepton">switches must not be null.</exception>
public ArgParser(Dictionary<string, Action> switches, Dictionary<string, Action<string>> switcheswithArg, Action<List<string>> otherArgs)
{
if (switches == null)
throw new ArgumentNullException("switches");
mySwitches = CreateDictWithShortCuts(switches);
myOtherArgs = otherArgs;
if (switcheswithArg == null)
{
mySwitchesWithArg = new Dictionary<string, Action<string>>();
}
else
{
mySwitchesWithArg = CreateDictWithShortCuts(switcheswithArg);
// check for duplicate keys
foreach (string key in mySwitches.Keys)
{
if (mySwitchesWithArg.ContainsKey(key))
{
throw new ArgumentException(
String.Format("The command line switch -{0} occurs in both switches dictionaries. Please make it unambiguous.", key));
}
}
}
}
/// <summary>
/// Parse command line and call the corresponding actions passed from the ctor for each
/// found command line argument.
/// </summary>
/// <param name="args">Command line array.</param>
/// <returns>true when all command line switched could be parsed. false otherwise. In this case you should call
/// PrintHelpWithMessages to print your help string. Then all validation errors will be printed as well.</returns>
public bool ParseArgs(string[] args)
{
if (args.Length == 0)
{
AddFormat(Levels.Error, "No arguments specified.");
return false;
}
for (int i = 0; i < args.Length; i++)
{
string arg = args[i];
// get command line parameter for current argument if there is one
string parameter = (i + 1 < args.Length) ? args[i + 1] : null;
parameter = IsSwitch(parameter) ? null : parameter;
if (parameter != null) // Advance counter to next command line argument which does not belong to the current one
{
i++;
}
if (IsSwitch(arg))
{
string strippedArg = arg.Substring(1).ToLower(); // command line switches are not case sensitive
if (true == mySwitches.OnValue(strippedArg)) // Set Flag for simple command line switch
{
if (parameter != null)
{
if (myOtherArgs == null)
{
AddFormat(Levels.Error, "Superflous argument ({0}) for command line switch {1}", parameter, arg);
}
else // Other arguments present then process it if this was the last command line argument
{
if (CallOtherArgs(args, i)) // all arguments are processed when this returns true
{
break;
}
}
}
continue;
}
// Set for given flag the passed parameter for this flag
bool? ret = mySwitchesWithArg.OnValueAndParameterNotNull(strippedArg, parameter);
// not found then it must be an unknown command line switch
if (null == ret)
{
AddFormat(Levels.Error, "Unknown command line switch {0}", arg);
}
else if (false == ret) // Found but argument was missing
{
AddFormat(Levels.Error, "Missing data for command line switch {0}", arg);
}
else // when command was ok perhaps we have some arguments left if this was the last argument
{
if (CallOtherArgs(args, i + 1)) // all arguments are processed when this returns true
{
break;
}
}
}
else
{
if (CallOtherArgs(args, (parameter == null) ? i : i-1))
{
break; // all arguments are processed when this returns true
}
else
{
AddFormat(Levels.Error, "Not a command line switch: {0}", arg);
if (parameter != null)
{
AddFormat(Levels.Error, "Not a command line switch: {0}", parameter);
}
}
}
}
return myMessages.Count == 0;
}
/// <summary>
/// Check if first character is one of the allowed command line tags.
/// </summary>
/// <param name="arg">command line argument to check. Can be null.</param>
/// <returns>true if it is an command line switch, false otherwise.</returns>
bool IsSwitch(string arg)
{
if (String.IsNullOrEmpty(arg))
return false;
return Array.Exists(myDelimiters, (char c) => arg[0] == c);
}
/// <summary>
/// Add a message to the list of messages which is displayed after parsing and parameter validation.
/// </summary>
/// <param name="level">Message type</param>
/// <param name="format">Message format string</param>
/// <param name="args">Optional message parameters</param>
public void AddFormat(Levels level, string format, params object[] args)
{
myMessages.Add(new Message { Level = level, Text = String.Format(format, args) });
}
/// <summary>
/// Call the delegate for the "other" arguments when no more command line switches are present.
/// </summary>
/// <param name="args">The command line argument array</param>
/// <param name="start">The start index from where the search starts.</param>
/// <returns>true if the delegate was called with the arguments. false otherwise.</returns>
bool CallOtherArgs(string[] args, int start)
{
List<string> ret = new List<string>();
for (int i = start; i < args.Length; i++)
{
string curr = args[i];
if (IsSwitch(curr)) // when a switch is found this is not the last argument. Do not process it
{
ret = null;
break;
}
else
{
ret.Add(curr);
}
}
if (myOtherArgs != null && ret != null)
{
myOtherArgs(ret);
return true;
}
else
{
return false;
}
}
private Dictionary<string, T> CreateDictWithShortCuts<T>(Dictionary<string, T> switches)
{
Dictionary<string, T> ret = new Dictionary<string, T>();
// Generate shortcut names from upper case letters of command line arguments
foreach (var kvp in switches)
{
ret.Add(kvp.Key.ToLower(), kvp.Value);
string shortCut = new string(
(from c in kvp.Key
where Char.IsUpper(c)
select c).ToArray()).ToLower();
if (shortCut != "")
{
if (ret.ContainsKey(shortCut))
{
throw new ArgumentException(
String.Format("The generated shortcut \"-{0}\" from \"-{1}\" collides with another command line switch.", shortCut, kvp.Key));
}
ret.Add(shortCut, kvp.Value);
}
}
return ret;
}
/// <summary>
/// Print help and colored error and warning messages to the console.
/// </summary>
/// <param name="helpString">Help string which is printed first. Can be null.</param>
public void PrintHelpWithMessages(string helpString)
{
if (helpString != null)
{
Console.WriteLine(helpString);
}
foreach (var message in myMessages)
{
string text = message.Text;
if (message.Level == Levels.Warning)
{
text = "Warning: " + text;
}
else if (message.Level == Levels.Error)
{
text = "Error: " + text;
}
Console.WriteLine(text);
}
}
struct Message
{
public Levels Level;
public string Text;
}
}
}
// Console template from old VS template from 2010 https://marketplace.visualstudio.com/items?itemName=Alois.ColoredConsoleApplicationTemplate
// Original announcement: https://web.archive.org/web/20101115040531/http://geekswithblogs.net/akraus1/archive/2010/11/11/142685.aspx
using System;
using System.Text;
using System.IO;
namespace $safeprojectname$
{
/// <summary>
/// Colored console class which does color the console output which do contain specific keywords.
/// The only thing you need to do is to instantiate this class and use Console.WriteLine as usual.
/// </summary>
class ColorConsole : TextWriter
{
TextWriter myOriginal = Console.Out;
Func<string, ConsoleColor?> myColorizer;
/// <summary>
/// Initializes a new instance of the <see cref="ColorConsole"/> class.
/// </summary>
/// <param name="colorizer">The colorizer function which does return the color for each line to be printed to console.</param>
public ColorConsole(Func<string, ConsoleColor?> colorizer)
{
if (colorizer == null)
{
throw new ArgumentNullException("colorizer");
}
myColorizer = colorizer;
if (!IsRedirected)
{
// Replace Console.Out with our own colorizing instance which will be removed on dispose
Console.SetOut(this);
}
}
public override Encoding Encoding
{
get { return myOriginal.Encoding; }
}
public override void WriteLine(string format)
{
// we do not need to set the color every time only for the ones which do return a color
ConsoleColor? newColor = myColorizer(format);
if (newColor != null)
{
ConsoleColor original = Console.ForegroundColor;
try
{
Console.ForegroundColor = newColor.Value;
myOriginal.WriteLine(format);
}
finally
{
Console.ForegroundColor = original;
}
}
else
{
myOriginal.WriteLine(format);
}
}
public override void Write(char[] buffer)
{
myOriginal.Write(buffer);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!IsRedirected)
{
Console.SetOut(myOriginal);
}
}
static bool? myIsRedirected;
static bool IsRedirected
{
get
{
if (myIsRedirected == null)
myIsRedirected = IsConsoleRedirected();
return myIsRedirected.Value;
}
}
static bool IsConsoleRedirected()
{
try
{
// this is the easiest way to check if sdtout is redirected. Then this property throws
// an exception.
bool visible = Console.CursorVisible;
return false;
}
catch (Exception)
{
return true;
}
}
}
}
// Console template from old VS template from 2010 https://marketplace.visualstudio.com/items?itemName=Alois.ColoredConsoleApplicationTemplate
// Original announcement: https://web.archive.org/web/20101115040531/http://geekswithblogs.net/akraus1/archive/2010/11/11/142685.aspx
using System;
using System.Collections.Generic;
namespace $safeprojectname$
{
static class Extensions
{
/// <summary>
/// Execute action inside a dictionary when the key is present. When key is not present do nothing.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dict">Dictionary</param>
/// <param name="key">key to look up acton</param>
/// <returns>true when action was found, false otherwise</returns>
public static bool OnValue<T>(this Dictionary<T, Action> dict, T key)
{
if (key == null)
{
throw new ArgumentNullException("key");
}
Action acc = null;
if( dict.TryGetValue(key, out acc))
{
acc();
return true;
}
return false;
}
/// <summary>
/// Execute action inside a dictionary with given parameter. When key is not present do nothing.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="dict">Dictionary</param>
/// <param name="key">key too look up action in dictionary.</param>
/// <param name="parameter">Passed parameter to action.</param>
/// <returns>true if action was found. false when parameter was null. null when no action for given key was found.</returns>
/// <exception cref="ArgumentNullException">When key is null.</exception>
/// <remarks>When the parameter is null the action will NOT be called since we expect some data to work with.
/// Besides this it makes error handline in the actions easier when they can rely on a non null input argument.</remarks>
public static bool? OnValueAndParameterNotNull<T>(this Dictionary<T, Action<T>> dict, T key, T parameter)
{
if (key == null)
{
throw new ArgumentNullException("key");
}
Action<T> acc = null;
if( dict.TryGetValue(key, out acc) )
{
if (parameter == null)
{
return false;
}
acc(parameter);
return true;
}
return null;
}
}
}
// Console template from old VS template from 2010 https://marketplace.visualstudio.com/items?itemName=Alois.ColoredConsoleApplicationTemplate
// Original announcement: https://web.archive.org/web/20101115040531/http://geekswithblogs.net/akraus1/archive/2010/11/11/142685.aspx
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Diagnostics;
using System.Reflection;
namespace $safeprojectname$
{
class Program
{
static string HelpStr =
String.Format("$safeprojectname$ (c) 2010 by Alois Kraus v{0}{1}", Assembly.GetExecutingAssembly().GetName().Version, Environment.NewLine) +
"Explain what your application does" + Environment.NewLine +
"Usage: " + Environment.NewLine +
"$safeprojectname$ [-OptionaL] -ArgSwitch <file> <other arguments>" + Environment.NewLine +
" -ArgSwitch <file> Sample switch with argument." + Environment.NewLine +
" [-OptionaL] Optional: Optional command line switch" + Environment.NewLine +
" Examples: " + Environment.NewLine +
" $safeprojectname$ " + Environment.NewLine +
" $safeprojectname$ -as file" + Environment.NewLine +
" $safeprojectname$ -as file arg1 arg2 arg3" + Environment.NewLine +
" $safeprojectname$ -Optional -argswitch file arg1 arg2 arg3" + Environment.NewLine +
" $safeprojectname$ -Optional -argswitch file" + Environment.NewLine +
Environment.NewLine;
#region Parsed Command Line Switches
public bool Optional
{
get;
set;
}
public string ArgSwitch
{
get;
set;
}
public List<string> OtherArgs
{
get;
set;
}
#endregion
/// <summary>
/// Main entry point which is directly called from main where nothing happens execept exception catching.
/// </summary>
/// <param name="args"></param>
public Program(string [] args)
{
// define parameterless command line switches.
// Please note: Upper case characters define the shortcut name for each switch
var switches = new Dictionary<string, Action>
{
{"OptionaL", () => Optional=true }, // shortcut -ol
};
// define command line switches which take one parameter
var switchWithArg = new Dictionary<string, Action<string>>
{
{"ArgSwitch", (arg) => ArgSwitch = arg }, // shortcut -AS
};
// Handler for <other arguments> if present
Action<List<string>> rest = (parameters) => OtherArgs = parameters;
ArgParser parser = new ArgParser(switches, switchWithArg, rest);
// check if command line is well formed
if( !parser.ParseArgs(args) )
{
parser.PrintHelpWithMessages(HelpStr);
return;
}
if(!ValidateArgs(parser))
{
// Display errors but not the help screen.
parser.PrintHelpWithMessages(null);
}
else
{
// Ok we can start
Run();
}
}
private void Run()
{
// Place here your actual code to execute your logic after the command line has been parsed and validated.
Console.WriteLine("Got Optional: {0}", this.Optional);
Console.WriteLine("Got ArgSwitch: {0}", this.ArgSwitch);
if (this.OtherArgs != null)
{
Console.WriteLine("Info: Additional Arg Count: {0}", this.OtherArgs.Count);
foreach (var addtionalArg in this.OtherArgs)
{
Console.WriteLine("Additional: {0}", addtionalArg);
}
}
}
/// <summary>
/// Check if the passed command line arguments do contain valid data
/// </summary>
/// <param name="parser">used for error reporting</param>
/// <returns>true if all parameters are valid. false otherwise.</returns>
private bool ValidateArgs(ArgParser parser)
{
bool lret = true;
/* if (String.IsNullOrEmpty(ArgSwitch) )
{
parser.AddFormat(Levels.Error, "No arg for -ArgSwitch passed.");
return false;
}
*/
return lret;
}
static void Main(string[] args)
{
using (ColorConsole console = new ColorConsole(Colorizer))
{
try
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
Program p = new Program(args);
}
catch (Exception ex)
{
PrintError("Error: {0}", ex);
}
}
}
/// <summary>
/// Set console color depending on currently to be printed line.
/// </summary>
/// <param name="line">line to be printed</param>
/// <returns>ConsoleColor if color needs to be set. Null otherwise.</returns>
static ConsoleColor? Colorizer(string line)
{
ConsoleColor? col = null;
if (line.StartsWith("Error"))
col = ConsoleColor.Red;
else if (line.StartsWith("Info"))
col = ConsoleColor.Green;
return col;
}
/// <summary>
/// Catch uncatched exceptions thrown from other threads.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
PrintError("Unhandled exception: {0}", (Exception)e.ExceptionObject);
}
static void PrintError(string format, Exception ex)
{
Trace.TraceError(format, ex);
Console.WriteLine(format, ex.Message);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment