You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

215 lines
6.8 KiB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. namespace IPA
  7. {
  8. public class Arguments
  9. {
  10. public static readonly Arguments CmdLine = new Arguments(Environment.GetCommandLineArgs());
  11. private readonly List<string> positional = new List<string>();
  12. private readonly Dictionary<string, string> longFlags = new Dictionary<string, string>();
  13. private readonly Dictionary<char, string> flags = new Dictionary<char, string>();
  14. private readonly List<ArgumentFlag> flagObjects = new List<ArgumentFlag>();
  15. private string[] toParse;
  16. private Arguments(string[] args)
  17. {
  18. toParse = args.Skip(1).ToArray();
  19. }
  20. public Arguments Flags(params ArgumentFlag[] toAdd)
  21. {
  22. foreach (var f in toAdd) AddFlag(f);
  23. return this;
  24. }
  25. public void AddFlag(ArgumentFlag toAdd)
  26. {
  27. if (toParse == null) throw new InvalidOperationException();
  28. flagObjects.Add(toAdd);
  29. }
  30. public void Process()
  31. {
  32. foreach (var arg in toParse)
  33. {
  34. if (arg.StartsWith("--"))
  35. { // parse as a long flag
  36. var name = arg.Substring(2); // cut off first two chars
  37. string value = null;
  38. if (name.Contains('='))
  39. {
  40. var spl = name.Split('=');
  41. name = spl[0];
  42. value = string.Join("=", spl, 1, spl.Length - 1);
  43. }
  44. longFlags.Add(name, value);
  45. }
  46. else if (arg.StartsWith("-"))
  47. { // parse as flags
  48. var argument = arg.Substring(1); // cut off first char
  49. var subBuildState = new StringBuilder();
  50. var parsingValue = false;
  51. var escaped = false;
  52. var mainChar = ' ';
  53. foreach (var chr in argument)
  54. {
  55. if (!parsingValue)
  56. {
  57. if (chr == '=')
  58. {
  59. parsingValue = true;
  60. }
  61. else
  62. {
  63. mainChar = chr;
  64. flags.Add(chr, null);
  65. }
  66. }
  67. else
  68. {
  69. if (!escaped)
  70. {
  71. if (chr == ',')
  72. {
  73. parsingValue = false;
  74. flags[mainChar] = subBuildState.ToString();
  75. subBuildState = new StringBuilder();
  76. continue;
  77. }
  78. else if (chr == '\\')
  79. {
  80. escaped = true;
  81. continue;
  82. }
  83. }
  84. subBuildState.Append(chr);
  85. }
  86. }
  87. if (parsingValue)
  88. {
  89. parsingValue = false;
  90. flags[mainChar] = subBuildState.ToString();
  91. subBuildState = new StringBuilder();
  92. }
  93. }
  94. else
  95. { // parse as positional
  96. positional.Add(arg);
  97. }
  98. }
  99. toParse = null;
  100. foreach (var flag in flagObjects)
  101. {
  102. foreach (var charFlag in flag.ShortFlags)
  103. {
  104. if (!(flag.exists_ = HasFlag(charFlag))) continue;
  105. flag.value_ = GetFlagValue(charFlag);
  106. goto FoundValue; // continue to next flagObjects item
  107. }
  108. foreach (var longFlag in flag.LongFlags)
  109. {
  110. if (!(flag.exists_ = HasLongFlag(longFlag))) continue;
  111. flag.value_ = GetLongFlagValue(longFlag);
  112. goto FoundValue; // continue to next flagObjects item
  113. }
  114. FoundValue:;
  115. }
  116. }
  117. public bool HasLongFlag(string flag)
  118. {
  119. return longFlags.ContainsKey(flag);
  120. }
  121. public bool HasFlag(char flag)
  122. {
  123. return flags.ContainsKey(flag);
  124. }
  125. public string GetLongFlagValue(string flag)
  126. {
  127. return longFlags[flag];
  128. }
  129. public string GetFlagValue(char flag)
  130. {
  131. return flags[flag];
  132. }
  133. public void PrintHelp()
  134. {
  135. const string indent = " ";
  136. var filename = Path.GetFileName(Environment.GetCommandLineArgs()[0]);
  137. const string format = @"usage:
  138. {2}{0} [FLAGS] [ARGUMENTS]
  139. flags:
  140. {1}";
  141. var flagsBuilder = new StringBuilder();
  142. foreach (var flag in flagObjects)
  143. {
  144. flagsBuilder.AppendFormat("{2}{0}{3}{1}",
  145. string.Join(", ", flag.ShortFlags.Select(s => $"-{s}").Concat( flag.LongFlags.Select(s => $"--{s}")) ),
  146. Environment.NewLine, indent, flag.ValueString != null ? "=" + flag.ValueString : "");
  147. flagsBuilder.AppendFormat("{2}{2}{0}{1}", flag.DocString, Environment.NewLine, indent);
  148. }
  149. Console.Write(format, filename, flagsBuilder, indent);
  150. }
  151. public IReadOnlyList<string> PositionalArgs => positional;
  152. }
  153. public class ArgumentFlag
  154. {
  155. internal readonly List<char> ShortFlags = new List<char>();
  156. internal readonly List<string> LongFlags = new List<string>();
  157. internal string value_;
  158. internal bool exists_;
  159. public ArgumentFlag(params string[] flags)
  160. {
  161. foreach (var part in flags)
  162. AddPart(part);
  163. }
  164. private void AddPart(string flagPart)
  165. {
  166. if (flagPart.StartsWith("--"))
  167. LongFlags.Add(flagPart.Substring(2));
  168. else if (flagPart.StartsWith("-"))
  169. ShortFlags.Add(flagPart[1]);
  170. }
  171. public bool Exists => exists_;
  172. public string Value => value_;
  173. public bool HasValue => Exists && Value != null;
  174. public string DocString { get; set; } = "";
  175. public string ValueString { get; set; }
  176. public static implicit operator bool(ArgumentFlag f)
  177. {
  178. return f.Exists;
  179. }
  180. }
  181. }