using System; using System.Collections.Generic; using System.Linq; using System.Text; using Quadarax.Foundation.Core.Console; using Quadarax.Foundation.Core.QConsole.Argument; using Quadarax.Foundation.Core.QConsole.Context; using Quadarax.Foundation.Core.QConsole.Extensions; using Quadarax.Foundation.Core.Value; namespace Quadarax.Foundation.Core.QConsole.Command.Base { public abstract class AbstractCommand { #region *** Enumerations *** public enum WriteTextSeverity { Info, Caption, Warning, Error, } #endregion #region *** Private Fields *** private Engine _engine; private IDictionary _arguments; protected ICommandContext _context; #endregion #region *** Properties *** public abstract string Name { get; } public abstract string Description { get; } public virtual string Notes { get; set; } public virtual string Examples { get; set; } public virtual string CustomUsage { get; set; } public AbstractArgument[] Arguments => _arguments.Values.ToArray(); protected Engine Engine => _engine; #endregion #region *** Constructor *** public AbstractCommand(Engine engine) { _engine = engine ?? throw new ArgumentNullException(nameof(engine)); _arguments = new Dictionary(); _context = _engine.Context.CreateCommandContext(); WriteDebugInfo($"Context for command '{Name}' created: {_context}"); var args = SetupArguments(); foreach (var arg in args) _arguments.Add(arg.Code, arg); Init(); WriteDebugInfo($"Command '{Name}' has {_arguments.Count} arguments. Initialized."); } #endregion #region *** Public operations *** public Result Execute(string input) { ConsoleWriter writer = null; try { writer = new ConsoleWriter(ConsoleColor.Green); foreach (var arg in Arguments) arg.SetValueAsDefault(); ParseInput(input); OnInitialize(); if (_engine.Configuration.ShowStatusOnEveryCommand) ShowContextStatus(writer); OnValidateArguments(); BeginExecute(); var result = OnExecute(); result = EndExecute(result); return result; } catch (Exception e) { return new Result(e); } finally { writer?.Dispose(); } } public override string ToString() { return "Command: " + Name; } #endregion #region *** Private operations *** private void Init() { OnCommandInit(); } private void ParseInput(string input) { //var args = input.Split(new[] {Engine.Configuration.CharacterNamedArgumentSeparator}, StringSplitOptions.RemoveEmptyEntries); var args = Split(input, Engine.Configuration.CharacterTextValueBraceSeparator, new[] { Engine.Configuration.CharacterNamedArgumentSeparator, Engine.Configuration.CharacterOrdinalArgumentSeparator }); for (int i = 1; i < args.Length; i++) { var arg = args[i]; var index = i - 1; WriteDebugInfo($"Parsing argument [index={index}] by value string '{arg}' in progress.."); AbstractArgument argument = null; if (arg.StartsWith(Engine.Configuration.CharacterNamedArgumentSeparator)) { //Named WriteDebugInfo("Detects argument as named."); var parsed = ParseNamedArgument(arg); if (!ContainsArgument(parsed.Item1)) throw new Exception( $"Invalid argument '{Engine.Configuration.CharacterNamedArgumentSeparator}{parsed.Item1}' for this command."); argument = GetArgument(parsed.Item1); if (argument.IsPositional()) if (!((OrdinalArgument)argument).IsCodeAllowed) throw new Exception($"Usage of ordinal argument '{argument.Code}' as named argument is not allowed!"); if (argument.IsFlag()) ((FlagArgument)argument).Set(); else { argument.Value.Set(parsed.Item2); argument.Hit(); } } else { //Ordinal WriteDebugInfo("Detects argument as ordinal."); argument = GetArgument(index); if (argument == null) throw new Exception( $"Invalid ordinal argument '{arg}' at position {index} for this command."); argument.Value.Set(arg); argument.Hit(); WriteDebugInfo($"Ordinal argument '{argument.Code}' is set to value '{argument.Value.AsString()}'."); } } } private Tuple ParseNamedArgument(string argument) { try { var argParts = argument.Split(new[] {Engine.Configuration.CharacterNamedArgumentValueSeparator}, StringSplitOptions.RemoveEmptyEntries); WriteDebugInfo($"Splitting arg-parts '{argument}' to parts (count={argParts.Length}): {string.Join(",",argParts)}"); var argName = argParts[0].Replace(Engine.Configuration.CharacterNamedArgumentSeparator,string.Empty); var argValue = argParts.Length > 1 ? string.Join(Engine.Configuration.CharacterNamedArgumentValueSeparator,argParts.Skip(1).Take(argParts.Length - 1)).Replace(_engine.Configuration.CharacterTextValueBraceSeparator,string.Empty) : string.Empty; WriteDebugInfo($"Named argument '{argument}' parsed as name='{argName}'; value='{argValue}'."); return new Tuple(argName, argValue); } catch (Exception e) { WriteDebugInfo($"Error during parsing named argument '{argument}'."); throw new Exception($"Argument '{argument}' parsing error.", e); } } private string[] Split(string input, string valueBracket, string[] separators) { var result = new List(); var trimedInput = input.Trim(); if (string.IsNullOrEmpty(trimedInput)) return result.ToArray(); var cValueBracked = valueBracket.ToCharArray().First(); var cSeparators = separators.Select(x => x.ToCharArray().First()); var nIndexStart = 0; var nIndexEnd = 0; var isInsideBracked = false; int offset = 0; for (offset = 0; offset < trimedInput.Length; offset++) { var current = trimedInput[offset]; // current | isInside | Result // 1 1 0 // 1 0 1 // 0 1 1 // 0 0 0 isInsideBracked = current == cValueBracked ^ isInsideBracked; if (isInsideBracked) continue; if (cSeparators.Contains(current)) { nIndexEnd = offset; result.Add(trimedInput.Substring(nIndexStart,nIndexEnd - nIndexStart)); nIndexStart = nIndexEnd; isInsideBracked = false; } } if (offset>nIndexStart) result.Add(trimedInput.Substring(nIndexStart,offset - nIndexStart)); return result.Where(x => !string.IsNullOrEmpty(x.Trim())).Select(x=>x.Trim()).ToArray(); } private IEnumerable SetupArguments() { return OnSetupArguments(); } protected void WriteDebugInfo(string text) { using (var debug = new DebugConsoleWriter()) { debug.WriteDebugLine(text); } } protected void WriteInfo(string text) { using (var writer = new ConsoleWriter(ConsoleColor.Green)) WriteLine(writer,WriteTextSeverity.Info, text); } protected void WriteCaption(string text) { using (var writer = new ConsoleWriter(ConsoleColor.White)) WriteLine(writer,WriteTextSeverity.Caption, text); } protected void WriteWarning(string text) { using (var writer = new ConsoleWriter(ConsoleColor.Yellow)) WriteLine(writer,WriteTextSeverity.Warning, text); } protected void WriteError(string text) { using (var writer = new ConsoleWriter(ConsoleColor.Red)) WriteLine(writer, WriteTextSeverity.Error, text); } protected void WriteError(Exception e) { if (e is AggregateException aggr) { foreach (var ex in aggr.InnerExceptions) DumpException(ex, "- "); } else DumpException(e, string.Empty); } private void DumpException(Exception e, string prefix) { while (e != null) { WriteError(prefix + e.Message); e = e.InnerException; } } protected virtual void WriteLine(ConsoleWriter writer, WriteTextSeverity severity, string text) { writer.WriteLine(text); } private void ShowContextStatus(ConsoleWriter writer) { using (var writerBlue = new ConsoleWriter(ConsoleColor.Blue)) { using (var writerYellow = new ConsoleWriter(ConsoleColor.Magenta)) { writer.WriteLine("Engine context:"); writerBlue.Write(_engine.Context.ToString()).Write(" : "); writerYellow.WriteLine(_engine.Context.Status); writer.WriteLine("Command context:"); writerBlue.Write(_context.ToString()).Write(" : "); writerYellow.WriteLine(_context.Status); } } } #endregion #region *** Protected virtuals *** protected virtual void OnCommandInit() { WriteDebugInfo("Command called OnCommandInit."); } protected virtual IEnumerable OnSetupArguments() { return new AbstractArgument[0]; //{ // new FlagArgument(Constants.Arguments.General.DebugMode.Code, Constants.Arguments.General.DebugMode.Description, Constants.Arguments.General.DebugMode.Hint, true), //}; } protected abstract Result OnExecute(); protected virtual void OnValidateArguments() { var ex = new List(); foreach (var arg in _arguments.Where(x=>x.Value.IsMandatory).Select(x=>x.Value)) { if (!arg.Value.HasValue) ex.Add(new Exception($"Argument '{arg.Code}' is mandatory!")); } if (ex.Count > 0) throw new AggregateException(ex); } protected virtual void OnInitialize() { } protected virtual void BeginExecute() { } protected virtual Result EndExecute(Result incommingResult) { return incommingResult; } #endregion #region *** Protected operations *** protected AbstractArgument GetArgument(string code) { if (!_arguments.ContainsKey(code)) throw new Exception($"Argument '{code}' is not defined."); return _arguments[code]; } protected AbstractArgument GetArgument(int ordinalPosition) { return _arguments.Values.Where(x => x.IsPositional()).Skip(ordinalPosition).FirstOrDefault(); } protected TValue GetArgumentValueOrDefault(string code, TValue defalutValue) { if (!ContainsArgument(code)) return defalutValue; return (TValue) GetArgumentValueOrDefault(code); } protected TValue GetArgumentValueOrDefault(string code) { if (!ContainsArgument(code)) throw new ArgumentOutOfRangeException(code, $"Argument '{code}' not defined."); var arg = GetArgument(code); var value = arg.Value.Get() ?? arg.DefaultValue.Get(); if (typeof(TValue).IsEnum) return (TValue)Enum.Parse(typeof(TValue), value?.ToString(),true); return (TValue) value; } protected bool WasArgumentSpecified(string code) { if (!ContainsArgument(code)) throw new ArgumentOutOfRangeException(code, $"Argument '{code}' not defined."); var arg = GetArgument(code); return arg.Value.HasValue && arg.WasUsed; } protected void ValidateArgumentValueEnum(string argumentName, Type enumType) { var argVal = GetArgumentValueOrDefault(argumentName); if (!Enum.GetNames(enumType).Select(x => x.ToLower()).Contains(argVal.ToLower())) throw new ArgumentOutOfRangeException( $"Argument '{argumentName}' value must be set as following: {string.Join(",", Enum.GetNames(enumType))}"); } protected void ValidateArgumentValueEnum(string argumentName) where TEnum : Enum { ValidateArgumentValueEnum(argumentName, typeof(TEnum)); } protected bool ContainsArgument(string code) { return _arguments.ContainsKey(code); } protected TContext GetContext() where TContext : class, ICommandContext { return (TContext)_context; } #endregion } }