Initial commit. master
Initial commit.

file:b/.gitattributes (new)
--- /dev/null
+++ b/.gitattributes
@@ -1,1 +1,13 @@
+* text=auto
+* eol=lf
 
+# These files are text and should be normalized (convert crlf => lf)
+*.cs      text diff=csharp
+*.cfg     text
+*.csproj  text eol=crlf
+*.sln     text eol=crlf
+
+# Images should be treated as binary
+# (binary is a macro for -text -diff)
+*.png     binary
+

file:b/ArgParseSharp.sln (new)
--- /dev/null
+++ b/ArgParseSharp.sln
@@ -1,1 +1,83 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArgParseSharp", "ArgParseSharp\ArgParseSharp.csproj", "{958CE889-9517-408C-A9ED-2791C3ACF301}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "Tests\Tests.csproj", "{08417F39-6BD7-401B-98DC-235726654767}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{08417F39-6BD7-401B-98DC-235726654767}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{08417F39-6BD7-401B-98DC-235726654767}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{08417F39-6BD7-401B-98DC-235726654767}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{08417F39-6BD7-401B-98DC-235726654767}.Release|Any CPU.Build.0 = Release|Any CPU
+		{958CE889-9517-408C-A9ED-2791C3ACF301}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{958CE889-9517-408C-A9ED-2791C3ACF301}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{958CE889-9517-408C-A9ED-2791C3ACF301}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{958CE889-9517-408C-A9ED-2791C3ACF301}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(MonoDevelopProperties) = preSolution
+		Policies = $0
+		$0.DotNetNamingPolicy = $1
+		$1.DirectoryNamespaceAssociation = None
+		$1.ResourceNamePolicy = FileFormatDefault
+		$0.TextStylePolicy = $2
+		$2.inheritsSet = null
+		$2.scope = text/x-csharp
+		$0.CSharpFormattingPolicy = $3
+		$3.IndentSwitchBody = True
+		$3.ClassBraceStyle = EndOfLine
+		$3.InterfaceBraceStyle = EndOfLine
+		$3.StructBraceStyle = EndOfLine
+		$3.EnumBraceStyle = EndOfLine
+		$3.MethodBraceStyle = EndOfLine
+		$3.ConstructorBraceStyle = EndOfLine
+		$3.DestructorBraceStyle = EndOfLine
+		$3.PropertyBraceStyle = NextLine
+		$3.SimpleGetBlockFormatting = ForceNewLine
+		$3.SimpleSetBlockFormatting = ForceNewLine
+		$3.EventBraceStyle = NextLine
+		$3.AllowEventAddBlockInline = False
+		$3.ElseNewLinePlacement = NewLine
+		$3.CatchNewLinePlacement = NewLine
+		$3.FinallyNewLinePlacement = NewLine
+		$3.BeforeMethodDeclarationParentheses = False
+		$3.BeforeMethodCallParentheses = False
+		$3.BeforeConstructorDeclarationParentheses = False
+		$3.BeforeIndexerDeclarationBracket = False
+		$3.BeforeDelegateDeclarationParentheses = False
+		$3.AfterDelegateDeclarationParameterComma = True
+		$3.NewParentheses = False
+		$3.SpacesBeforeBrackets = False
+		$3.BlankLinesBeforeUsings = 1
+		$3.MethodCallArgumentWrapping = WrapIfTooLong
+		$3.NewLineAferMethodCallOpenParentheses = NewLine
+		$3.MethodCallClosingParenthesesOnNewLine = NewLine
+		$3.MethodDeclarationParameterWrapping = WrapIfTooLong
+		$3.NewLineAferMethodDeclarationOpenParentheses = NewLine
+		$3.MethodDeclarationClosingParenthesesOnNewLine = NewLine
+		$3.AlignToFirstMethodDeclarationParameter = False
+		$3.IndexerDeclarationParameterWrapping = WrapIfTooLong
+		$3.NewLineAferIndexerDeclarationOpenBracket = NewLine
+		$3.IndexerDeclarationClosingBracketOnNewLine = NewLine
+		$3.AlignToFirstIndexerDeclarationParameter = False
+		$3.IndexerArgumentWrapping = WrapIfTooLong
+		$3.NewLineAferIndexerOpenBracket = NewLine
+		$3.IndexerClosingBracketOnNewLine = NewLine
+		$3.inheritsSet = Mono
+		$3.inheritsScope = text/x-csharp
+		$3.scope = text/x-csharp
+		$0.TextStylePolicy = $4
+		$4.FileWidth = 120
+		$4.TabsToSpaces = False
+		$4.EolMarker = Unix
+		$4.inheritsSet = VisualStudio
+		$4.inheritsScope = text/plain
+		$4.scope = text/plain
+	EndGlobalSection
+EndGlobal
 

--- /dev/null
+++ b/ArgParseSharp/ArgParseSharp.csproj
@@ -1,1 +1,41 @@
-
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{958CE889-9517-408C-A9ED-2791C3ACF301}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <RootNamespace>ArgParseSharp</RootNamespace>
+    <AssemblyName>ArgParseSharp</AssemblyName>
+    <UseMSBuildEngine>False</UseMSBuildEngine>
+    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>full</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Utilities\Utils.cs" />
+    <Compile Include="ArgParser.cs" />
+    <Compile Include="ArgScanner.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+</Project>

