using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Quadarax.Foundation.Core.Console; using Quadarax.Foundation.Core.QConsole.Attributes; using Quadarax.Foundation.Core.QConsole.Command.Base; using Quadarax.Foundation.Core.QConsole.Configuration; using Quadarax.Foundation.Core.QConsole.Context; namespace Quadarax.Foundation.Core.QConsole { public class Engine : IDisposable { #region *** Private Fields *** private bool _isDispoising; private bool _isExitSignal; private IList _commands = new List(); private StartupConfiguration _configuration; private IList _selections = new List(); #endregion #region *** Properties *** public string[] RawArguments { get; } public StartupConfiguration Configuration => _configuration; public AbstractCommand[] Commands => _commands.ToArray(); public ISelectionEntry[] Selections => _selections.ToArray(); public IEngineContext Context { get; } #endregion #region *** Constructor *** public Engine(StartupConfiguration configuration, IEngineContext context, string[] args) { _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); Context = context ?? throw new ArgumentNullException(nameof(context)); if (args == null) args = new string[0]; // correction of arguments (fix " values) for (var i = 0; i < args.Length; i++) { if (args[i].Contains(" ") && args[i].Contains(configuration.CharacterNamedArgumentValueSeparator) || args[i].Contains(configuration.CharacterNamedArgumentSeparator) && args[i].Contains(configuration.CharacterNamedArgumentValueSeparator)) { var parts = args[i].Split(new[] {configuration.CharacterNamedArgumentValueSeparator}, StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 2) args[i] = parts[0] + configuration.CharacterNamedArgumentValueSeparator + configuration.CharacterTextValueBraceSeparator + parts[1] + configuration.CharacterTextValueBraceSeparator; else args[i] = parts[0] + configuration.CharacterNamedArgumentValueSeparator + configuration.CharacterTextValueBraceSeparator + string.Join(":",parts.Skip(1).Take(parts.Length - 1)) + configuration.CharacterTextValueBraceSeparator; } } // Ensure if debug mode -> Enable debug mode and remove explicit argument if (args.Any(x => x.Trim().ToLower() == Constants.Console.DebugModeExplicitArg)) { configuration.EnableDebugMode(); var newArgs = new List(args); newArgs.Remove(Constants.Console.DebugModeExplicitArg); args = newArgs.ToArray(); } WriteDebugInfo($"Console '{_configuration.ConsoleName}' initialization started."); WriteDebugInfo($"Engine assembly version '{Assembly.GetExecutingAssembly().GetName().Version}'."); WriteDebugInfo($"IsInitFromXml is set to '{_configuration.IsInitFromXml}'."); ShowHeader(); RawArguments = args; WriteDebugInfo($"Input {args.Length} arguments appended."); WriteDebugInfo($"Context set: {Context}"); var commandClasses = GetTypesWithHelpAttribute(new CommandDefinitionAttribute()); WriteDebugInfo($"Console found {commandClasses.Count()} command definitions."); var paramConstrTypes = new Type[] { typeof(Engine) }; var paramConstrValues = new object[] { this }; foreach (var commandClass in commandClasses) { var command = Construct(commandClass, paramConstrTypes, paramConstrValues); _commands.Add(command); WriteDebugInfo($"Command definition '{command}' added - class '{command.GetType().FullName}', assembly '{command.GetType().Assembly.GetName().Name}'."); } WriteDebugInfo("Console initialized."); } public void Start() { WriteDebugInfo("Console starting."); var commandLineArguments = string.Join(" ", RawArguments); using (var writer = new ConsoleWriter(ConsoleColor.White)) { while (!_isExitSignal) { var input = string.Empty; if (commandLineArguments.Length == 0 && Configuration.AllowInteractive) { // interactive command entry writer.Write(_configuration.CharacterLineIntroduce); input = System.Console.ReadLine(); } else { // inline command entry input = commandLineArguments; _isExitSignal = true; } var args = input.Split(new[] {" "}, StringSplitOptions.RemoveEmptyEntries); if (args.Length == 0) { if (_isExitSignal == true && !Configuration.AllowInteractive && Configuration.HandlerNothingToDo != null) { Configuration.HandlerNothingToDo.Invoke(this); } continue; } var command = GetCommand(args[0]); if (command == null) { using (var writerError = new ConsoleWriter(ConsoleColor.Red)) { writerError.WriteLine($"Unknown command '{args[0]}'."); } continue; } var result = command.Execute(input); if (result.IsSuccess) { using (var writerSucc = new ConsoleWriter(ConsoleColor.Green)) { writerSucc.WriteLine("Done."); } } else { using (var writerError = new ConsoleWriter(ConsoleColor.Red)) { writerError.WriteLine("Error:"); var e = result.ThrownException; if (e is AggregateException aggr) { foreach (var ex in aggr.InnerExceptions) DumpException(writerError, ex); } else while (e != null) { writerError.WriteLine(e.Message); e = e.InnerException; } } } } if (!Configuration.AllowInteractive && Configuration.WaitOnKeyInNonInteractiveMode) { WriteDebugInfo("Waiting for the key by configuration settings..."); using (var writerGreen = new ConsoleWriter(ConsoleColor.Magenta)) { writerGreen.WriteLine("Press any key to close..."); System.Console.ReadKey(); } } } } public void Stop() { _isExitSignal = true; WriteDebugInfo("Console stopped."); } public AbstractCommand GetCommand(string commandName) { return _commands.FirstOrDefault(x => x.Name.ToUpper() == commandName.ToUpper()); } public void AppendSelection(params ISelectionEntry[] selectionEntries) { if (selectionEntries == null) throw new ArgumentNullException(nameof(selectionEntries)); foreach (var entry in selectionEntries) { _selections.Add(entry); } WriteDebugInfo($"{selectionEntries.Length} was added to global selections. Total count {_selections.Count} entries."); } public void ClearSelection() { _selections.Clear(); WriteDebugInfo("Global selections was cleared."); } public ISelectionEntry GetSelection(int ordinal) { if (ordinal==0) throw new ArgumentOutOfRangeException(nameof(ordinal), "Ordinal value must be greater than 0."); if (_selections.Count < ordinal) throw new ArgumentOutOfRangeException(nameof(ordinal), $"Ordinal value must be less than {_selections.Count}."); return _selections[ordinal - 1]; } #endregion #region *** Public Operations *** public void Dispose() { if (_isDispoising) return; _isDispoising = true; WriteDebugInfo("Dispoising called."); OnDispoising(); WriteDebugInfo("Dispoising done."); } #endregion #region *** Protected Operations *** protected virtual void OnDispoising() { } protected virtual void OnShowHeader() { var headerText = string.IsNullOrEmpty(_configuration.ConsoleCopyright) ? @"{0} * {1}" : @"{0} * {1} * {2}"; using (var writer = new ConsoleWriter(ConsoleColor.Green)) { writer.WriteLine(!string.IsNullOrEmpty(_configuration.ConsoleCopyright) ? string.Format(headerText, _configuration.ConsoleName, _configuration.ConsoleVersion, _configuration.ConsoleCopyright) : string.Format(headerText, _configuration.ConsoleName, _configuration.ConsoleVersion)); } } #endregion #region *** Private Operations *** private static T Construct(Type instance, Type[] paramTypes, object[] paramValues) { var ci = instance.GetConstructor( BindingFlags.Instance | BindingFlags.Public, null, paramTypes, null); if (ci == null) throw new Exception($"Command class '{instance.FullName}' has not public constuctor defined or has bad constructor's format."); return (T)ci.Invoke(paramValues); } private IEnumerable GetTypesWithHelpAttribute(Attribute attribute) { WriteDebugInfo($"Loading command assemblies ..."); var defaultTypes = Assembly.GetExecutingAssembly().GetTypes().Where(type => type.GetCustomAttributes(attribute.GetType(), true).Length > 0).Distinct().ToList(); var customTypes = new List(); if (_configuration.CommandDefinitionAssebmly != null) { foreach (var assembly in _configuration.CommandDefinitionAssebmly) { customTypes.AddRange(assembly.GetTypes().Where(type => type.GetCustomAttributes(attribute.GetType(), true).Length > 0).Distinct().ToArray()); WriteDebugInfo(assembly.GetName().Name + " was scanned for commands."); } } else { WriteDebugInfo("Custom CommandDefinitionAssembly is not defined. Skip."); } WriteDebugInfo($"Command definition obtaining process found {defaultTypes.Count} default definitions and {customTypes.Count} custom definitions."); defaultTypes.AddRange(customTypes); defaultTypes = defaultTypes.Distinct().ToList(); foreach (var disabledCommand in _configuration.DisabledCommands) { var disabled = defaultTypes.FirstOrDefault(x => x.Name == disabledCommand); if (disabled==null) continue; defaultTypes.Remove(disabled); WriteDebugInfo($"Commnad class '{disabled.Name}', fullname '{disabled.FullName}', assembly '{disabled.Assembly.GetName().Name}' was skipped by configuration."); } return defaultTypes.ToArray(); } private void WriteDebugInfo(string text) { using (var debug = new DebugConsoleWriter()) { debug.WriteDebugLine(text); } } private void DumpException(ConsoleWriter writer, Exception e) { while (e != null) { writer.WriteLine("- " + e.Message); e = e.InnerException; } } private void ShowHeader() { OnShowHeader(); } #endregion } }