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.

213 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(Environment.GetCommandLineArgs());
  11. private readonly List<string> positional = new();
  12. private readonly Dictionary<string, string?> longFlags = new();
  13. private readonly Dictionary<char, string?> flags = new();
  14. private readonly List<ArgumentFlag> flagObjects = new();
  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. if (toParse == null) throw new InvalidOperationException();
  33. foreach (var arg in toParse)
  34. {
  35. if (arg.StartsWith("--"))
  36. { // parse as a long flag
  37. var name = arg.Substring(2); // cut off first two chars
  38. string? value = null;
  39. if (name.Contains('='))
  40. {
  41. var spl = name.Split('=');
  42. name = spl[0];
  43. value = string.Join("=", spl, 1, spl.Length - 1);
  44. }
  45. longFlags.Add(name, value);
  46. }
  47. else if (arg.StartsWith("-"))
  48. { // parse as flags
  49. var argument = arg.Substring(1); // cut off first char
  50. var subBuildState = new StringBuilder();
  51. var parsingValue = false;
  52. var escaped = false;
  53. var mainChar = ' ';
  54. foreach (var chr in argument)
  55. {
  56. if (!parsingValue)
  57. {
  58. if (chr == '=')
  59. {
  60. parsingValue = true;
  61. }
  62. else
  63. {
  64. mainChar = chr;
  65. flags.Add(chr, null);
  66. }
  67. }
  68. else
  69. {
  70. if (!escaped)
  71. {
  72. if (chr == ',')
  73. {
  74. parsingValue = false;
  75. flags[mainChar] = subBuildState.ToString();
  76. subBuildState = new StringBuilder();
  77. continue;
  78. }
  79. else if (chr == '\\')
  80. {
  81. escaped = true;
  82. continue;
  83. }
  84. }
  85. _ = subBuildState.Append(chr);
  86. }
  87. }
  88. if (parsingValue)
  89. {
  90. flags[mainChar] = subBuildState.ToString();
  91. }
  92. }
  93. else
  94. { // parse as positional
  95. positional.Add(arg);
  96. }
  97. }
  98. toParse = null;
  99. foreach (var flag in flagObjects)
  100. {
  101. foreach (var charFlag in flag.ShortFlags)
  102. {
  103. if (!(flag.exists_ = HasFlag(charFlag))) continue;
  104. flag.value_ = GetFlagValue(charFlag);
  105. goto FoundValue; // continue to next flagObjects item
  106. }
  107. foreach (var longFlag in flag.LongFlags)
  108. {
  109. if (!(flag.exists_ = HasLongFlag(longFlag))) continue;
  110. flag.value_ = GetLongFlagValue(longFlag);
  111. goto FoundValue; // continue to next flagObjects item
  112. }
  113. FoundValue:;
  114. }
  115. }
  116. public bool HasLongFlag(string flag)
  117. {
  118. return longFlags.ContainsKey(flag);
  119. }
  120. public bool HasFlag(char flag)
  121. {
  122. return flags.ContainsKey(flag);
  123. }
  124. public string? GetLongFlagValue(string flag)
  125. {
  126. return longFlags[flag];
  127. }
  128. public string? GetFlagValue(char flag)
  129. {
  130. return flags[flag];
  131. }
  132. public void PrintHelp()
  133. {
  134. const string indent = " ";
  135. var filename = Path.GetFileName(Environment.GetCommandLineArgs()[0]);
  136. const string format = @"usage:
  137. {2}{0} [FLAGS] [ARGUMENTS]
  138. flags:
  139. {1}";
  140. var flagsBuilder = new StringBuilder();
  141. foreach (var flag in flagObjects)
  142. {
  143. _ = flagsBuilder
  144. .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. .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();
  156. internal readonly List<string> LongFlags = new();
  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) => f.Exists;
  177. }
  178. }