--- /dev/null
+++ b/ArgParseSharp/ArgParser.cs
@@ -1,1 +1,846 @@
-
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+using Utilities;
+
+namespace ArgParseSharp
+{
+	public enum ArgumentType : byte {
+		Positional,
+		Named
+	}
+
+	public enum ArgumentStatus : byte {
+		Required,
+		Optional
+	}
+
+	public class ArgumentDef {
+		public const char nullChar = '\0';
+		public const string emptyString = "";
+
+		public char ShortName;
+		public string LongName;
+		public ArgumentType ArgType;
+		public ArgumentStatus ArgStatus;
+		/// <summary>
+		/// The number of following arguments to consume, as a signed byte.  Negative values imply "greedy", meaning the
+		/// argument will consume all following sequential bare words.  Default is 0 (denoting a boolean flag).
+		/// </summary>
+		public sbyte Consumption;
+
+		public Type DataType
+		{
+			get {
+				return _dataType;
+			}
+			set {
+				switch (Type.GetTypeCode(value)) {
+					case TypeCode.String:
+					case TypeCode.Boolean:
+					case TypeCode.SByte:
+					case TypeCode.Int16:
+					case TypeCode.Int32:
+					case TypeCode.Int64:
+					case TypeCode.Byte:
+					case TypeCode.UInt16:
+					case TypeCode.UInt32:
+					case TypeCode.UInt64:
+					case TypeCode.Single:
+					case TypeCode.Double:
+					case TypeCode.Object:
+						_dataType = value;
+						break;
+					default:
+						throw new NotImplementedException(string.Format(
+							"Cannot set DataType to type '{0}': Unsupported type.",
+							value.FullName
+						));
+				}
+			}
+		}
+
+		private Type _dataType;
+
+		public ArgumentDef(
+			char shortName,
+			string longName,
+			Type dataType = null,
+			ArgumentType argType = ArgumentType.Positional,
+			ArgumentStatus argStatus = ArgumentStatus.Required,
+			sbyte consumption = 0
+		) {
+			if (shortName == nullChar && longName == emptyString) {
+				throw new Exception("Argument must have a ShortName, LongName, or both.");
+			}
+
+			if (argType == ArgumentType.Positional && consumption == 0) {
+				throw new Exception("Invalid number of values for positional argument '{0}'.  Positional arguments must be represented by at least one value.");
+			}
+
+			if (dataType == null) {
+				dataType = typeof(object);
+			}
+
+			this._dataType = null;
+			this.ShortName = shortName;
+			this.LongName = longName;
+			this.ArgType = argType;
+			this.ArgStatus = argStatus;
+			this.Consumption = consumption;
+
+			this.DataType = dataType;
+		}
+
+		public ArgumentDef(
+			char shortName,
+			Type dataType = null,
+			ArgumentType argType = ArgumentType.Positional,
+			ArgumentStatus argStatus = ArgumentStatus.Required,
+			sbyte consumption = 0
+		) : this(shortName, emptyString, dataType, argType, argStatus, consumption) {
+		}
+
+		public ArgumentDef(
+			string longName,
+			Type dataType = null,
+			ArgumentType argType = ArgumentType.Positional,
+			ArgumentStatus argStatus = ArgumentStatus.Required,
+			sbyte consumption = 0
+		) : this(nullChar, longName, dataType, argType, argStatus, consumption) {
+		}
+
+		public string HumanName() {
+			if (LongName != emptyString) {
+				return LongName;
+			}
+			else {
+				return ShortName.ToString();
+			}
+		}
+
+		public override string ToString() {
+			return string.Format(
+				"[ArgumentDef(shortName: '{0}', longName: \"{1}\", dataType: {2}, argType: {3}, argStatus: {4}, consumption: {5})]",
+				ShortName == nullChar ? "\\0" : ShortName.ToString(),
+				LongName,
+				DataType.FullName,
+				ArgType.ToString(),
+				ArgStatus.ToString(),
+				Consumption
+			);
+		}
+	}
+
+	public struct Argument {
+		public ArgumentDef Definition;
+		public object Value;
+
+		public override string ToString() {
+			string valueString;
+
+			if (Value is IList) {
+				valueString = Utils.JoinArray(Value as IList);
+			}
+			else {
+				valueString = Utils.SafeFormatObject(Value, string.Empty);
+			}
+
+			return string.Format("[Argument(definition: {0}, value: {1})]", Definition.ToString(), valueString);
+		}
+	}
+
+	public interface ArgumentCollection : IEnumerable<KeyValuePair<ArgumentDef, Argument>> {
+		void AddArgumentByDef(ArgumentDef key, Argument value);
+
+		Argument GetArgument(ArgumentDef def);
+
+		Argument GetArgumentByName(char shortName);
+
+		Argument GetArgumentByName(string longName);
+
+		bool ContainsArgument(ArgumentDef def);
+
+		bool ContainsArgumentByName(char shortName);
+
+		bool ContainsArgumentByName(string longName);
+
+		Argument this[ArgumentDef def] { get; }
+
+		Argument this[char shortName] { get; }
+
+		Argument this[string longName] { get; }
+	}
+
+	public static class ArgParser {
+		private class _ArgumentCollection : ArgumentCollection, IEnumerable<KeyValuePair<ArgumentDef, Argument>> {
+			private Dictionary<ArgumentDef, Argument> args;
+
+			public void AddArgumentByDef(ArgumentDef key, Argument value) {
+				if (args.ContainsKey(key)) { // This would probably signify a bug in the parser.
+					throw new Exception(string.Format("Cannot add duplicate argument {0}", key.HumanName()));
+				}
+				args[key] = value;
+			}
+
+			public Argument GetArgument(ArgumentDef def) {
+				return args[def];
+			}
+
+			public Argument GetArgumentByName(char shortName) {
+				return args[ArgParser.GetDefByShortName(shortName)];
+			}
+
+			public Argument GetArgumentByName(string longName) {
+				return args[ArgParser.GetDefByLongName(longName)];
+			}
+
+			public bool ContainsArgument(ArgumentDef def) {
+				return args.ContainsKey(def);
+			}
+
+			public bool ContainsArgumentByName(char shortName) {
+				return args.ContainsKey(ArgParser.GetDefByShortName(shortName));
+			}
+
+			public bool ContainsArgumentByName(string longName) {
+				return args.ContainsKey(ArgParser.GetDefByLongName(longName));
+			}
+
+			public Argument this[ArgumentDef def]
+			{
+				get {
+					return GetArgument(def);
+				}
+			}
+
+			public Argument this[char shortName]
+			{
+				get {
+					return GetArgumentByName(shortName);
+				}
+			}
+
+			public Argument this[string longName]
+			{
+				get {
+					return GetArgumentByName(longName);
+				}
+			}
+
+			public IEnumerator<KeyValuePair<ArgumentDef, Argument>> GetEnumerator() {
+				return args.GetEnumerator();
+			}
+
+			IEnumerator IEnumerable.GetEnumerator() {
+				return GetEnumerator();
+			}
+
+			internal _ArgumentCollection(Dictionary<ArgumentDef, Argument> _args) {
+				args = _args;
+			}
+
+			internal _ArgumentCollection() : this(new Dictionary<ArgumentDef, Argument>()) {
+			}
+		}
+
+		public static IList<ArgumentDef> Defs { get; private set; }
+
+		public static ArgumentCollection Arguments { get; private set; }
+
+		private static List<ArgumentDef> defs;
+		private static Dictionary<char, int> defsByShort;
+		private static Dictionary<string, int> defsByLong;
+		private static List<ArgumentDef> positionalDefs;
+		private static List<ArgumentDef> requiredDefs;
+		private static bool optionalPositionalDefined;
+		private static bool greedyArgDefined;
+
+		private static Dictionary<ArgumentDef, Argument> arguments;
+
+		private static sbyte positionalIdx;
+
+		static ArgParser() {
+			defs = new List<ArgumentDef>();
+			defsByShort = new Dictionary<char, int>();
+			defsByLong = new Dictionary<string, int>();
+			positionalDefs = new List<ArgumentDef>();
+			requiredDefs = new List<ArgumentDef>();
+			optionalPositionalDefined = false;
+			greedyArgDefined = false;
+
+			Defs = defs.AsReadOnly();
+
+			arguments = new Dictionary<ArgumentDef, Argument>();
+			Arguments = new _ArgumentCollection(arguments);
+
+			positionalIdx = 0;
+		}
+
+		public static ArgumentDef AddArgument(
+			char shortName,
+			string longName,
+			Type dataType = default(Type),
+			ArgumentType argType = ArgumentType.Positional,
+			ArgumentStatus argStatus = ArgumentStatus.Required,
+			sbyte consumption = 0
+		) {
+			ArgumentDef def = new ArgumentDef(shortName, longName, dataType, argType, argStatus, consumption);
+
+			int _;
+
+			if (def.ShortName != ArgumentDef.nullChar && defsByShort.TryGetValue(def.ShortName, out _)) {
+				throw new Exception(string.Format("Cannot add new argument with duplicate short name '{0}'", def.ShortName));
+			}
+
+			if (def.LongName != ArgumentDef.emptyString && defsByLong.TryGetValue(def.LongName, out _)) {
+				throw new Exception(string.Format("Cannot add new argument with duplicate long name \"{0}\"", def.LongName));
+			}
+
+			if (def.ArgType == ArgumentType.Positional && def.ArgStatus == ArgumentStatus.Required && optionalPositionalDefined) {
+				throw new Exception("Cannot add new required positional arguments after optional positional arguments.");
+			}
+
+			if (def.ArgType == ArgumentType.Positional && def.ArgStatus == ArgumentStatus.Required && greedyArgDefined) {
+				throw new Exception("Cannot add new required positional arguments after greedy arguments.");
+			}
+
+			defs.Add(def);
+
+			if (def.Consumption < 0) {
+				greedyArgDefined = true;
+			}
+
+			if (def.ArgType == ArgumentType.Positional) {
+				positionalDefs.Add(def);
+
+				if (def.ArgStatus == ArgumentStatus.Optional) {
+					optionalPositionalDefined = true;
+				}
+			}
+
+			if (def.ArgStatus == ArgumentStatus.Required) {
+				requiredDefs.Add(def);
+			}
+
+			if (def.ShortName != ArgumentDef.nullChar) {
+				defsByShort[def.ShortName] = defs.Count - 1;
+			}
+
+			if (def.LongName != ArgumentDef.emptyString) {
+				defsByLong[def.LongName] = defs.Count - 1;
+			}
+
+			return def;
+		}
+
+		public static ArgumentDef AddArgument(
+			char shortName,
+			Type dataType = default(Type),
+			ArgumentType argType = ArgumentType.Positional,
+			ArgumentStatus argStatus = ArgumentStatus.Required,
+			sbyte consumption = 0
+		) {
+			return AddArgument(shortName, ArgumentDef.emptyString, dataType, argType, argStatus, consumption);
+		}
+
+		public static ArgumentDef AddArgument(
+			string longName,
+			Type dataType = default(Type),
+			ArgumentType argType = ArgumentType.Positional,
+			ArgumentStatus argStatus = ArgumentStatus.Required,
+			sbyte consumption = 0
+		) {
+			return AddArgument(ArgumentDef.nullChar, longName, dataType, argType, argStatus, consumption);
+		}
+
+		public static ArgumentDef GetDefByShortName(char shortName) {
+			int idx;
+
+			if (defsByShort.TryGetValue(shortName, out idx)) {
+				return defs[idx];
+			}
+
+			throw new Exception(string.Format("No argument found with short name '{0}'", shortName));
+		}
+
+		public static ArgumentDef GetDefByLongName(string longName) {
+			int idx;
+
+			if (defsByLong.TryGetValue(longName, out idx)) {
+				return defs[idx];
+			}
+
+			throw new Exception(string.Format("No argument found with long name '{0}'", longName));
+		}
+
+		private static ArgumentDef GetDefByToken(Token token) {
+			switch (token.Type) {
+				case TokenType.ShortOption:
+					return GetDefByShortName((char)token.Data);
+				case TokenType.LongOption:
+					return GetDefByLongName((string)token.Data);
+				default:
+					throw new NotImplementedException();
+			}
+		}
+
+		private static ArgumentDef GetNextPositionalDef() {
+			if (positionalDefs.Count > positionalIdx) {
+				return positionalDefs[positionalIdx++];
+			}
+			throw new Exception(string.Format("Too many positional arguments; expected no more than {0}.", positionalDefs.Count));
+		}
+
+		private static List<ArgumentDef> FindMissingRequiredDefs() {
+			List<ArgumentDef> missingDefs = new List<ArgumentDef>();
+			foreach (ArgumentDef def in requiredDefs) {
+				Argument _;
+				if (!arguments.TryGetValue(def, out _)) {
+					missingDefs.Add(def);
+				}
+			}
+
+			return missingDefs;
+		}
+
+		public static ArgumentCollection ParseArgs(string[] args) {
+			List<Token> tokens = ArgScanner.TokenizeArgs(args);
+
+			IEnumerator<Token> tokenIterator = tokens.GetEnumerator();
+
+			while (tokenIterator.MoveNext()) {
+				Argument arg = ParseNextToken(tokenIterator);
+				arguments.Add(arg.Definition, arg);
+			}
+
+			List<ArgumentDef> missingDefs = FindMissingRequiredDefs();
+
+			if (missingDefs.Count > 0) {
+				throw new Exception("Missing required arguments");
+			}
+
+			return Arguments;
+		}
+
+		private static Argument ParseNextToken(IEnumerator<Token> tokenIterator) {
+			Token token = tokenIterator.Current;
+
+			switch (token.Type) {
+				case TokenType.ShortOption:
+					return ParseShortArg(tokenIterator);
+				case TokenType.LongOption:
+					return ParseLongArg(tokenIterator);
+				case TokenType.BareArg:
+					return ParsePositionalArg(tokenIterator);
+				default:
+					throw new NotImplementedException("We don't serve your kind here");
+			}
+		}
+
+		private static Argument ParsePositionalArg(IEnumerator<Token> tokenIterator) {
+			ArgumentDef def = GetNextPositionalDef();
+			Argument arg = new Argument();
+			arg.Definition = def;
+
+
+			sbyte iter = 0;
+
+			List<object> argValues = new List<object>();
+
+			do {
+				iter++;
+				Token token = tokenIterator.Current;
+
+				if (token.Type != TokenType.BareArg) {
+					return arg;
+				}
+
+				argValues.Add(ConvertTokenDataToType(token.Data, def.DataType));
+			} while ((def.Consumption < 0 || (iter < def.Consumption)) && tokenIterator.MoveNext());
+
+			if (argValues.Count < def.Consumption) {
+				throw new Exception(string.Format(
+					"Not enough values for positional argument '{0}'; {1} {2} required.",
+					def.ToString(),
+					def.Consumption,
+					def.Consumption > 1 ? "are" : "is"
+				));
+			}
+
+			if (def.Consumption == 1) {
+				arg.Value = argValues[0];
+			}
+			else {
+				arg.Value = argValues;
+			}
+
+			return arg;
+		}
+
+		private static Argument ParseLongArg(IEnumerator<Token> tokenIterator) {
+			Token token = tokenIterator.Current;
+
+			ArgumentDef def = GetDefByToken(token);
+
+			Argument arg = new Argument();
+			arg.Definition = def;
+
+			if (def.Consumption == 0) { // No consumption
+				return arg;
+			}
+			else if (def.Consumption == 1) { // Consume single following argument
+				if (tokenIterator.MoveNext()) {
+					Token next = tokenIterator.Current;
+					string data;
+
+					// If the next token is a ShortOption but we're a consumer, grab new bare data out of the
+					// next sequential ShortOptions
+					switch (next.Type) {
+						case TokenType.BareArg:
+							data = (string)next.Data;
+							break;
+						default:
+							throw new Exception(string.Format(
+								"Unexpected token type '{0}' after '{1}': Expected one of '{2}'",
+								next.Type,
+								token.Type,
+								Utils.JoinArray(new TokenType[] {
+									TokenType.BareArg
+								})
+							));
+					}
+
+					arg.Value = ConvertTokenDataToType(data, def.DataType);
+					return arg;
+				}
+
+				throw new Exception(string.Format(
+					"No arguments after option '{0}'; {1} {2} required.",
+					def.ToString(),
+					def.Consumption,
+					def.Consumption > 1 ? "are" : "is"
+				));
+			}
+			else if (def.Consumption < 0) { // Greedy
+				bool firstTime = true;
+				List<object> argData = new List<object>();
+
+				while (tokenIterator.MoveNext()) {
+					Token next = tokenIterator.Current;
+					string tokenData;
+
+					switch (next.Type) {
+						case TokenType.BareArg:
+							tokenData = (string)next.Data;
+							break;
+						default:
+							if (firstTime) {
+								throw new Exception(string.Format(
+									"Unexpected token type '{0}' after '{1}': Expected one of '{2}'",
+									next.Type,
+									token.Type,
+									Utils.JoinArray(new TokenType[] {
+										TokenType.ShortOption,
+										TokenType.BareArg
+									})
+								));
+							}
+							else {
+								arg.Value = argData;
+								return arg;
+							}
+					}
+
+					argData.Add(ConvertTokenDataToType(tokenData, def.DataType));
+				}
+
+				if (argData.Count < 1) {
+					throw new Exception(string.Format(
+						"No arguments after greedy option '{0}'; at least one is required.",
+						def.ToString()
+					));
+				}
+				arg.Value = argData;
+				return arg;
+			}
+			else { // Fixed, positive number of args.
+				bool firstTime = true;
+				List<object> argData = new List<object>();
+
+				sbyte iter = 0;
+				while (iter++ < def.Consumption && tokenIterator.MoveNext()) {
+					Token next = tokenIterator.Current;
+					string tokenData;
+
+					switch (next.Type) {
+						case TokenType.BareArg:
+							tokenData = (string)next.Data;
+							break;
+						default:
+							if (firstTime) {
+								throw new Exception(string.Format(
+									"Unexpected token type '{0}' after '{1}': Expected one of '{2}'",
+									next.Type,
+									token.Type,
+									Utils.JoinArray(new TokenType[] {
+										TokenType.ShortOption,
+										TokenType.BareArg
+									})
+								));
+							}
+							else {
+								arg.Value = argData;
+								return arg;
+							}
+					}
+
+					argData.Add(ConvertTokenDataToType(tokenData, def.DataType));
+				}
+
+				if (argData.Count < def.Consumption) {
+					throw new Exception(string.Format(
+						"Not enough arguments after option '{0}'; {1} {2} required.",
+						def.ToString(),
+						def.Consumption,
+						def.Consumption > 1 ? "are" : "is"
+					));
+				}
+				arg.Value = argData;
+				return arg;
+			}
+		}
+
+		private static Argument ParseShortArg(IEnumerator<Token> tokenIterator) {
+			Token token = tokenIterator.Current;
+
+			ArgumentDef def = GetDefByToken(token);
+
+			Argument arg = new Argument();
+			arg.Definition = def;
+
+			if (def.Consumption == 0) { // No consumption
+				return arg;
+			}
+			else if (def.Consumption == 1) { // Consume single following argument
+				if (tokenIterator.MoveNext()) {
+					Token next = tokenIterator.Current;
+					string data;
+
+					// If the next token is a ShortOption but we're a consumer, grab new bare data out of the
+					// next sequential ShortOptions
+					switch (next.Type) {
+						case TokenType.ShortOption:
+							data = ConsumeSequentialShortOptions(tokenIterator);
+							break;
+						case TokenType.BareArg:
+							data = (string)next.Data;
+							break;
+						default:
+							throw new Exception(string.Format(
+								"Unexpected token type '{0}' after '{1}': Expected one of '{2}'",
+								next.Type,
+								token.Type,
+								Utils.JoinArray(new TokenType[] {
+									TokenType.BareArg
+								})
+							));
+					}
+
+					arg.Value = ConvertTokenDataToType(data, def.DataType);
+					return arg;
+				}
+
+				throw new Exception(string.Format(
+					"No arguments after option '{0}'; {1} {2} required.",
+					def.ToString(),
+					def.Consumption,
+					def.Consumption > 1 ? "are" : "is"
+				));
+			}
+			else if (def.Consumption < 0) { // Greedy
+				bool firstTime = true;
+				List<object> argData = new List<object>();
+
+				while (tokenIterator.MoveNext()) {
+					Token next = tokenIterator.Current;
+					string tokenData;
+
+					switch (next.Type) {
+						case TokenType.ShortOption:
+							if (firstTime) {
+								tokenData = ConsumeSequentialShortOptions(tokenIterator);
+							}
+							else {
+								arg.Value = argData;
+								return arg;
+							}
+							break;
+						case TokenType.BareArg:
+							tokenData = (string)next.Data;
+							break;
+						default:
+							if (firstTime) {
+								throw new Exception(string.Format(
+									"Unexpected token type '{0}' after '{1}': Expected one of '{2}'",
+									next.Type,
+									token.Type,
+									Utils.JoinArray(new TokenType[] {
+										TokenType.ShortOption,
+										TokenType.BareArg
+									})
+								));
+							}
+							else {
+								arg.Value = argData;
+								return arg;
+							}
+					}
+
+					argData.Add(ConvertTokenDataToType(tokenData, def.DataType));
+				}
+
+				if (argData.Count < 1) {
+					throw new Exception(string.Format(
+						"No arguments after greedy option '{0}'; at least one is required.",
+						def.ToString()
+					));
+				}
+				arg.Value = argData;
+				return arg;
+			}
+			else { // Fixed, positive number of args.
+				bool firstTime = true;
+				List<object> argData = new List<object>();
+
+				sbyte iter = 0;
+				while (iter++ < def.Consumption && tokenIterator.MoveNext()) {
+					Token next = tokenIterator.Current;
+					string tokenData;
+
+					switch (next.Type) {
+						case TokenType.ShortOption:
+							if (firstTime) {
+								tokenData = ConsumeSequentialShortOptions(tokenIterator);
+							}
+							else {
+								arg.Value = argData;
+								return arg;
+							}
+							break;
+						case TokenType.BareArg:
+							tokenData = (string)next.Data;
+							break;
+						default:
+							if (firstTime) {
+								throw new Exception(string.Format(
+									"Unexpected token type '{0}' after '{1}': Expected one of '{2}'",
+									next.Type,
+									token.Type,
+									Utils.JoinArray(new TokenType[] {
+										TokenType.ShortOption,
+										TokenType.BareArg
+									})
+								));
+							}
+							else {
+								if (argData.Count < def.Consumption) {
+									throw new Exception(string.Format(
+										"Not enough arguments after option '{0}'; {1} {2} required.",
+										def.ToString(),
+										def.Consumption,
+										def.Consumption > 1 ? "are" : "is"
+									));
+								}
+
+								arg.Value = argData;
+								return arg;
+							}
+					}
+
+					argData.Add(ConvertTokenDataToType(tokenData, def.DataType));
+				}
+
+				if (argData.Count < def.Consumption) {
+					throw new Exception(string.Format(
+						"Not enough arguments after option '{0}'; {1} {2} required.",
+						def.ToString(),
+						def.Consumption,
+						def.Consumption > 1 ? "are" : "is"
+					));
+				}
+
+				arg.Value = argData;
+				return arg;
+			}
+		}
+
+		private static string ConsumeSequentialShortOptions(IEnumerator<Token> tokenIterator) {
+			Token token = tokenIterator.Current;
+
+			if (token.Type != TokenType.ShortOption) {
+				throw new ArgumentException("Argument must point to a valid ShortOption token.", "tokenIterator");
+			}
+
+			StringBuilder sb = new StringBuilder();
+
+			sb.Append((char)token.Data);
+
+			while (tokenIterator.MoveNext()) {
+				token = tokenIterator.Current;
+
+				if (token.Type == TokenType.ShortOption) {
+					sb.Append((char)token.Data);
+				}
+				else {
+					break;
+				}
+			}
+
+			return sb.ToString();
+		}
+
+		private static object ConvertTokenDataToType(object data, Type t) {
+			switch (Type.GetTypeCode(t)) {
+				case TypeCode.String:
+					return (string)data;
+				case TypeCode.Boolean:
+					return Boolean.Parse((string)data);
+				case TypeCode.SByte:
+					return SByte.Parse((string)data);
+				case TypeCode.Int16:
+					return Int16.Parse((string)data);
+				case TypeCode.Int32:
+					return Int32.Parse((string)data);
+				case TypeCode.Int64:
+					return Int64.Parse((string)data);
+				case TypeCode.Byte:
+					return Byte.Parse((string)data);
+				case TypeCode.UInt16:
+					return UInt16.Parse((string)data);
+				case TypeCode.UInt32:
+					return UInt32.Parse((string)data);
+				case TypeCode.UInt64:
+					return UInt64.Parse((string)data);
+				case TypeCode.Single:
+					return Single.Parse((string)data);
+				case TypeCode.Double:
+					return Double.Parse((string)data);
+				case TypeCode.Object:
+					return data;
+				default:
+					throw new NotImplementedException(string.Format(
+						"Cannot convert data to type '{0}': Unsupported type.",
+						t.FullName
+					));
+			}
+		}
+
+		private static T ConvertTokenDataToType<T>(object data) {
+			return (T)ConvertTokenDataToType(data, typeof(T));
+		}
+	}
+}
+
+

