From 4398ae6ad0b609a1e7299b37fa4cfe91e363fb34 Mon Sep 17 00:00:00 2001 From: Craig_Oates Date: Fri, 22 Sep 2017 22:17:32 +0100 Subject: [PATCH] Functionality built. The solution now has a working version of the class library on the console program. The console program is now setup, as well. I have not implemented the Help section properly in the console program yet. But, it is wired up and awaiting the addition of the attributes to the command-methods. --- Console.Waterworks/CW_Console/App.config | 6 +- .../CW_Console/CW_Console.csproj | 10 +- .../CW_Console/ConsoleCommands.cs | 36 ++++ Console.Waterworks/CW_Console/Program.cs | 5 +- Console.Waterworks/Console.Waterworks.sln | 6 + .../Assistants/CoOrdinatorAssistant.cs | 46 ++++- .../Assistants/CommandsAssistant.cs | 99 +++++++++- .../Attributes/DescriptionAttribute.cs | 13 +- .../Attributes/ListCommandAttribute.cs | 16 +- .../Attributes/ParametersAttribute.cs | 12 +- .../Attributes/UsageAttribute.cs | 12 +- .../Console.Waterworks/CWLiaision.cs | 12 -- .../Console.Waterworks/CW_Liaison.cs | 23 +++ .../CoOrdinators/CoOrdinator.cs | 94 +++++++++- .../Console.Waterworks.csproj | 26 ++- .../Constants/CW_Constants.cs | 19 +- .../Console.Waterworks/Loggers/CW_Logger.cs | 10 +- .../Console.Waterworks/Models/Command.cs | 68 ++++++- .../Specialists/CoercionSpecialist.cs | 172 +++++++++++++++++- .../Specialists/CommandsSpecialist.cs | 52 +++++- .../Specialists/ConsoleIOSpecialist.cs | 61 ++++++- .../Specialists/HelpSpecialist.cs | 59 +++++- .../Specialists/ProgramInfoSpecialist.cs | 66 ++++++- 23 files changed, 830 insertions(+), 93 deletions(-) create mode 100644 Console.Waterworks/CW_Console/ConsoleCommands.cs delete mode 100644 Console.Waterworks/Console.Waterworks/CWLiaision.cs create mode 100644 Console.Waterworks/Console.Waterworks/CW_Liaison.cs diff --git a/Console.Waterworks/CW_Console/App.config b/Console.Waterworks/CW_Console/App.config index 731f6de..9d2c7ad 100644 --- a/Console.Waterworks/CW_Console/App.config +++ b/Console.Waterworks/CW_Console/App.config @@ -1,6 +1,6 @@ - + - + - \ No newline at end of file + diff --git a/Console.Waterworks/CW_Console/CW_Console.csproj b/Console.Waterworks/CW_Console/CW_Console.csproj index 6a83257..25c60e4 100644 --- a/Console.Waterworks/CW_Console/CW_Console.csproj +++ b/Console.Waterworks/CW_Console/CW_Console.csproj @@ -8,9 +8,10 @@ Exe CW_Console CW_Console - v4.6.1 + v4.7 512 true + AnyCPU @@ -42,11 +43,18 @@ + + + + {d1354760-61f7-4aee-8d6f-e78a463ee3c9} + Console.Waterworks + + \ No newline at end of file diff --git a/Console.Waterworks/CW_Console/ConsoleCommands.cs b/Console.Waterworks/CW_Console/ConsoleCommands.cs new file mode 100644 index 0000000..240b1b2 --- /dev/null +++ b/Console.Waterworks/CW_Console/ConsoleCommands.cs @@ -0,0 +1,36 @@ +using Console.Waterworks; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CW_Console +{ + public static class ConsoleCommands + { + public static string Help() + { + CW_Liaison liaison = new CW_Liaison(); + return liaison.RequestHelpDocumentation("CW_Console"); + } + + public static string Command1() => "Command1 has completed"; + public static string Command2(string input) => $"Command2 has completed... {input} was entered"; + public static string Command3(int input) => $"Command3 has completed... The number {input} was entered"; + public static string Command4(int int1, int int2) => $"Command4 has completed... {int1} and {int2} was entered and make {int1 + int2} when added together"; + public static string Command5(int int1, double double1) => $"Command5 has completed... {int1} and {double1} was entered and make {int1 + double1} when added together"; + public static void Quit() => Environment.Exit(-1); + + #region Alias Methods + // These methods are shorthand versions of the ones above. + // These are useful for experienced users of your console program. + public static string c1() => Command1(); + public static string c2(string input) => Command2(input); + public static string c3(int input) => Command3(input); + public static string c4(int int1, int int2) => Command4(int1, int2); + public static string c5(int int1, double double1) => Command5(int1, double1); + public static void quit() => Quit(); + #endregion + } +} diff --git a/Console.Waterworks/CW_Console/Program.cs b/Console.Waterworks/CW_Console/Program.cs index 7834e14..9548340 100644 --- a/Console.Waterworks/CW_Console/Program.cs +++ b/Console.Waterworks/CW_Console/Program.cs @@ -1,4 +1,5 @@ -using System; +using Console.Waterworks; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -10,6 +11,8 @@ namespace CW_Console { static void Main(string[] args) { + CW_Liaison bob = new CW_Liaison(); + bob.Run("CW_Console", true); } } } diff --git a/Console.Waterworks/Console.Waterworks.sln b/Console.Waterworks/Console.Waterworks.sln index 239ba98..dd94c8a 100644 --- a/Console.Waterworks/Console.Waterworks.sln +++ b/Console.Waterworks/Console.Waterworks.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 15.0.26730.16 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Console.Waterworks", "Console.Waterworks\Console.Waterworks.csproj", "{D1354760-61F7-4AEE-8D6F-E78A463EE3C9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CW_Console", "CW_Console\CW_Console.csproj", "{E26D7001-2A4E-4618-8C27-8BF504993EE9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {D1354760-61F7-4AEE-8D6F-E78A463EE3C9}.Debug|Any CPU.Build.0 = Debug|Any CPU {D1354760-61F7-4AEE-8D6F-E78A463EE3C9}.Release|Any CPU.ActiveCfg = Release|Any CPU {D1354760-61F7-4AEE-8D6F-E78A463EE3C9}.Release|Any CPU.Build.0 = Release|Any CPU + {E26D7001-2A4E-4618-8C27-8BF504993EE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E26D7001-2A4E-4618-8C27-8BF504993EE9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E26D7001-2A4E-4618-8C27-8BF504993EE9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E26D7001-2A4E-4618-8C27-8BF504993EE9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Console.Waterworks/Console.Waterworks/Assistants/CoOrdinatorAssistant.cs b/Console.Waterworks/Console.Waterworks/Assistants/CoOrdinatorAssistant.cs index 17a553f..398e6a1 100644 --- a/Console.Waterworks/Console.Waterworks/Assistants/CoOrdinatorAssistant.cs +++ b/Console.Waterworks/Console.Waterworks/Assistants/CoOrdinatorAssistant.cs @@ -1,12 +1,52 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Console.Waterworks.Loggers; +using Console.Waterworks.Specialists; +using System.Reflection; +using Console.Waterworks.Constants; namespace Console.Waterworks.Assistants { class CoOrdinatorAssistant { + internal void SetConsoleTitle(CW_Logger logger, ProgramInfoSpecialist progInfoSpec, ConsoleIOSpecialist consoleSpec) + { + logger.LogInfoMessage("Attempting to set console title.."); + var title = progInfoSpec.GetProductName(); + if (!string.IsNullOrEmpty(title)) + { + consoleSpec.SetConsoleTitle(title); + logger.LogSuccessMessage("Console title is now set"); + } + else + logger.LogErrorMessage("Unable to determine the program's title. Check the manifest file to make sure it has been set"); + } + + internal void OutputProgramInfo(CW_Logger logger, ProgramInfoSpecialist progInfoSpec, ConsoleIOSpecialist consoleSpec) + { + logger.LogInfoMessage("Attempting to retrieve program information.."); + List programInfo = progInfoSpec.GatherProgramInfomation(); + logger.LogInfoMessage($"Found {programInfo.Count} out of {CW_Constants.PROGRAM_INFO_PROPERTIES_COUNT} properties"); + logger.LogInfoMessage($"Writing program properties to console.."); + consoleSpec.WriteProgramInfo(programInfo); + } + + + internal void LogCommandGatheringAttempt(List commandClasses, Dictionary>> commandLibraries, CW_Logger logger) + { + logger.LogInfoMessage($"Found {commandClasses.Count} command class(es).."); + logger.LogInfoMessage($"Found {GetFoundCommandsTotal(commandLibraries)} command(s).."); + logger.LogInfoMessage($"Found [{CW_Constants.COMMAND_CLASS_NAME}] class: { commandLibraries.ContainsKey(CW_Constants.COMMAND_CLASS_NAME)}"); + logger.LogNoteMessage($"WaterWorks is only looking for the methods in the [{CW_Constants.COMMAND_CLASS_NAME}] class in the specified namespace. Everything else will be ignored"); + logger.LogInfoMessage("Program is now initialised and awaiting input from the user. You are welcome ;-)"); + } + + internal int GetFoundCommandsTotal(Dictionary>> commandLibraries) + { + int total = 0; + foreach (var currentClass in commandLibraries.Values) + total += currentClass.Count; + return total; + } } } diff --git a/Console.Waterworks/Console.Waterworks/Assistants/CommandsAssistant.cs b/Console.Waterworks/Console.Waterworks/Assistants/CommandsAssistant.cs index a8b3c41..cfa76cf 100644 --- a/Console.Waterworks/Console.Waterworks/Assistants/CommandsAssistant.cs +++ b/Console.Waterworks/Console.Waterworks/Assistants/CommandsAssistant.cs @@ -1,12 +1,105 @@ -using System; +using Console.Waterworks.Models; +using Console.Waterworks.Specialists; +using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Reflection; namespace Console.Waterworks.Assistants { class CommandsAssistant { + internal bool ValidateClass(Command command, Dictionary>> commandLibraries) + => (commandLibraries.ContainsKey(command.ClassName)) ? true : false; + + internal bool ValidateCommand(Command command, Dictionary>> commandLibraries) + { + var methodDict = commandLibraries[command.ClassName]; + return (methodDict.ContainsKey(command.Name)) ? true : false; + } + + internal string ExecuteBadCommandProcedure(Command command, ConsoleIOSpecialist consoleSpec) + { + consoleSpec.WriteErrorSuffix(); + return $"The command \'{command.Name}\' is not recognised."; + } + + internal bool ValidateParamArguments(Command command, List paramInfoList) + { + var requiredParams = paramInfoList.Where(p => p.IsOptional == false); + var optionalParams = paramInfoList.Where(p => p.IsOptional == true); + int requiredCount = requiredParams.Count(); + int optionalCount = optionalParams.Count(); + int providedCount = command.Arguments.Count(); + return (requiredCount > providedCount) ? false : true; + } + + internal string ExecuteMissingArgumentProcedure(Command command, List paramInfoList, ConsoleIOSpecialist consoleSpec) + { + var requiredParams = paramInfoList.Where(p => p.IsOptional == false); + var optionalParams = paramInfoList.Where(p => p.IsOptional == true); + int requiredCount = requiredParams.Count(); + int optionalCount = optionalParams.Count(); + int providedCount = command.Arguments.Count(); + return $"Missing required argument. {requiredCount} required, {optionalCount} optional, {providedCount} provided."; + } + + internal List GetParametreValueList(Command command, List paramInfoList) + { + var methodParameterValueList = new List(); + if (paramInfoList.Count() > 0) + { + foreach (var param in paramInfoList) + { + methodParameterValueList.Add(param.DefaultValue); + } + for (int i = 0; i < command.Arguments.Count(); i++) + { + var methodParam = paramInfoList.ElementAt(i); + var typeRequired = methodParam.ParameterType; + object value = null; + try + { + value = CoercionSpecialist.CoerceArgument(typeRequired, command.Arguments.ElementAt(i)); + methodParameterValueList.RemoveAt(i); + methodParameterValueList.Insert(i, value); + } + catch (ArgumentException ex) + { + string message = $"The value passed for argument '{methodParam.Name}' cannot be parsed to type '{typeRequired.Name}'"; // The exception message is being used instead, for now. + throw new ArgumentException(ex.Message); + } + } + } + return methodParameterValueList; + } + + internal Type BuildCommandLibraryClass(Command command, string commandsNamespace) + { + Assembly programAssembly = Assembly.GetEntryAssembly(); + Type commandLibraryClass = programAssembly.GetType($"{commandsNamespace}.{command.ClassName}"); + return commandLibraryClass; + } + + internal string InvokeCommand(Command command, Type typeInfo, object[] inputArguments) + { + try + { + var result = typeInfo.InvokeMember(command.Name, BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public, null, null, inputArguments); + return result.ToString(); + } + catch (TargetInvocationException ex) + { + throw ex.InnerException; + } + } + + internal object[] GetInputArguments(List methodParametreValueList) + { + object[] inputArguments = null; + if (methodParametreValueList.Count > 0) + inputArguments = methodParametreValueList.ToArray(); + return inputArguments; + } } } diff --git a/Console.Waterworks/Console.Waterworks/Attributes/DescriptionAttribute.cs b/Console.Waterworks/Console.Waterworks/Attributes/DescriptionAttribute.cs index 7751d06..54dbbee 100644 --- a/Console.Waterworks/Console.Waterworks/Attributes/DescriptionAttribute.cs +++ b/Console.Waterworks/Console.Waterworks/Attributes/DescriptionAttribute.cs @@ -1,12 +1,15 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Console.Waterworks.Attributes { - class DescriptionAttribute + [AttributeUsage(AttributeTargets.All)] + public class DescriptionAttribute : Attribute { + public readonly string Description; + + public DescriptionAttribute(string description) + { + Description = description; + } } } diff --git a/Console.Waterworks/Console.Waterworks/Attributes/ListCommandAttribute.cs b/Console.Waterworks/Console.Waterworks/Attributes/ListCommandAttribute.cs index 12a5844..edc452b 100644 --- a/Console.Waterworks/Console.Waterworks/Attributes/ListCommandAttribute.cs +++ b/Console.Waterworks/Console.Waterworks/Attributes/ListCommandAttribute.cs @@ -1,12 +1,18 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Console.Waterworks.Attributes { - class ListCommandAttribute + [AttributeUsage(AttributeTargets.All)] + public class ListCommandAttribute : Attribute { + public readonly bool ShowCommand; + public ListCommandAttribute() + { + ShowCommand = true; + } + public ListCommandAttribute(bool show) + { + ShowCommand = show; + } } } diff --git a/Console.Waterworks/Console.Waterworks/Attributes/ParametersAttribute.cs b/Console.Waterworks/Console.Waterworks/Attributes/ParametersAttribute.cs index 87e39d4..994cea6 100644 --- a/Console.Waterworks/Console.Waterworks/Attributes/ParametersAttribute.cs +++ b/Console.Waterworks/Console.Waterworks/Attributes/ParametersAttribute.cs @@ -1,12 +1,14 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Console.Waterworks.Attributes { - class ParametersAttribute + [AttributeUsage(AttributeTargets.All)] + public class ParametresAttribute : Attribute { + public readonly string Parametres; + public ParametresAttribute(string arguments) + { + Parametres = arguments; + } } } diff --git a/Console.Waterworks/Console.Waterworks/Attributes/UsageAttribute.cs b/Console.Waterworks/Console.Waterworks/Attributes/UsageAttribute.cs index 0586f29..d77ff68 100644 --- a/Console.Waterworks/Console.Waterworks/Attributes/UsageAttribute.cs +++ b/Console.Waterworks/Console.Waterworks/Attributes/UsageAttribute.cs @@ -1,12 +1,14 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Console.Waterworks.Attributes { - class UsageAttribute + [AttributeUsage(AttributeTargets.All)] + public class UsageAttribute : Attribute { + public readonly string UsageExample; + public UsageAttribute(string example) + { + UsageExample = example; + } } } diff --git a/Console.Waterworks/Console.Waterworks/CWLiaision.cs b/Console.Waterworks/Console.Waterworks/CWLiaision.cs deleted file mode 100644 index 7b7fe7b..0000000 --- a/Console.Waterworks/Console.Waterworks/CWLiaision.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Console.Waterworks -{ - public class CWLiaision - { - } -} diff --git a/Console.Waterworks/Console.Waterworks/CW_Liaison.cs b/Console.Waterworks/Console.Waterworks/CW_Liaison.cs new file mode 100644 index 0000000..bcf5b12 --- /dev/null +++ b/Console.Waterworks/Console.Waterworks/CW_Liaison.cs @@ -0,0 +1,23 @@ +using Console.Waterworks.CoOrdinators; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Console.Waterworks +{ + public class CW_Liaison + { + CoOrdinator kevin = new CoOrdinator(); + + public void Run(string consoleCommandsNamespace,bool includeProgramInfo) + { + kevin.PrepareConsoleEnvironment(); + if (includeProgramInfo == true) kevin.DisplayProgramInfo(); + kevin.RunProgram(consoleCommandsNamespace); + } + + public string RequestHelpDocumentation(string consoleCommandsNamespace) => kevin.DisplayHelpSection(consoleCommandsNamespace); + } +} diff --git a/Console.Waterworks/Console.Waterworks/CoOrdinators/CoOrdinator.cs b/Console.Waterworks/Console.Waterworks/CoOrdinators/CoOrdinator.cs index b31ecce..b96efe8 100644 --- a/Console.Waterworks/Console.Waterworks/CoOrdinators/CoOrdinator.cs +++ b/Console.Waterworks/Console.Waterworks/CoOrdinators/CoOrdinator.cs @@ -1,12 +1,96 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Console.Waterworks.Assistants; +using Console.Waterworks.Constants; +using Console.Waterworks.Loggers; +using Console.Waterworks.Models; +using Console.Waterworks.Specialists; +using System; namespace Console.Waterworks.CoOrdinators { class CoOrdinator { + CoOrdinatorAssistant Assistant = new CoOrdinatorAssistant(); + ProgramInfoSpecialist ProgInfoSpec = new ProgramInfoSpecialist(); + ConsoleIOSpecialist ConsoleSpec = new ConsoleIOSpecialist(); + CommandsSpecialist CommSpec = new CommandsSpecialist(); + HelpSpecialist HelpSpec = new HelpSpecialist(); + CW_Logger Logger = new CW_Logger(); + + internal void PrepareConsoleEnvironment() + { + Logger.LogInfoMessage($"Preparing console.."); + ConsoleSpec.PrepareConsoleEnvironment(); + ConsoleSpec.SetInputPrompt($"{ProgInfoSpec.GetProductName()}> "); + Logger.LogSuccessMessage("Console environment is now setup"); + } + + internal void DisplayProgramInfo() + { + Logger.LogInfoMessage($"Displaying program information.."); + Assistant.SetConsoleTitle(Logger, ProgInfoSpec, ConsoleSpec); + Assistant.OutputProgramInfo(Logger, ProgInfoSpec, ConsoleSpec); + } + + internal void RunProgram(string commandsNamespace) + { + Logger.LogInfoMessage("Attempting to building commands library.."); + var commandClasses = CommSpec.GetCommandClasses(commandsNamespace); + var commandLibraries = CommSpec.GetCommandLibraries(commandClasses); + Assistant.LogCommandGatheringAttempt(commandClasses, commandLibraries, Logger); + while (true) + { + var consoleInput = ConsoleSpec.GetInputFromUser(); + if (string.IsNullOrEmpty(consoleInput)) continue; + try + { + Logger.LogInfoMessage("Parsing input from user.."); + var command = new Command(consoleInput, commandsNamespace, CW_Constants.COMMAND_CLASS_NAME); + Logger.LogInfoMessage("Attempting to execute command.."); + var result = CommSpec.ExecuteCommand(commandsNamespace, command, commandClasses, commandLibraries, ConsoleSpec); + ConsoleSpec.WriteOutputToConsole(result); + Logger.LogSuccessMessage("Command has been executed."); + Logger.LogNoteMessage("An error message does not mean the command did not execute properly or sucessfully."); + } + catch (Exception ex) + { + Logger.LogErrorMessage("Command was not successfully executed. See the error message in console for further details"); + ConsoleSpec.WriteErrorMessage(ex.Message); + } + Logger.LogInfoMessage("Resetting the console's formatting.."); + Logger.LogNoteMessage("This is to make sure no error messages or one-off formating change corrupts the console environment."); + ConsoleSpec.ResetConsoleColour(); + Logger.LogSuccessMessage("Console's formating has been reset"); + } + } + + internal string DisplayHelpSection(string commandsNamespace) + { + Logger.LogInfoMessage("Attempting to display help section.."); + var commandClasses = CommSpec.GetCommandClasses(commandsNamespace); + if (commandClasses.Count == 0) + { + Logger.LogErrorMessage("Unable to find any help information. Make sure the commands hace the correct atrributes applied"); + ConsoleSpec.WriteErrorSuffix(); + return "Unable to find help information"; + } + Logger.LogSuccessMessage("Found help information"); + Logger.LogInfoMessage("Attempting to display the help information.."); + ConsoleSpec.WriteOutputToConsole("Displaying Help section."); + ConsoleSpec.LineBreak(); + var commandMembers = HelpSpec.GetCommandMembers(commandClasses); + foreach (var command in commandMembers) + { + if (HelpSpec.ListCommand(command) == true) + { + ConsoleSpec.WriteOutputToConsole($"Command Name: {command.Name}"); + ConsoleSpec.WriteOutputToConsole($"Parametres: {HelpSpec.GetParametres(command)}"); + ConsoleSpec.WriteOutputToConsole($"Description: {HelpSpec.GetDescription(command)}"); + ConsoleSpec.WriteOutputToConsole($"Example: {HelpSpec.GetUsageExamples(command)}"); + ConsoleSpec.LineBreak(); + } + } + Logger.LogSuccessMessage("Help section displayed in the console"); + return "End of Help section."; + } } } diff --git a/Console.Waterworks/Console.Waterworks/Console.Waterworks.csproj b/Console.Waterworks/Console.Waterworks/Console.Waterworks.csproj index 1a9a8ab..4d27078 100644 --- a/Console.Waterworks/Console.Waterworks/Console.Waterworks.csproj +++ b/Console.Waterworks/Console.Waterworks/Console.Waterworks.csproj @@ -44,17 +44,25 @@ - + + + + + + + + + + + + + + + + - - - - - - - - + diff --git a/Console.Waterworks/Console.Waterworks/Constants/CW_Constants.cs b/Console.Waterworks/Console.Waterworks/Constants/CW_Constants.cs index 663e5c3..e4c8335 100644 --- a/Console.Waterworks/Console.Waterworks/Constants/CW_Constants.cs +++ b/Console.Waterworks/Console.Waterworks/Constants/CW_Constants.cs @@ -1,12 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Console.Waterworks.Constants +namespace Console.Waterworks.Constants { - class CWConstants + class CW_Constants { + /// + /// This represents the maximum number of properties the console can display when the client "DisplayProgramInfo" to true. This is used mostly for logging purposes. + /// + public const int PROGRAM_INFO_PROPERTIES_COUNT = 4; + + /// + /// This specifies the name of the class WaterWorks looks for when it tries to build its command library. This class should not be in WaterWorks. It should be in the clients console program. + /// + public const string COMMAND_CLASS_NAME = "ConsoleCommands"; } } diff --git a/Console.Waterworks/Console.Waterworks/Loggers/CW_Logger.cs b/Console.Waterworks/Console.Waterworks/Loggers/CW_Logger.cs index 627db64..89f9f52 100644 --- a/Console.Waterworks/Console.Waterworks/Loggers/CW_Logger.cs +++ b/Console.Waterworks/Console.Waterworks/Loggers/CW_Logger.cs @@ -1,12 +1,12 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Diagnostics; namespace Console.Waterworks.Loggers { class CW_Logger { + internal void LogInfoMessage(string message) => Debug.WriteLine($"[Console.WaterWorks] INFO: {message}."); + internal void LogNoteMessage(string message) => Debug.WriteLine($"[Console.WaterWorks] NOTE: {message}."); + internal void LogSuccessMessage(string message) => Debug.WriteLine($"[Console.WaterWorks] SUCCESS: {message}."); + internal void LogErrorMessage(string message) => Debug.WriteLine($"[Console.WaterWorks] ERROR: {message}."); } } diff --git a/Console.Waterworks/Console.Waterworks/Models/Command.cs b/Console.Waterworks/Console.Waterworks/Models/Command.cs index db298b2..1c3360e 100644 --- a/Console.Waterworks/Console.Waterworks/Models/Command.cs +++ b/Console.Waterworks/Console.Waterworks/Models/Command.cs @@ -1,12 +1,70 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Text.RegularExpressions; namespace Console.Waterworks.Models { class Command { + public string Name { get; set; } + public string ClassName { get; set; } + + private List _arguments; + public IEnumerable Arguments + { + get + { + return _arguments; + } + } + + public Command(string input, string commandsNamespace, string className) + { + // Ugly regex to split string on spaces, but preserve quoted text intact: + var stringArray = + Regex.Split(input, "(?<=^[^\"]*(?:\"[^\"]*\"[^\"]*)*) (?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)"); + + _arguments = new List(); + for (int i = 0; i < stringArray.Length; i++) + { + // The first element is always the command: + if (i == 0) + { + Name = stringArray[i]; + + // Set the class name + ClassName = className; + string[] s = stringArray[0].Split('.'); + if (s.Length == 2) + { + ClassName = s[0]; + Name = s[1]; + } + } + else + { + var inputArgument = stringArray[i]; + + // Assume that most of the time, the input argument is NOT quoted text: + string argument = inputArgument; + + // Is the argument a quoted text string? + var regex = new Regex("\"(.*?)\"", RegexOptions.Singleline); + var match = regex.Match(inputArgument); + + // If it IS quoted, there will be at least one capture: + if (match.Captures.Count > 0) + { + // Get the unquoted text from within the qoutes: + var captureQuotedText = new Regex("[^\"]*[^\"]"); + var quoted = captureQuotedText.Match(match.Captures[0].Value); + + // The argument should include all text from between the quotes + // as a single string: + argument = quoted.Captures[0].Value; + } + _arguments.Add(argument); + } + } + } } } diff --git a/Console.Waterworks/Console.Waterworks/Specialists/CoercionSpecialist.cs b/Console.Waterworks/Console.Waterworks/Specialists/CoercionSpecialist.cs index 2a119d9..ffae1be 100644 --- a/Console.Waterworks/Console.Waterworks/Specialists/CoercionSpecialist.cs +++ b/Console.Waterworks/Console.Waterworks/Specialists/CoercionSpecialist.cs @@ -1,12 +1,174 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Console.Waterworks.Specialists { - class CoercionSpecialist + internal static class CoercionSpecialist { + internal static object CoerceArgument(Type requiredType, string inputValue) + { + var requiredTypeCode = Type.GetTypeCode(requiredType); + string exceptionMessage = $"Cannnot coerce the input argument {inputValue} to required type {requiredType.Name}"; + + object result = null; + switch (requiredTypeCode) + { + case TypeCode.String: + result = inputValue; + break; + + case TypeCode.Int16: + short number16; + if (Int16.TryParse(inputValue, out number16)) + { + result = number16; + } + else + { + throw new ArgumentException(exceptionMessage); + } + break; + + case TypeCode.Int32: + int number32; + if (Int32.TryParse(inputValue, out number32)) + { + result = number32; + } + else + { + throw new ArgumentException(exceptionMessage); + } + break; + + case TypeCode.Int64: + long number64; + if (Int64.TryParse(inputValue, out number64)) + { + result = number64; + } + else + { + throw new ArgumentException(exceptionMessage); + } + break; + + case TypeCode.Boolean: + bool trueFalse; + if (bool.TryParse(inputValue, out trueFalse)) + { + result = trueFalse; + } + else + { + throw new ArgumentException(exceptionMessage); + } + break; + + case TypeCode.Byte: + byte byteValue; + if (byte.TryParse(inputValue, out byteValue)) + { + result = byteValue; + } + else + { + throw new ArgumentException(exceptionMessage); + } + break; + + case TypeCode.Char: + char charValue; + if (char.TryParse(inputValue, out charValue)) + { + result = charValue; + } + else + { + throw new ArgumentException(exceptionMessage); + } + break; + + case TypeCode.DateTime: + DateTime dateValue; + if (DateTime.TryParse(inputValue, out dateValue)) + { + result = dateValue; + } + else + { + throw new ArgumentException(exceptionMessage); + } + break; + case TypeCode.Decimal: + Decimal decimalValue; + if (Decimal.TryParse(inputValue, out decimalValue)) + { + result = decimalValue; + } + else + { + throw new ArgumentException(exceptionMessage); + } + break; + case TypeCode.Double: + Double doubleValue; + if (Double.TryParse(inputValue, out doubleValue)) + { + result = doubleValue; + } + else + { + throw new ArgumentException(exceptionMessage); + } + break; + case TypeCode.Single: + Single singleValue; + if (Single.TryParse(inputValue, out singleValue)) + { + result = singleValue; + } + else + { + throw new ArgumentException(exceptionMessage); + } + break; + case TypeCode.UInt16: + UInt16 uInt16Value; + if (UInt16.TryParse(inputValue, out uInt16Value)) + { + result = uInt16Value; + } + else + { + throw new ArgumentException(exceptionMessage); + } + break; + case TypeCode.UInt32: + UInt32 uInt32Value; + if (UInt32.TryParse(inputValue, out uInt32Value)) + { + result = uInt32Value; + } + else + { + throw new ArgumentException(exceptionMessage); + } + break; + case TypeCode.UInt64: + UInt64 uInt64Value; + if (UInt64.TryParse(inputValue, out uInt64Value)) + { + result = uInt64Value; + } + else + { + throw new ArgumentException(exceptionMessage); + } + break; + default: + throw new ArgumentException(exceptionMessage); + } + return result; + } } } diff --git a/Console.Waterworks/Console.Waterworks/Specialists/CommandsSpecialist.cs b/Console.Waterworks/Console.Waterworks/Specialists/CommandsSpecialist.cs index 2d0bf79..c0784be 100644 --- a/Console.Waterworks/Console.Waterworks/Specialists/CommandsSpecialist.cs +++ b/Console.Waterworks/Console.Waterworks/Specialists/CommandsSpecialist.cs @@ -1,12 +1,60 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using Console.Waterworks.Models; +using System.Reflection; +using Console.Waterworks.Assistants; namespace Console.Waterworks.Specialists { class CommandsSpecialist { + CommandsAssistant Assistant = new CommandsAssistant(); + + internal List GetCommandClasses(string commandsNameSpace) + { + string theNamespace = commandsNameSpace; + var q = from t in Assembly.GetEntryAssembly().GetTypes() + where t.IsClass && t.Namespace == theNamespace + select t; + return q.ToList(); + } + + internal Dictionary>> GetCommandLibraries(List commandClasses) + { + var commandLibraries = new Dictionary>>(); + foreach (var commandClass in commandClasses) + { + var methods = commandClass.GetMethods(BindingFlags.Static | BindingFlags.Public); + var methodDictionary = new Dictionary>(); + foreach (var method in methods) + { + string commandName = method.Name; + methodDictionary.Add(commandName, method.GetParameters()); + } + commandLibraries.Add(commandClass.Name, methodDictionary); + } + return commandLibraries; + } + + internal string ExecuteCommand(string commandsNamespace, Command command, List commandClasses, Dictionary>> commandLibraries, ConsoleIOSpecialist consoleSpec) + { + var classValidated = Assistant.ValidateClass(command, commandLibraries); + var commandNameValidated = Assistant.ValidateCommand(command, commandLibraries); + Dictionary> methodDictionary; + if (classValidated != true || commandNameValidated != true) + return Assistant.ExecuteBadCommandProcedure(command, consoleSpec); + else + methodDictionary = commandLibraries[command.ClassName]; + var paramInfoList = methodDictionary[command.Name].ToList(); + var paramsValidated = Assistant.ValidateParamArguments(command, paramInfoList); + if (paramsValidated == false) + return Assistant.ExecuteMissingArgumentProcedure(command, paramInfoList, consoleSpec); + var methodParametreValueList = Assistant.GetParametreValueList(command, paramInfoList); + var typeInfo = Assistant.BuildCommandLibraryClass(command, commandsNamespace); + var inputArguments = Assistant.GetInputArguments(methodParametreValueList); + var result = Assistant.InvokeCommand(command, typeInfo, inputArguments); + return result; + } } } diff --git a/Console.Waterworks/Console.Waterworks/Specialists/ConsoleIOSpecialist.cs b/Console.Waterworks/Console.Waterworks/Specialists/ConsoleIOSpecialist.cs index 506bdb3..983d3eb 100644 --- a/Console.Waterworks/Console.Waterworks/Specialists/ConsoleIOSpecialist.cs +++ b/Console.Waterworks/Console.Waterworks/Specialists/ConsoleIOSpecialist.cs @@ -1,12 +1,67 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Console.Waterworks.Specialists { class ConsoleIOSpecialist { + string _consolePrompt; + + internal void PrepareConsoleEnvironment() + { +#if DEBUG + System.Console.BackgroundColor = ConsoleColor.DarkGray; + System.Console.Clear(); + System.Console.ForegroundColor = ConsoleColor.White; + System.Console.WriteLine(" ------------ "); + System.Console.WriteLine("| DEBUG MODE |"); + System.Console.WriteLine(" ------------ "); +#endif + } + + internal void SetConsoleTitle(string title) => System.Console.Title = title; + + internal void SetInputPrompt(string prompt) => _consolePrompt = prompt; + + internal void WriteProgramInfo(List programInfo) + { + foreach (var line in programInfo) + { + if (!string.IsNullOrEmpty(line)) System.Console.WriteLine(line); + } + } + + internal string GetInputFromUser() + { + System.Console.ForegroundColor = ConsoleColor.Green; + System.Console.Write(_consolePrompt); + System.Console.ResetColor(); + return System.Console.ReadLine(); + } + + internal void WriteOutputToConsole(string message) => System.Console.WriteLine(message); + + internal void LineBreak() => System.Console.WriteLine(); + + internal void WriteErrorSuffix() + { + System.Console.ForegroundColor = ConsoleColor.Red; + System.Console.Write("[ERROR] "); + } + + internal void WriteErrorMessage(string message) + { + System.Console.ForegroundColor = ConsoleColor.Red; + System.Console.WriteLine($"[ERROR] {message}"); + System.Console.ResetColor(); + } + + internal void ResetConsoleColour() => System.Console.ResetColor(); + + internal void WriteInfoSuffix() + { + System.Console.ForegroundColor = ConsoleColor.Yellow; + System.Console.Write("[INFO] "); + } } } diff --git a/Console.Waterworks/Console.Waterworks/Specialists/HelpSpecialist.cs b/Console.Waterworks/Console.Waterworks/Specialists/HelpSpecialist.cs index aba3bab..42d3de2 100644 --- a/Console.Waterworks/Console.Waterworks/Specialists/HelpSpecialist.cs +++ b/Console.Waterworks/Console.Waterworks/Specialists/HelpSpecialist.cs @@ -1,12 +1,63 @@ -using System; +using Console.Waterworks.Attributes; +using Console.Waterworks.Loggers; +using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Reflection; namespace Console.Waterworks.Specialists { class HelpSpecialist { + CW_Logger _logger = new CW_Logger(); + + internal MemberInfo[] GetCommandMembers(List commandClasses) => commandClasses[0].GetMembers(); + + internal bool ListCommand(MemberInfo command) + { + var customAttributes = GetCustomAttributes(command); + var castedAttributes = CheckForAttributesType(customAttributes); + return (castedAttributes != null) ? castedAttributes.ShowCommand : false; + } + + T CheckForAttributesType(object[] attributes) + { + T theAttributes = default(T); + for (int i = 0; i < attributes.Length; i++) + { + try + { + theAttributes = (T)attributes[i]; + break; + } + catch (Exception) + { + _logger.LogInfoMessage("The attempted casting attempt failed. Do not panic. This was expected"); + } + } + return theAttributes; + } + + object[] GetCustomAttributes(MemberInfo command) => command.GetCustomAttributes(true); + + internal object GetParametres(MemberInfo command) + { + var attributes = GetCustomAttributes(command); + var castedAttributes = CheckForAttributesType(attributes); + return (castedAttributes != null) ? castedAttributes.Parametres : "Parametres values could not be found"; + } + + internal object GetDescription(MemberInfo command) + { + var attributes = GetCustomAttributes(command); + var castedAttributes = CheckForAttributesType(attributes); + return (castedAttributes != null) ? castedAttributes.Description : "Description could not be found"; + } + + internal object GetUsageExamples(MemberInfo command) + { + var attributes = GetCustomAttributes(command); + var castedAttributes = CheckForAttributesType(attributes); + return (castedAttributes != null) ? castedAttributes.UsageExample : "Example could not be found"; + } } } diff --git a/Console.Waterworks/Console.Waterworks/Specialists/ProgramInfoSpecialist.cs b/Console.Waterworks/Console.Waterworks/Specialists/ProgramInfoSpecialist.cs index d605ce2..b282a19 100644 --- a/Console.Waterworks/Console.Waterworks/Specialists/ProgramInfoSpecialist.cs +++ b/Console.Waterworks/Console.Waterworks/Specialists/ProgramInfoSpecialist.cs @@ -1,12 +1,70 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Reflection; namespace Console.Waterworks.Specialists { class ProgramInfoSpecialist { + internal List GatherProgramInfomation() + { + var info = new List() + { + addProductLine(), + addCompanyLine(), + addCopyRightLine(), + addProductDescription() + }; + return info; + } + + private string addCopyRightLine() + { + Assembly assembly = Assembly.GetEntryAssembly(); + var copyRightAttribute = assembly + .GetCustomAttributes(typeof(AssemblyCopyrightAttribute), false) + .OfType() + .FirstOrDefault(); + return (copyRightAttribute != null) ? copyRightAttribute.Copyright : null; + } + + string addProductLine() + { + var name = GetProductName(); + var buildInfo = getBuildInfo(); + return (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(buildInfo)) ? $"{name} {buildInfo}" : null; + } + + string addCompanyLine() + { + Assembly assembly = Assembly.GetEntryAssembly(); + var companyAttribute = assembly + .GetCustomAttributes(typeof(AssemblyCompanyAttribute), false) + .OfType() + .FirstOrDefault(); + return (companyAttribute != null) ? $"by {companyAttribute.Company}" : null; + } + + string addProductDescription() + { + Assembly assembly = Assembly.GetEntryAssembly(); + var descriptionAttribute = assembly + .GetCustomAttributes(typeof(AssemblyDescriptionAttribute), false) + .OfType() + .FirstOrDefault(); + return (descriptionAttribute != null) ? descriptionAttribute.Description : null; + } + + internal string GetProductName() + { + Assembly assembly = Assembly.GetEntryAssembly(); + var productAttribute = assembly + .GetCustomAttributes(typeof(AssemblyProductAttribute), false) + .OfType() + .FirstOrDefault(); + return (productAttribute != null) ? productAttribute.Product : null; + } + + string getBuildInfo() => Assembly.GetEntryAssembly().GetName().Version.ToString(); } }