Engine.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Reflection;
  5. using Quadarax.Foundation.Core.Console;
  6. using Quadarax.Foundation.Core.Exceptions;
  7. using Quadarax.Foundation.Core.QConsole.Attributes;
  8. using Quadarax.Foundation.Core.QConsole.Command.Base;
  9. using Quadarax.Foundation.Core.QConsole.Configuration;
  10. using Quadarax.Foundation.Core.QConsole.Context;
  11. using Quadarax.Foundation.Core.Value.Extensions;
  12. namespace Quadarax.Foundation.Core.QConsole
  13. {
  14. public class Engine : IDisposable
  15. {
  16. #region *** Private Fields ***
  17. private bool _isDispoising;
  18. private bool _isExitSignal;
  19. private IList<AbstractCommand> _commands = new List<AbstractCommand>();
  20. private StartupConfiguration _configuration;
  21. private IList<ISelectionEntry> _selections = new List<ISelectionEntry>();
  22. #endregion
  23. #region *** Properties ***
  24. public string[] RawArguments { get; }
  25. public StartupConfiguration Configuration => _configuration;
  26. public AbstractCommand[] Commands => _commands.ToArray();
  27. public ISelectionEntry[] Selections => _selections.ToArray();
  28. public IEngineContext Context { get; }
  29. #endregion
  30. #region *** Constructor ***
  31. public Engine(StartupConfiguration configuration, IEngineContext context, string[] args)
  32. {
  33. _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
  34. Context = context ?? throw new ArgumentNullException(nameof(context));
  35. if (args == null)
  36. args = new string[0];
  37. // correction of arguments (fix " values)
  38. //for (var i = 0; i < args.Length; i++)
  39. //{
  40. // if (args[i].Contains(" ") && args[i].Contains(configuration.CharacterNamedArgumentValueSeparator) ||
  41. // args[i].Contains(configuration.CharacterNamedArgumentSeparator) && args[i].Contains(configuration.CharacterNamedArgumentValueSeparator))
  42. // {
  43. // var parts = args[i].SplitQuoted(configuration.CharacterNamedArgumentValueSeparator,configuration.CharacterTextValueBraceSeparator, removeEmptyItems:true, keepBracket:true);
  44. // if (parts.Length == 2)
  45. // args[i] = parts[0] + configuration.CharacterNamedArgumentValueSeparator + configuration.CharacterTextValueBraceSeparator + parts[1] +
  46. // configuration.CharacterTextValueBraceSeparator;
  47. // else
  48. // args[i] = parts[0] + configuration.CharacterNamedArgumentValueSeparator + configuration.CharacterTextValueBraceSeparator + string.Join(":",parts.Skip(1).Take(parts.Length - 1)) +
  49. // configuration.CharacterTextValueBraceSeparator;
  50. // }
  51. //}
  52. // Ensure if debug mode -> Enable debug mode and remove explicit argument
  53. if (args.Any(x => x.Trim().ToLower() == Constants.Console.DebugModeExplicitArg))
  54. {
  55. configuration.EnableDebugMode();
  56. var newArgs = new List<string>(args);
  57. newArgs.Remove(Constants.Console.DebugModeExplicitArg);
  58. args = newArgs.ToArray();
  59. }
  60. WriteDebugInfo($"Console '{_configuration.ConsoleName}' initialization started.");
  61. WriteDebugInfo($"Engine assembly version '{Assembly.GetExecutingAssembly().GetName().Version}'.");
  62. WriteDebugInfo($"IsInitFromXml is set to '{_configuration.IsInitFromXml}'.");
  63. ShowHeader();
  64. RawArguments = args;
  65. WriteDebugInfo($"Input {args.Length} arguments appended.");
  66. WriteDebugInfo($"Context set: {Context}");
  67. var commandClasses = GetTypesWithHelpAttribute(new CommandDefinitionAttribute());
  68. WriteDebugInfo($"Console found {commandClasses.Count()} command definitions.");
  69. var paramConstrTypes = new Type[] { typeof(Engine) };
  70. var paramConstrValues = new object[] { this };
  71. foreach (var commandClass in commandClasses)
  72. {
  73. var command = Construct<AbstractCommand>(commandClass, paramConstrTypes, paramConstrValues);
  74. _commands.Add(command);
  75. WriteDebugInfo($"Command definition '{command}' added - class '{command.GetType().FullName}', assembly '{command.GetType().Assembly.GetName().Name}'.");
  76. }
  77. WriteDebugInfo("Console initialized.");
  78. }
  79. public void Start()
  80. {
  81. WriteDebugInfo("Console starting.");
  82. var commandLineArguments = string.Join(" ", RawArguments);
  83. using (var writer = new ConsoleWriter(ConsoleColor.White))
  84. {
  85. while (!_isExitSignal)
  86. {
  87. var input = string.Empty;
  88. if (commandLineArguments.Length == 0 && Configuration.AllowInteractive)
  89. {
  90. // interactive command entry
  91. writer.Write(_configuration.CharacterLineIntroduce);
  92. input = System.Console.ReadLine();
  93. }
  94. else
  95. {
  96. // inline command entry
  97. input = commandLineArguments;
  98. _isExitSignal = true;
  99. }
  100. var args = (input ?? String.Empty).Split([" "], StringSplitOptions.RemoveEmptyEntries);
  101. if (args.Length == 0)
  102. {
  103. if (_isExitSignal == true && !Configuration.AllowInteractive &&
  104. Configuration.HandlerNothingToDo != null)
  105. {
  106. Configuration.HandlerNothingToDo.Invoke(this);
  107. }
  108. continue;
  109. }
  110. var command = GetCommand(args[0]);
  111. if (command == null)
  112. {
  113. using (var writerError = new ConsoleWriter(ConsoleColor.Red))
  114. {
  115. writerError.WriteLine($"Unknown command '{args[0]}'.");
  116. if (Configuration.ThrowExceptionWhenNotSuccess)
  117. throw new Exception($"Command '{args[0]}' is not found in the console commands list.");
  118. }
  119. continue;
  120. }
  121. var result = command.Execute((input ?? String.Empty));
  122. if (result.IsSuccess)
  123. {
  124. using (var writerSucc = new ConsoleWriter(ConsoleColor.Green))
  125. {
  126. writerSucc.WriteLine("Done.");
  127. }
  128. }
  129. else
  130. {
  131. using (var writerError = new ConsoleWriter(ConsoleColor.Red))
  132. {
  133. writerError.WriteLine("Error:");
  134. var e = result.ThrownException;
  135. if (Configuration.ThrowExceptionWhenNotSuccess)
  136. throw e.ToAggregateException();
  137. if (e is AggregateException aggr)
  138. {
  139. foreach (var ex in aggr.InnerExceptions)
  140. DumpException(writerError, ex);
  141. }
  142. else
  143. while (e != null)
  144. {
  145. writerError.WriteLine(e.Message);
  146. e = e.InnerException;
  147. }
  148. }
  149. }
  150. }
  151. if (!Configuration.AllowInteractive && Configuration.WaitOnKeyInNonInteractiveMode)
  152. {
  153. WriteDebugInfo("Waiting for the key by configuration settings...");
  154. using (var writerGreen = new ConsoleWriter(ConsoleColor.Magenta))
  155. {
  156. writerGreen.WriteLine("Press any key to close...");
  157. System.Console.ReadKey();
  158. }
  159. }
  160. }
  161. }
  162. public void Stop()
  163. {
  164. _isExitSignal = true;
  165. WriteDebugInfo("Console stopped.");
  166. }
  167. public AbstractCommand? GetCommand(string commandName)
  168. {
  169. return _commands.FirstOrDefault(x => x.Name.ToUpper() == commandName.ToUpper());
  170. }
  171. public void AppendSelection(params ISelectionEntry[] selectionEntries)
  172. {
  173. if (selectionEntries == null)
  174. throw new ArgumentNullException(nameof(selectionEntries));
  175. foreach (var entry in selectionEntries)
  176. {
  177. _selections.Add(entry);
  178. }
  179. WriteDebugInfo($"{selectionEntries.Length} was added to global selections. Total count {_selections.Count} entries.");
  180. }
  181. public void ClearSelection()
  182. {
  183. _selections.Clear();
  184. WriteDebugInfo("Global selections was cleared.");
  185. }
  186. public ISelectionEntry GetSelection(int ordinal)
  187. {
  188. if (ordinal==0)
  189. throw new ArgumentOutOfRangeException(nameof(ordinal), "Ordinal value must be greater than 0.");
  190. if (_selections.Count < ordinal)
  191. throw new ArgumentOutOfRangeException(nameof(ordinal), $"Ordinal value must be less than {_selections.Count}.");
  192. return _selections[ordinal - 1];
  193. }
  194. #endregion
  195. #region *** Public Operations ***
  196. public void Dispose()
  197. {
  198. if (_isDispoising)
  199. return;
  200. _isDispoising = true;
  201. WriteDebugInfo("Dispoising called.");
  202. OnDispoising();
  203. WriteDebugInfo("Dispoising done.");
  204. }
  205. #endregion
  206. #region *** Protected Operations ***
  207. protected virtual void OnDispoising()
  208. {
  209. }
  210. protected virtual void OnShowHeader()
  211. {
  212. var headerText = string.IsNullOrEmpty(_configuration.ConsoleCopyright) ? @"{0} * {1}" : @"{0} * {1} * {2}";
  213. using (var writer = new ConsoleWriter(ConsoleColor.Green))
  214. {
  215. writer.WriteLine(!string.IsNullOrEmpty(_configuration.ConsoleCopyright)
  216. ? string.Format(headerText, _configuration.ConsoleName, _configuration.ConsoleVersion,
  217. _configuration.ConsoleCopyright)
  218. : string.Format(headerText, _configuration.ConsoleName, _configuration.ConsoleVersion));
  219. }
  220. }
  221. #endregion
  222. #region *** Private Operations ***
  223. private static T Construct<T>(Type instance, Type[] paramTypes, object[] paramValues)
  224. {
  225. var ci = instance.GetConstructor(
  226. BindingFlags.Instance | BindingFlags.Public,
  227. null, paramTypes, null);
  228. if (ci == null)
  229. throw new Exception($"Command class '{instance.FullName}' has not public constuctor defined or has bad constructor's format.");
  230. return (T)ci.Invoke(paramValues);
  231. }
  232. private IEnumerable<Type> GetTypesWithHelpAttribute(Attribute attribute)
  233. {
  234. WriteDebugInfo($"Loading command assemblies ...");
  235. var defaultTypes = Assembly.GetExecutingAssembly().GetTypes().Where(type => type.GetCustomAttributes(attribute.GetType(), true).Length > 0).Distinct().ToList();
  236. var customTypes = new List<Type>();
  237. if (_configuration.CommandDefinitionAssembly != null)
  238. {
  239. foreach (var assembly in _configuration.CommandDefinitionAssembly)
  240. {
  241. customTypes.AddRange(assembly.GetTypes().Where(type => type.GetCustomAttributes(attribute.GetType(), true).Length > 0).Distinct().ToArray());
  242. WriteDebugInfo(assembly.GetName().Name + " was scanned for commands.");
  243. }
  244. }
  245. else
  246. {
  247. WriteDebugInfo("Custom CommandDefinitionAssembly is not defined. Skip.");
  248. }
  249. WriteDebugInfo($"Command definition obtaining process found {defaultTypes.Count} default definitions and {customTypes.Count} custom definitions.");
  250. defaultTypes.AddRange(customTypes);
  251. defaultTypes = defaultTypes.Distinct().ToList();
  252. foreach (var disabledCommand in _configuration.DisabledCommands)
  253. {
  254. var disabled = defaultTypes.FirstOrDefault(x => x.Name == disabledCommand);
  255. if (disabled==null)
  256. continue;
  257. defaultTypes.Remove(disabled);
  258. WriteDebugInfo($"Commnad class '{disabled.Name}', fullname '{disabled.FullName}', assembly '{disabled.Assembly.GetName().Name}' was skipped by configuration.");
  259. }
  260. return defaultTypes.ToArray();
  261. }
  262. private void WriteDebugInfo(string text)
  263. {
  264. using (var debug = new DebugConsoleWriter())
  265. {
  266. debug.WriteDebugLine(text);
  267. }
  268. }
  269. private void DumpException(ConsoleWriter writer, Exception? e)
  270. {
  271. while (e != null)
  272. {
  273. writer.WriteLine("- " + e.Message);
  274. e = e.InnerException;
  275. }
  276. }
  277. private void ShowHeader()
  278. {
  279. OnShowHeader();
  280. }
  281. #endregion
  282. }
  283. }