--- /dev/null
+++ b/ArgParseSharp/ArgScanner.cs
@@ -1,1 +1,245 @@
-
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.RegularExpressions;
+using Utilities;
+
+namespace ArgParseSharp
+{
+	public enum TokenType : byte {
+		ShortOption,
+		LongOption,
+		BareArg
+	}
+
+	public struct Token {
+		public TokenType Type;
+		public object Data;
+
+		public Token(TokenType type, object data) {
+			this.Type = type;
+			this.Data = data;
+		}
+
+		public override string ToString() {
+			return string.Format("Token(type: {0}, data: {1})", this.Type.ToString(), this.Data);
+		}
+	}
+
+	internal static class ArgScanner {
+		enum ScannerState : byte {
+			Unstarted,
+			InOptionToken,
+			InShortArg,
+			InLongArg,
+			InBareArg,
+			InQuoteString,
+			InEscapeChar,
+			NeedsNextBare,
+			NeedsNextQuote
+		}
+
+		public const char tokenOptionChar = '-';
+		public const char assignmentOpChar = '=';
+		public const char doubleQuoteChar = '"';
+		public const char singleQuoteChar = '\'';
+		public const char escapeNextChar = '\\';
+
+		static StringBuilder tokenBuilder = new StringBuilder();
+		static ScannerState state;
+		static ScannerState escapeReturnState;
+
+		private static Token NextTokenFromArg(string s, ref int idx) {
+			switch (state) {
+				case ScannerState.NeedsNextBare:
+					state = ScannerState.InBareArg;
+					break;
+				case ScannerState.NeedsNextQuote:
+					tokenBuilder.Append(' ');
+					state = ScannerState.InQuoteString;
+					break;
+				case ScannerState.InShortArg:
+					tokenBuilder.Length = 0;
+					break;
+				default:
+					state = ScannerState.Unstarted;
+					tokenBuilder.Length = 0;
+					break;
+			}
+			Token token;
+
+			while (idx < s.Length) {
+				char c = s[idx];
+				switch (state) {
+					case ScannerState.Unstarted:
+						switch (c) {
+							case tokenOptionChar:
+								state = ScannerState.InOptionToken;
+								idx++;
+								break;
+							case doubleQuoteChar:
+							case singleQuoteChar:
+								idx++;
+								state = ScannerState.InQuoteString;
+								break;
+							default:
+								state = ScannerState.InBareArg;
+								break;
+						}
+						break;
+					case ScannerState.InOptionToken:
+						switch (c) {
+							case tokenOptionChar:
+								state = ScannerState.InLongArg;
+								idx++;
+								break;
+							default:
+								if (Char.IsWhiteSpace(c)) {
+									// If we get a single '-' followed by whitespace, figure it's a bare arg
+									// (like a stdin placeholder).
+									idx++;
+									return new Token(TokenType.BareArg, c);
+								}
+								else {
+									state = ScannerState.InShortArg;
+								}
+								break;
+						}
+						break;
+					case ScannerState.InShortArg:
+						if (Char.IsLetterOrDigit(c)) {
+							idx++;
+							return new Token(TokenType.ShortOption, c);
+						}
+						else {
+							throw new Exception(string.Format(
+								"Unexpected character '{0}' at position {1}.  Short options must be letters or digits.",
+								c,
+								idx
+							));
+						}
+					case ScannerState.InLongArg:
+						if (Char.IsWhiteSpace(c) || c == assignmentOpChar) {
+							idx++;
+							// The token is done, return it.
+							token = new Token(TokenType.LongOption, tokenBuilder.ToString());
+							tokenBuilder.Length = 0;
+							return token;
+						}
+						else {
+							// Keep adding string data to the token.
+							tokenBuilder.Append(c);
+							idx++;
+						}
+						break;
+					case ScannerState.InQuoteString:
+						switch (c) {
+							case doubleQuoteChar:
+							case singleQuoteChar:
+								idx++;
+								return new Token(TokenType.BareArg, tokenBuilder.ToString());
+							case escapeNextChar:
+								state = ScannerState.InEscapeChar;
+								escapeReturnState = ScannerState.InQuoteString;
+								break;
+							default:
+								idx++;
+								tokenBuilder.Append(c);
+								break;
+						}
+						break;
+					case ScannerState.InBareArg:
+						switch (c) {
+							case escapeNextChar:
+								state = ScannerState.InEscapeChar;
+								escapeReturnState = ScannerState.InBareArg;
+								break;
+							default:
+								// Keep adding string data to the token until the end of the arg.
+								tokenBuilder.Append(c);
+								idx++;
+								break;
+						}
+						break;
+					case ScannerState.InEscapeChar:
+						string escapeSeq = s.SafeSubstring(idx, 5);
+						if (idx == (s.Length - 1)) {
+							escapeSeq = string.Concat(escapeSeq, ' ');
+							state = ScannerState.NeedsNextBare;
+						}
+						else {
+							state = escapeReturnState;
+						}
+						try {
+							int unescapedLen = escapeSeq.Length;
+							escapeSeq = Regex.Unescape(escapeSeq);
+							int escapedLen = unescapedLen - escapeSeq.Length;
+							idx += escapedLen;
+							tokenBuilder.Append(escapeSeq[0]);
+						}
+						catch (ArgumentException) {
+							tokenBuilder.Append(c);
+						}
+
+						idx++;
+
+						break;
+				}
+			}
+
+			// If we get here, we've hit the end of the string.
+			// If the token builder has zero length, something is malformed.
+			if (tokenBuilder.Length < 1) {
+				throw new Exception(string.Format("Unexpected end of arg while parsing {0}.", state.ToString()));
+			}
+
+			switch (state) {
+				case ScannerState.InQuoteString:
+					state = ScannerState.NeedsNextQuote;
+					return new Token();
+				case ScannerState.NeedsNextBare:
+					return new Token();
+				case ScannerState.InBareArg:
+					return new Token(TokenType.BareArg, tokenBuilder.ToString());
+				case ScannerState.InShortArg:
+					return new Token(TokenType.ShortOption, tokenBuilder.ToString());
+				case ScannerState.InLongArg:
+					return new Token(TokenType.LongOption, tokenBuilder.ToString());
+				default:
+					throw new Exception(string.Format("Unexpected end of arg while parsing {0}.", state.ToString()));
+			}
+		}
+
+		public static List<Token> TokenizeArgs(string[] args) {
+			List<Token> tokens = new List<Token>();
+			foreach (string arg in args) {
+				int idx = 0;
+
+				while (idx < arg.Length) {
+					Token token = NextTokenFromArg(arg, ref idx);
+
+					switch (state) {
+						case ScannerState.NeedsNextBare:
+						case ScannerState.NeedsNextQuote:
+							break;
+						default:
+							tokens.Add(token);
+							break;
+					}
+				}
+
+				switch (state) {
+					case ScannerState.InShortArg:
+						state = ScannerState.Unstarted;
+						break;
+					default:
+						break;
+				}
+			}
+
+			return tokens;
+		}
+	}
+}
+
+

