|
|
@@ -0,0 +1,393 @@
|
|
|
+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<string, AbstractArgument> _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<string, AbstractArgument>();
|
|
|
+ _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<string, string> 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<string, string>(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<string>();
|
|
|
+ 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<AbstractArgument> 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<AbstractArgument> 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<Exception>();
|
|
|
+ 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<TValue>(string code, TValue defalutValue)
|
|
|
+ {
|
|
|
+ if (!ContainsArgument(code)) return defalutValue;
|
|
|
+ return (TValue) GetArgumentValueOrDefault<TValue>(code);
|
|
|
+ }
|
|
|
+ protected TValue GetArgumentValueOrDefault<TValue>(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<string>(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<TEnum>(string argumentName) where TEnum : Enum
|
|
|
+ {
|
|
|
+ ValidateArgumentValueEnum(argumentName, typeof(TEnum));
|
|
|
+ }
|
|
|
+ protected bool ContainsArgument(string code)
|
|
|
+ {
|
|
|
+ return _arguments.ContainsKey(code);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected TContext GetContext<TContext>() where TContext : class, ICommandContext
|
|
|
+ {
|
|
|
+ return (TContext)_context;
|
|
|
+ }
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ }
|
|
|
+}
|