AbstractCommand.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using Quadarax.Foundation.Common.Console;
  5. using Quadarax.Foundation.Common.Value;
  6. using Quadarax.Foundation.QConsole.Argument;
  7. using Quadarax.Foundation.QConsole.Context;
  8. using Quadarax.Foundation.QConsole.Extensions;
  9. namespace Quadarax.Foundation.QConsole.Command.Base
  10. {
  11. public abstract class AbstractCommand
  12. {
  13. #region *** Private Fields ***
  14. private Engine _engine;
  15. private IDictionary<string, AbstractArgument> _arguments;
  16. protected ICommandContext _context;
  17. #endregion
  18. #region *** Properties ***
  19. public abstract string Name { get; }
  20. public abstract string Description { get; }
  21. public AbstractArgument[] Arguments => _arguments.Values.ToArray();
  22. protected Engine Engine => _engine;
  23. #endregion
  24. #region *** Constructor ***
  25. public AbstractCommand(Engine engine)
  26. {
  27. _engine = engine ?? throw new ArgumentNullException(nameof(engine));
  28. _arguments = new Dictionary<string, AbstractArgument>();
  29. _context = _engine.Context.CreateCommandContext();
  30. WriteDebugInfo($"Context for command '{Name}' created: {_context}");
  31. var args = SetupArguments();
  32. foreach (var arg in args)
  33. _arguments.Add(arg.Code, arg);
  34. Init();
  35. WriteDebugInfo($"Command '{Name}' has {_arguments.Count} arguments. Initialized.");
  36. }
  37. #endregion
  38. #region *** Public operations ***
  39. public Result Execute(string input)
  40. {
  41. ConsoleWriter writer = null;
  42. try
  43. {
  44. writer = new ConsoleWriter(ConsoleColor.Green);
  45. foreach (var arg in Arguments)
  46. arg.SetValueAsDefault();
  47. ParseInput(input);
  48. if (_engine.Configuration.ShowStatusOnEveryCommand)
  49. ShowContextStatus(writer);
  50. var result = OnExecute();
  51. result = EndExecute(result);
  52. return result;
  53. }
  54. catch (Exception e)
  55. {
  56. return new Result(e);
  57. }
  58. finally
  59. {
  60. writer?.Dispose();
  61. }
  62. }
  63. public override string ToString()
  64. {
  65. return "Command: " + Name;
  66. }
  67. #endregion
  68. #region *** Private operations ***
  69. private void Init()
  70. {
  71. OnCommandInit();
  72. }
  73. private void ParseInput(string input)
  74. {
  75. //var args = input.Split(new[] {Engine.Configuration.CharacterNamedArgumentSeparator}, StringSplitOptions.RemoveEmptyEntries);
  76. var args = Split(input, Engine.Configuration.CharacterTextValueBraceSeparator, new[]
  77. {
  78. Engine.Configuration.CharacterNamedArgumentSeparator,
  79. Engine.Configuration.CharacterOrdinalArgumentSeparator
  80. });
  81. for (int i = 1; i < args.Length; i++)
  82. {
  83. var arg = args[i];
  84. var index = i - 1;
  85. WriteDebugInfo($"Parsing argument [index={index}] by value string '{arg}' in progress..");
  86. AbstractArgument argument = null;
  87. if (arg.StartsWith(Engine.Configuration.CharacterNamedArgumentSeparator))
  88. {
  89. //Named
  90. WriteDebugInfo("Detects argument as named.");
  91. var parsed = ParseNamedArgument(arg);
  92. if (!ContainsArgument(parsed.Item1))
  93. throw new Exception(
  94. $"Invalid argument '{Engine.Configuration.CharacterNamedArgumentSeparator}{parsed.Item1}' for this command.");
  95. argument = GetArgument(parsed.Item1);
  96. if (argument.IsPositional())
  97. if (!((OrdinalArgument)argument).IsCodeAllowed)
  98. throw new Exception($"Usage of ordinal argument '{argument.Code}' as named argument is not allowed!");
  99. if (argument.IsFlag())
  100. ((FlagArgument)argument).Set();
  101. else
  102. argument.Value.Set(parsed.Item2);
  103. }
  104. else
  105. {
  106. //Ordinal
  107. WriteDebugInfo("Detects argument as ordinal.");
  108. argument = GetArgument(index);
  109. if (argument == null)
  110. throw new Exception(
  111. $"Invalid ordinal argument '{arg}' at position {index} for this command.");
  112. argument.Value.Set(arg);
  113. WriteDebugInfo($"Ordinal argument '{argument.Code}' is set to value '{argument.Value.AsString()}'.");
  114. }
  115. }
  116. }
  117. private Tuple<string, string> ParseNamedArgument(string argument)
  118. {
  119. try
  120. {
  121. var argParts = argument.Split(new[] {Engine.Configuration.CharacterNamedArgumentValueSeparator}, StringSplitOptions.RemoveEmptyEntries);
  122. WriteDebugInfo($"Splitting arg-parts '{argument}' to parts (count={argParts.Length}): {string.Join(",",argParts)}");
  123. var argName = argParts[0].Replace(Engine.Configuration.CharacterNamedArgumentSeparator,string.Empty);
  124. var argValue = argParts.Length > 1 ? argParts[1] : string.Empty;
  125. WriteDebugInfo($"Named argument '{argument}' parsed as name='{argName}'; value='{argValue}'.");
  126. return new Tuple<string, string>(argName, argValue);
  127. }
  128. catch (Exception e)
  129. {
  130. WriteDebugInfo($"Error during parsing named argument '{argument}'.");
  131. throw new Exception($"Argument '{argument}' parsing error.", e);
  132. }
  133. }
  134. private string[] Split(string input, string valueBracket, string[] separators)
  135. {
  136. var result = new List<string>();
  137. var trimedInput = input.Trim();
  138. if (string.IsNullOrEmpty(trimedInput))
  139. return result.ToArray();
  140. var cValueBracked = valueBracket.ToCharArray().First();
  141. var cSeparators = separators.Select(x => x.ToCharArray().First());
  142. var nIndexStart = 0;
  143. var nIndexEnd = 0;
  144. var isInsideBracked = false;
  145. int offset = 0;
  146. for (offset = 0; offset < trimedInput.Length; offset++)
  147. {
  148. var current = trimedInput[offset];
  149. // current | isInside | Result
  150. // 1 1 0
  151. // 1 0 1
  152. // 0 1 1
  153. // 0 0 0
  154. isInsideBracked = current == cValueBracked ^ isInsideBracked;
  155. if (isInsideBracked)
  156. continue;
  157. if (cSeparators.Contains(current))
  158. {
  159. nIndexEnd = offset;
  160. result.Add(trimedInput.Substring(nIndexStart,nIndexEnd - nIndexStart));
  161. nIndexStart = nIndexEnd;
  162. isInsideBracked = false;
  163. }
  164. }
  165. if (offset>nIndexStart)
  166. result.Add(trimedInput.Substring(nIndexStart,offset - nIndexStart));
  167. return result.Where(x => !string.IsNullOrEmpty(x.Trim())).Select(x=>x.Trim()).ToArray();
  168. }
  169. private IEnumerable<AbstractArgument> SetupArguments()
  170. {
  171. return OnSetupArguments();
  172. }
  173. protected void WriteDebugInfo(string text)
  174. {
  175. using (var debug = new DebugConsoleWriter())
  176. {
  177. debug.WriteDebugLine(text);
  178. }
  179. }
  180. protected void WriteInfo(string text)
  181. {
  182. using (var writer = new ConsoleWriter(ConsoleColor.Green))
  183. writer.WriteLine(text);
  184. }
  185. protected void WriteCaption(string text)
  186. {
  187. using (var writer = new ConsoleWriter(ConsoleColor.White))
  188. writer.WriteLine(text);
  189. }
  190. protected void WriteWarning(string text)
  191. {
  192. using (var writer = new ConsoleWriter(ConsoleColor.Yellow))
  193. writer.WriteLine(text);
  194. }
  195. private void ShowContextStatus(ConsoleWriter writer)
  196. {
  197. using (var writerBlue = new ConsoleWriter(ConsoleColor.Blue))
  198. {
  199. using (var writerYellow = new ConsoleWriter(ConsoleColor.Magenta))
  200. {
  201. writer.WriteLine("Engine context:");
  202. writerBlue.Write(_engine.Context.ToString()).Write(" : ");
  203. writerYellow.WriteLine(_engine.Context.Status);
  204. writer.WriteLine("Command context:");
  205. writerBlue.Write(_context.ToString()).Write(" : ");
  206. writerYellow.WriteLine(_context.Status);
  207. }
  208. }
  209. }
  210. #endregion
  211. #region *** Protected virtuals ***
  212. protected virtual void OnCommandInit()
  213. {
  214. WriteDebugInfo("Command called OnCommandInit.");
  215. }
  216. protected virtual IEnumerable<AbstractArgument> OnSetupArguments()
  217. {
  218. return new AbstractArgument[0];
  219. //{
  220. // new FlagArgument(Constants.Arguments.General.DebugMode.Code, Constants.Arguments.General.DebugMode.Description, Constants.Arguments.General.DebugMode.Hint, true),
  221. //};
  222. }
  223. protected abstract Result OnExecute();
  224. protected virtual Result EndExecute(Result incommingResult)
  225. {
  226. return incommingResult;
  227. }
  228. #endregion
  229. #region *** Protected operations ***
  230. protected AbstractArgument GetArgument(string code)
  231. {
  232. if (!_arguments.ContainsKey(code))
  233. throw new Exception($"Argument '{code}' is not defined.");
  234. return _arguments[code];
  235. }
  236. protected AbstractArgument GetArgument(int ordinalPosition)
  237. {
  238. return _arguments.Values.Where(x => x.IsPositional()).Skip(ordinalPosition).FirstOrDefault();
  239. }
  240. protected bool ContainsArgument(string code)
  241. {
  242. return _arguments.ContainsKey(code);
  243. }
  244. protected TContext GetContext<TContext>() where TContext : class, ICommandContext
  245. {
  246. return (TContext)_context;
  247. }
  248. #endregion
  249. }
  250. }