--- /dev/null
+++ b/ArgParseSharp/Properties/AssemblyInfo.cs
@@ -1,1 +1,16 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
 
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle("ArgParseSharp")]
+[assembly: AssemblyDescription(".NET Implementation roughly shadowing python's ArgParse")]
+[assembly: AssemblyCopyright("Andrew Wilkinson")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+[assembly: AssemblyVersion("0.1.*")]
+

--- /dev/null
+++ b/ArgParseSharp/Utilities/Utils.cs
@@ -1,1 +1,159 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
 
+namespace Utilities
+{
+	public static class Utils
+	{
+		public static string SafeFormatObject(this object o, string format, IFormatProvider provider, bool quoteCharsAndStrings) {
+			string s;
+			if (o is IFormattable) {
+				s = (o as IFormattable)?.ToString(format, provider) ?? "NULL";
+			}
+			else if (quoteCharsAndStrings && o is string) {
+				s = string.Format("\"{0}\"", o == null ? "NULL" : (o as string));
+			}
+			else if (quoteCharsAndStrings && o is char) {
+				s = string.Format("'{0}'", o);
+			}
+			else {
+				s = o?.ToString() ?? "NULL";
+			}
+
+			return s;
+		}
+
+		public static string SafeFormatObject(this object o, string format, bool quoteCharsAndStrings) {
+			return SafeFormatObject(o, format, System.Globalization.CultureInfo.CurrentCulture, quoteCharsAndStrings);
+		}
+
+		public static string SafeFormatObject(this object o, bool quoteCharsAndStrings) {
+			return SafeFormatObject(o, string.Empty, System.Globalization.CultureInfo.CurrentCulture, quoteCharsAndStrings);
+		}
+
+		public static string SafeFormatObject(this object o, string format) {
+			return SafeFormatObject(o, format, System.Globalization.CultureInfo.CurrentCulture, false);
+		}
+
+		public static string SafeFormatObject(this object o) {
+			return SafeFormatObject(o, string.Empty, System.Globalization.CultureInfo.CurrentCulture, false);
+		}
+
+		public static string SafeSubstring(this string s, int startIndex, int length=int.MaxValue) {
+			length = Math.Min(length, s.Length - startIndex);
+			return s.Substring(startIndex, length);
+		}
+
+		public static string JoinArray<T>(T[] ar, string delim, string format, IFormatProvider provider)
+		{
+			int lengthGuess = ar.Length * (1 + delim.Length) + 2;
+			StringBuilder sb = new StringBuilder(lengthGuess);
+
+			sb.Append("[");
+
+			foreach (T o in ar)
+			{
+				sb.Append(SafeFormatObject(o, format, provider, true));
+				sb.Append(delim);
+			}
+
+			if (ar.Length > 0)
+				sb.Length -= delim.Length;
+
+			sb.Append("]");
+
+			return sb.ToString();
+		}
+
+		public static string JoinArray<T>(IList<T> ar, string delim, string format, IFormatProvider provider)
+		{
+			int lengthGuess = ar.Count * (1 + delim.Length) + 2;
+			StringBuilder sb = new StringBuilder(lengthGuess);
+
+			sb.Append("[");
+
+			foreach (T o in ar)
+			{
+				sb.Append(SafeFormatObject(o, format, provider, true));
+				sb.Append(delim);
+			}
+
+			if (ar.Count > 0)
+				sb.Length -= delim.Length;
+
+			sb.Append("]");
+
+			return sb.ToString();
+		}
+
+		public static string JoinArray(IList ar, string delim, string format, IFormatProvider provider)
+		{
+			int lengthGuess = ar.Count * (1 + delim.Length) + 2;
+			StringBuilder sb = new StringBuilder(lengthGuess);
+
+			sb.Append("[");
+
+			foreach (object o in ar)
+			{
+				sb.Append(SafeFormatObject(o, format, provider, true));
+				sb.Append(delim);
+			}
+
+			if (ar.Count > 0)
+				sb.Length -= delim.Length;
+
+			sb.Append("]");
+
+			return sb.ToString();
+		}
+
+		public static string JoinArray<T>(T[] ar, string delim, string format)
+		{
+			return JoinArray(ar, delim, format, System.Globalization.CultureInfo.CurrentCulture);
+		}
+
+		public static string JoinArray<T>(T[] ar, string delim)
+		{
+			return JoinArray(ar, delim, "", System.Globalization.CultureInfo.CurrentCulture);
+		}
+
+		public static string JoinArray<T>(T[] ar)
+		{
+			return JoinArray(ar, ", ", "", System.Globalization.CultureInfo.CurrentCulture);
+		}
+
+		public static string JoinArray<T>(IList<T> ar, string delim, string format)
+		{
+			return JoinArray(ar, delim, format, System.Globalization.CultureInfo.CurrentCulture);
+		}
+
+		public static string JoinArray<T>(IList<T> ar, string delim)
+		{
+			return JoinArray(ar, delim, "", System.Globalization.CultureInfo.CurrentCulture);
+		}
+
+		public static string JoinArray<T>(IList<T> ar)
+		{
+			return JoinArray(ar, ", ", "", System.Globalization.CultureInfo.CurrentCulture);
+		}
+
+		public static string JoinArray(IList ar, string delim, string format)
+		{
+			return JoinArray(ar, delim, format, System.Globalization.CultureInfo.CurrentCulture);
+		}
+
+		public static string JoinArray(IList ar, string delim)
+		{
+			return JoinArray(ar, delim, "", System.Globalization.CultureInfo.CurrentCulture);
+		}
+
+		public static string JoinArray(IList ar)
+		{
+			return JoinArray(ar, ", ", "", System.Globalization.CultureInfo.CurrentCulture);
+		}
+	}
+}
+
+

file:b/Tests/Program.cs (new)
--- /dev/null
+++ b/Tests/Program.cs
@@ -1,1 +1,25 @@
+using ArgParseSharp;
+using System;
 
+namespace Tests
+{
+	class MainClass
+	{
+		static int Main(string[] args) {
+			ArgParser.AddArgument('a', "alpha", argType: ArgumentType.Named, consumption: 0);
+			ArgParser.AddArgument(
+				"NUM",
+				argType: ArgumentType.Positional,
+				consumption: -1,
+				dataType: typeof(int)
+			);
+
+			foreach (var argPair in ArgParser.ParseArgs(new string[] {"--alpha", "7", "1", "55"})) {
+				Console.WriteLine(argPair.Value);
+			}
+
+			return 0;
+		}
+	}
+}
+

--- /dev/null
+++ b/Tests/Properties/AssemblyInfo.cs
@@ -1,1 +1,28 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
 
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle("Tests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("")]
+[assembly: AssemblyCopyright("andy")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+[assembly: AssemblyVersion("1.0.*")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
+
+

--- /dev/null
+++ b/Tests/Tests.csproj
@@ -1,1 +1,45 @@
-
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProjectGuid>{08417F39-6BD7-401B-98DC-235726654767}</ProjectGuid>
+    <OutputType>Exe</OutputType>
+    <RootNamespace>Tests</RootNamespace>
+    <AssemblyName>Tests</AssemblyName>
+    <UseMSBuildEngine>False</UseMSBuildEngine>
+    <TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <Externalconsole>true</Externalconsole>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>full</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <Externalconsole>true</Externalconsole>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Program.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <ItemGroup>
+    <ProjectReference Include="..\ArgParseSharp\ArgParseSharp.csproj">
+      <Project>{958CE889-9517-408C-A9ED-2791C3ACF301}</Project>
+      <Name>ArgParseSharp</Name>
+    </ProjectReference>
+  </ItemGroup>
+</Project>