7 using Core.Commands.Results;
11 using System.Diagnostics;
19 public partial class Hermod {
21 public bool InteractiveMode {
get;
internal set; }
23 private Stack<string> m_previousCommands =
new Stack<string>();
31 m_consoleLock =
new object();
32 m_configManager = configManager;
35 InteractiveMode = configManager.GetConfig<
bool>(
"Terminal.EnableInteractive");
36 m_inputCancellationToken =
new CancellationTokenSource();
41 m_appLogger.Information(
"Setting up OS event handlers...");
42 Console.CancelKeyPress += Console_CancelKeyPress;
44 m_appLogger.Debug(
"Setting up PluginRegistry...");
45 PluginRegistry.Instance.AppLogger = m_appLogger;
46 PluginRegistry.Instance.BuiltInCommands = Commands;
48 m_appLogger.Information(
"Loading plugins...");
49 m_appLogger.Information($
"Plugin dir: { m_configManager.GetPluginInstallDir() }");
50 foreach (var plugin
in m_configManager.GetPluginInstallDir().EnumerateFiles(
"*.dll")) {
51 m_appLogger.Information($
"Attempting to load { plugin.FullName }...");
54 }
catch (Exception ex) {
55 m_appLogger.Error($
"Failed to load assembly { plugin.FullName }!");
56 m_appLogger.Error($
"Error: { ex.Message }");
58 m_appLogger.Debug(ex.StackTrace);
71 if (InteractiveMode) {
72 var promptInput = ShowPrompt();
73 if (
string.IsNullOrEmpty(promptInput)) {
continue; }
75 var splitString = promptInput.Split(
' ',
'\t');
76 if (TryGetCommand(splitString.First(), out var command)) {
77 if (command is
null) {
continue; }
79 var argArray =
new string[splitString.Length - 1];
80 if (argArray.Length > 0) {
81 Array.Copy(splitString[1..], argArray, argArray.Length);
84 var result = await command.ExecuteAsync(argArray);
86 if (result is
CommandErrorResult errResult && !
string.IsNullOrEmpty(result?.Message)) {
87 ConsoleErrorWrite(result.Message);
89 m_appLogger.Error(errResult.Result as Exception, $
"Command execution failed! Command: { command.Name } { string.Join(' ', argArray) }");
90 }
else if (!
string.IsNullOrEmpty(result?.Message)) {
91 ConsoleWrite(result.Message);
94 m_appLogger.Error($
"Command \"{splitString[0] }\" not found!");
112 const string PROMPT_STR =
"hermod > ";
114 void WritePrompt(
bool newLine =
true) {
115 if (newLine) { Console.WriteLine(); }
116 Console.Write(PROMPT_STR);
120 StringBuilder lineCache =
new StringBuilder();
122 ConsoleKeyInfo keyCode;
123 var historyStartIndex = m_previousCommands.Count;
125 while ((keyCode = Console.ReadKey()).Key != ConsoleKey.Enter) {
126 switch (keyCode.Key) {
127 case ConsoleKey.Tab: {
128 var autocompletedString = GetAutocompletion(lineCache.ToString());
129 if (autocompletedString is
null) {
134 Console.Write(autocompletedString);
135 lineCache.Append(autocompletedString);
138 case ConsoleKey.Backspace:
139 if (lineCache.Length == 0) { Console.Beep();
continue; }
140 lineCache.Remove(lineCache.Length - 1, 1);
147 case ConsoleKey.UpArrow:
148 if (m_previousCommands.Count == 0 || historyStartIndex == m_previousCommands.Count) {
154 lineCache.Append(m_previousCommands.ElementAt(m_previousCommands.Count - historyStartIndex++));
155 Console.CursorLeft = 0;
157 Console.Write(lineCache.ToString());
161 lineCache.Append(keyCode.KeyChar);
165 var cmdString = lineCache.ToString().Trim();
166 m_previousCommands.Push(cmdString);
179 let distance = LevenshteinDistance(command.Name, input)
180 where distance <= maxDistance
183 return matches.FirstOrDefault();
188 if (haystack == needle) {
return 0; }
189 if (haystack.Length == 0) {
return needle.Length; }
190 if (needle.Length == 0) {
return haystack.Length; }
193 int[,] distance =
new int[haystack.Length + 1, needle.Length + 1];
194 for (
int i = 0; i <= haystack.Length; i++) {
197 for (
int j = 0; j <= needle.Length; j++) {
202 for (
int i = 1; i <= haystack.Length; i++) {
203 for (
int j = 1; j <= needle.Length; j++) {
204 int cost = (haystack[i - 1] == needle[j - 1]) ? 0 : 1;
205 distance[i, j] = Math.Min(Math.Min(distance[i - 1, j] + 1, distance[i, j - 1] + 1), distance[i - 1, j - 1] + cost);
209 return distance[haystack.Length, needle.Length];
216 m_appLogger.Warning(
"Shutting down plugins...");
218 m_appLogger.Warning(
"Preparing for graceful exit.");
226 var version = GetType().Assembly.GetName().Version;
227 var appTitle =
new StringBuilder().Append(
"Hermod ");
229 if (InteractiveMode) { appTitle.Append(
"[interactive] "); }
231 Console.Title = $
"{ appTitle.ToString() } - v{ version?.Major }.{ version?.MajorRevision }.{ version?.Minor }.{ version?.MinorRevision }";
241 m_appLogger.Warning(
"Received signal SIGNIT (CTRL+C)!");
244 if (InteractiveMode) {
245 m_appLogger.Warning(
"Hermod is running in interactive mode! Please use \"quit\" command!");
250 m_inputCancellationToken.Cancel();
255 lock (m_consoleLock) {
256 Console.WriteLine(message);
261 lock (m_consoleLock) {
262 var prevBackground = Console.BackgroundColor;
263 var prevForegound = Console.ForegroundColor;
264 Console.ForegroundColor = ConsoleColor.Red;
265 Console.Error.WriteLine(message);
266 Console.BackgroundColor = prevBackground;
267 Console.ForegroundColor = prevForegound;
Provides an application-wide, thread safe way of getting and setting configurations relevant to the m...
A ICommandResult which implicates the command encountered an error.
void ShutDown()
Shuts Hermod down.
void ConsoleErrorWrite(string message)
async Task< int > Execute()
Executes the main business logic of the application.
void SetTerminalTitle()
Sets the terminal's title.
int LevenshteinDistance(string haystack, string needle)
void ConsoleWrite(string message)
string? GetAutocompletion(string input, int maxDistance=2)
Attempts to get an auto completed string for the user's input.
void Console_CancelKeyPress(object? sender, ConsoleCancelEventArgs e)
Handles SIGINT (CTRL+C)
Hermod(ConfigManager configManager, ILogger logger)
Main constructor; initialises the object.
string? ShowPrompt()
Displays the input prompt.
Handles the loading, unloading, and general management of plugins.
void LoadPlugin(FileInfo pluginFile)
Loads one or plugins from an Assembly on disk.
static PluginRegistry Instance
Gets the current instance of this object.
List< ICommand > GetAllCommands()
Gets a list containing all ICommand instances known to the application at the current time.