diff --git a/SrcMod/Shell/GlobalUsings.cs b/SrcMod/Shell/GlobalUsings.cs index a26ef43..8ae847e 100644 --- a/SrcMod/Shell/GlobalUsings.cs +++ b/SrcMod/Shell/GlobalUsings.cs @@ -1,12 +1,14 @@ global using Nerd_STF.Mathematics; +global using Newtonsoft.Json; global using SharpCompress.Archives.Rar; global using SharpCompress.Archives.SevenZip; global using SharpCompress.Readers; -global using SrcMod.Shell; global using SrcMod.Shell.Interop; global using SrcMod.Shell.Modules.ObjectModels; +global using SrcMod.Shell.ObjectModels; global using System; global using System.Collections.Generic; +global using System.ComponentModel; global using System.Diagnostics; global using System.Formats.Tar; global using System.IO; diff --git a/SrcMod/Shell/LoadingBar.cs b/SrcMod/Shell/LoadingBar.cs new file mode 100644 index 0000000..f658c8e --- /dev/null +++ b/SrcMod/Shell/LoadingBar.cs @@ -0,0 +1,79 @@ +namespace SrcMod.Shell; + +internal static class LoadingBar +{ + public static int position = -1; + public static int bufferSize = 0; + public static int lastValue = -1; + public static float value = 0; + public static ConsoleColor color = Console.ForegroundColor; + + public static bool Enabled { get; private set; } + + public static void End(bool clear = true) + { + if (position == -1) throw new("No loading bar is active."); + + if (clear) + { + Int2 oldPos = (Console.CursorLeft, Console.CursorTop); + + Console.CursorLeft = 0; + Console.CursorTop = position; + Console.Write(new string(' ', Console.BufferWidth)); + Console.CursorLeft = 0; + + Console.SetCursorPosition(oldPos.x, oldPos.y); + } + position = -1; + Enabled = false; + } + public static void Set(float value, ConsoleColor? color = null) + { + const string left = " --- [", + right = "] --- "; + int barSize = Console.BufferWidth - left.Length - right.Length, + filled = (int)(barSize * value); + + if (filled == lastValue) return; + lastValue = filled; + + Int2 oldPos = (Console.CursorLeft, Console.CursorTop); + + LoadingBar.value = value; + LoadingBar.color = color ?? Console.ForegroundColor; + + // Erase last bar. + Console.SetCursorPosition(0, position); + Console.Write(new string(' ', bufferSize)); + Console.CursorLeft = 0; + + // Add new bar. + bufferSize = Console.BufferWidth; + + Write(left, newLine: false); + ConsoleColor oldFore = Console.ForegroundColor; + + if (color is not null) Console.ForegroundColor = color.Value; + Write(new string('=', filled), newLine: false); + if (color is not null) Console.ForegroundColor = oldFore; + Write(new string(' ', barSize - filled), newLine: false); + Write(right, newLine: false); + + if (oldPos.y == Console.CursorTop) oldPos.y++; + while (oldPos.y >= Console.BufferHeight) + { + Console.WriteLine(); + oldPos.y--; + position--; + } + Console.SetCursorPosition(oldPos.x, oldPos.y); + } + public static void Start(float value = 0, int? position = null, ConsoleColor? color = null) + { + if (LoadingBar.position != -1) throw new("The loading bar has already been enabled."); + LoadingBar.position = position ?? Console.CursorTop; + Enabled = true; + Set(value, color); + } +} diff --git a/SrcMod/Shell/Modules/BaseModule.cs b/SrcMod/Shell/Modules/BaseModule.cs index 0436cb5..eae1fd6 100644 --- a/SrcMod/Shell/Modules/BaseModule.cs +++ b/SrcMod/Shell/Modules/BaseModule.cs @@ -45,7 +45,7 @@ public static class BaseModule Write($"Copying directory \"{source}\" to \"{destination}\"..."); - LoadingBarStart(); + LoadingBar.Start(); for (int i = 0; i < files.Length; i++) { string file = files[i], @@ -53,7 +53,7 @@ public static class BaseModule destFile = Path.Combine(destination, file); Directory.CreateDirectory(Path.GetDirectoryName(destFile)!); File.Copy(sourceFile, destFile); - LoadingBarSet((i + 1) / (float)files.Length, ConsoleColor.DarkGreen); + LoadingBar.Set((i + 1) / (float)files.Length, ConsoleColor.DarkGreen); Console.CursorLeft = 0; string message = $"{sourceFile}"; int remainder = Console.BufferWidth - message.Length; @@ -63,7 +63,7 @@ public static class BaseModule Write(message, newLine: false); } - LoadingBarEnd(); + LoadingBar.End(); Console.CursorLeft = 0; Write(new string(' ', Console.BufferWidth), newLine: false); @@ -361,13 +361,13 @@ public static class BaseModule Thread.Sleep(rand.Next(500, 1000)); - LoadingBarStart(); + LoadingBar.Start(); for (int i = 0; i < files.Length; i++) { FileInfo file = new(files[i]); Thread.Sleep((int)(rand.Next(50, 100) * (file.Length >> 20))); - LoadingBarSet((i + 1) / (float)files.Length, ConsoleColor.Red); + LoadingBar.Set((i + 1) / (float)files.Length, ConsoleColor.Red); Console.CursorLeft = 0; string message = $"{files[i]}"; int remainder = Console.BufferWidth - message.Length; @@ -377,7 +377,7 @@ public static class BaseModule Write(message, newLine: false); } - LoadingBarEnd(); + LoadingBar.End(); Console.CursorLeft = 0; Write(new string(' ', Console.BufferWidth), newLine: false); diff --git a/SrcMod/Shell/Modules/CompressionModule.cs b/SrcMod/Shell/Modules/CompressionModule.cs index 43d5804..128dcf9 100644 --- a/SrcMod/Shell/Modules/CompressionModule.cs +++ b/SrcMod/Shell/Modules/CompressionModule.cs @@ -168,7 +168,7 @@ public static class CompressionModule int failed = 0; - LoadingBarStart(); + LoadingBar.Start(); for (int i = 0; i < files.Count; i++) { bool failedThisTime = false; @@ -181,7 +181,7 @@ public static class CompressionModule failedThisTime = true; failed++; } - LoadingBarSet((i + 1) / (float)files.Count, failedThisTime ? ConsoleColor.Red : ConsoleColor.DarkGreen); ; + LoadingBar.Set((i + 1) / (float)files.Count, failedThisTime ? ConsoleColor.Red : ConsoleColor.DarkGreen); ; Console.CursorLeft = 0; string message = $"{relative[i]}"; int remainder = Console.BufferWidth - message.Length; @@ -194,7 +194,7 @@ public static class CompressionModule archive.Dispose(); writer.Dispose(); - LoadingBarEnd(); + LoadingBar.End(); Console.CursorLeft = 0; Write(new string(' ', Console.BufferWidth), newLine: false); diff --git a/SrcMod/Shell/Modules/ConfigModule.cs b/SrcMod/Shell/Modules/ConfigModule.cs new file mode 100644 index 0000000..58e88a0 --- /dev/null +++ b/SrcMod/Shell/Modules/ConfigModule.cs @@ -0,0 +1,194 @@ +namespace SrcMod.Shell.Modules; + +[Module("config")] +public static class ConfigModule +{ + [Command("display")] + [Command("list")] + public static void DisplayConfig(ConfigDisplayMode mode = ConfigDisplayMode.All) + { + switch (mode) + { + case ConfigDisplayMode.Raw: + DisplayConfigRaw(); + break; + + case ConfigDisplayMode.All: + DisplayConfigAll(); + break; + + case ConfigDisplayMode.GameDirectories: + DisplayConfigGameDirectories(); + break; + + case ConfigDisplayMode.RunUnsafeCommands: + DisplayConfigUnsafeCommands(); + break; + } + } + + [Command("add")] + [Command("append")] + public static void AppendConfigVariable(string name, string value) + { + Config config = Config.LoadedConfig; + + switch (name.Trim().ToLower()) + { + case "gamedirectories": + config.GameDirectories = config.GameDirectories.Append(value).ToArray(); + break; + + case "rununsafecommands": + throw new($"The config variable \"{name}\" is a single variable and cannot be appended to."); + + default: throw new($"Unknown config variable \"{name}\""); + } + + Config.LoadedConfig = config; + } + + [Command("delete")] + [Command("remove")] + public static void RemoveConfigVariable(string name, string value) + { + Config config = Config.LoadedConfig; + + switch (name.Trim().ToLower()) + { + case "gamedirectories": + config.GameDirectories = config.GameDirectories + .Where(x => x.Trim().ToLower() != value.Trim().ToLower()) + .ToArray(); + break; + + case "rununsafecommands": + throw new($"The config variable \"{name}\" is a single variable and cannot be appended to."); + + default: throw new($"Unknown config variable \"{name}\""); + } + + Config.LoadedConfig = config; + } + + [Command("reset")] + public static void ResetConfig(string name = "all") + { + Config config = Config.LoadedConfig; + + switch (name.Trim().ToLower()) + { + case "gamedirectories": + config.GameDirectories = Config.Defaults.GameDirectories; + break; + + case "rununsafecommands": + config.RunUnsafeCommands = Config.Defaults.RunUnsafeCommands; + break; + + case "all": + config = Config.Defaults; + break; + + default: throw new($"Unknown config variable \"{name}\""); + } + + Config.LoadedConfig = config; + } + + [Command("set")] + public static void SetConfigVariable(string name, string value) + { + Config config = Config.LoadedConfig; + + switch (name.Trim().ToLower()) + { + case "gamedirectories": + throw new($"The config variable \"{name}\" is a list and must be added or removed to."); + + case "rununsafecommands": + if (int.TryParse(value, out int intRes)) + { + AskMode mode = (AskMode)intRes; + if (!Enum.IsDefined(mode)) throw new($"(AskMode){value} is not a valid AskMode."); + config.RunUnsafeCommands = mode; + } + else if (Enum.TryParse(value, true, out AskMode modeRes)) + { + if (!Enum.IsDefined(modeRes)) throw new($"\"{value}\" is not a valid AskMode."); + config.RunUnsafeCommands = modeRes; + } + else throw new($"\"{value}\" is not a valid AskMode."); + break; + + default: throw new($"Unknown config variable \"{name}\""); + } + + Config.LoadedConfig = config; + } + + private static void DisplayConfigAll() + { + DisplayConfigGameDirectories(); + DisplayConfigUnsafeCommands(); + } + private static void DisplayConfigRaw() + { + // This is definitely a bit inefficient, but shouldn't be too much of an issue. + + MemoryStream ms = new(); + StreamWriter writer = new(ms, leaveOpen: true); + JsonTextWriter jsonWriter = new(writer); + + Serializer.Serialize(jsonWriter, Config.LoadedConfig); + + jsonWriter.Close(); + writer.Close(); + ms.Position = 0; + + StreamReader reader = new(ms); + string msg = reader.ReadToEnd(); + + Write(msg); + + reader.Close(); + ms.Close(); + } + private static void DisplayConfigGameDirectories() + { + Write("Steam Game Directories: ", null, false); + if (Config.LoadedConfig.GameDirectories is null || Config.LoadedConfig.GameDirectories.Length <= 0) + Write("None", ConsoleColor.DarkGray); + else + { + Write("[", ConsoleColor.DarkGray); + for (int i = 0; i < Config.LoadedConfig.GameDirectories.Length; i++) + { + Write(" \"", ConsoleColor.DarkGray, false); + Write(Config.LoadedConfig.GameDirectories[i], ConsoleColor.White, false); + if (i < Config.LoadedConfig.GameDirectories.Length - 1) Write("\",", ConsoleColor.DarkGray); + else Write("\"", ConsoleColor.DarkGray); + } + Write("]", ConsoleColor.DarkGray); + } + } + private static void DisplayConfigUnsafeCommands() + { + Write("Run Unsafe Commands: ", null, false); + ConsoleColor color = Config.LoadedConfig.RunUnsafeCommands switch + { + AskMode.Never => ConsoleColor.Red, + AskMode.Always => ConsoleColor.Green, + AskMode.Ask or _ => ConsoleColor.DarkGray + }; + Write(Config.LoadedConfig.RunUnsafeCommands, color); + } + + public enum ConfigDisplayMode + { + Raw, + All, + GameDirectories, + RunUnsafeCommands + } +} diff --git a/SrcMod/Shell/ObjectModels/AskMode.cs b/SrcMod/Shell/ObjectModels/AskMode.cs new file mode 100644 index 0000000..e425960 --- /dev/null +++ b/SrcMod/Shell/ObjectModels/AskMode.cs @@ -0,0 +1,8 @@ +namespace SrcMod.Shell.ObjectModels; + +public enum AskMode : sbyte +{ + Never = -1, + Ask = 0, + Always = 1 +} diff --git a/SrcMod/Shell/ObjectModels/Config.cs b/SrcMod/Shell/ObjectModels/Config.cs new file mode 100644 index 0000000..a14e6df --- /dev/null +++ b/SrcMod/Shell/ObjectModels/Config.cs @@ -0,0 +1,86 @@ +namespace SrcMod.Shell.ObjectModels; + +public struct Config +{ + public const string FilePath = "config.json"; + + public static readonly Config Defaults; + + public static Config LoadedConfig + { + get => p_applied; + set + { + p_applied = value; + p_changes = p_applied.GetChanges(Defaults); + } + } + + private static Config p_applied; + private static ConfigChanges? p_changes; + + static Config() + { + Defaults = new() + { + GameDirectories = Array.Empty(), + RunUnsafeCommands = AskMode.Ask + }; + } + + public string[] GameDirectories; + public AskMode RunUnsafeCommands; + + public Config ApplyChanges(ConfigChanges changes) => this with + { + GameDirectories = GameDirectories.Union(changes.GameDirectories ?? Array.Empty()).ToArray(), + RunUnsafeCommands = changes.RunUnsafeCommands ?? RunUnsafeCommands + }; + public ConfigChanges GetChanges(Config? baseConfig = null) + { + Config reference = baseConfig ?? Defaults; + ConfigChanges changes = new() + { + GameDirectories = reference.GameDirectories == GameDirectories ? null : + GameDirectories.Where(x => !reference.GameDirectories.Contains(x)).ToArray(), + RunUnsafeCommands = reference.RunUnsafeCommands == RunUnsafeCommands ? null : RunUnsafeCommands + }; + + return changes; + } + + public static void LoadConfig(string basePath) + { + string fullPath = Path.Combine(basePath, FilePath); + + if (!File.Exists(fullPath)) + { + p_applied = Defaults; + p_changes = null; + return; + } + StreamReader reader = new(fullPath); + JsonTextReader jsonReader = new(reader); + p_changes = Serializer.Deserialize(jsonReader); + jsonReader.Close(); + reader.Close(); + + p_applied = p_changes is null ? Defaults : Defaults.ApplyChanges(p_changes.Value); + } + public static void SaveConfig(string basePath) + { + string fullPath = Path.Combine(basePath, FilePath); + + if (p_changes is null || !p_changes.Value.HasChange) + { + if (File.Exists(fullPath)) File.Delete(fullPath); + return; + } + + StreamWriter writer = new(fullPath); + JsonTextWriter jsonWriter = new(writer); + Serializer.Serialize(jsonWriter, p_changes); + jsonWriter.Close(); + writer.Close(); + } +} diff --git a/SrcMod/Shell/ObjectModels/ConfigChanges.cs b/SrcMod/Shell/ObjectModels/ConfigChanges.cs new file mode 100644 index 0000000..8328796 --- /dev/null +++ b/SrcMod/Shell/ObjectModels/ConfigChanges.cs @@ -0,0 +1,10 @@ +namespace SrcMod.Shell.ObjectModels; + +public record struct ConfigChanges +{ + [JsonIgnore] + public bool HasChange => GameDirectories is not null || RunUnsafeCommands is not null; + + public string[]? GameDirectories; + public AskMode? RunUnsafeCommands; +} diff --git a/SrcMod/Shell/Shell.cs b/SrcMod/Shell/Shell.cs index 3a90b88..0629dd8 100644 --- a/SrcMod/Shell/Shell.cs +++ b/SrcMod/Shell/Shell.cs @@ -1,12 +1,10 @@ -using System.ComponentModel; - -namespace SrcMod.Shell; +namespace SrcMod.Shell; public class Shell { public const string Author = "That_One_Nerd"; public const string Name = "SrcMod"; - public const string Version = "Alpha 0.3.2"; + public const string Version = "Alpha 0.3.3"; public readonly string? ShellDirectory; @@ -50,6 +48,10 @@ public class Shell WorkingDirectory = Directory.GetCurrentDirectory(); + // Load config. + if (ShellDirectory is null) Write("[WARNING] Could not load config from shell location. Defaults will be used."); + else Config.LoadConfig(ShellDirectory); + // Load modules and commands. List possibleAsms = new() { @@ -95,6 +97,26 @@ public class Shell ReloadDirectoryInfo(); } + public bool LoadModule(Type moduleType) + { + if (LoadedModules.Any(x => x.Type.FullName == moduleType.FullName)) return false; + + ModuleInfo? module = ModuleInfo.FromType(moduleType); + if (module is null) return false; + + LoadedModules.Add(module); + LoadedCommands.AddRange(module.Commands); + + return true; + } + public bool LoadModule() => LoadModule(typeof(T)); + public int LoadModules(Assembly moduleAssembly) + { + int loaded = 0; + foreach (Type moduleType in moduleAssembly.GetTypes()) if (LoadModule(moduleType)) loaded++; + return loaded; + } + public void AddHistory(HistoryItem item) => History.Add(item); public void UndoItem() { @@ -227,12 +249,12 @@ public class Shell catch (TargetInvocationException ex) { Write($"[ERROR] {ex.InnerException!.Message}", ConsoleColor.Red); - if (LoadingBarEnabled) LoadingBarEnd(); + if (LoadingBar.Enabled) LoadingBar.End(); } catch (Exception ex) { Write($"[ERROR] {ex.Message}", ConsoleColor.Red); - if (LoadingBarEnabled) LoadingBarEnd(); + if (LoadingBar.Enabled) LoadingBar.End(); } #endif } @@ -250,6 +272,11 @@ public class Shell activeCommand.Dispose(); activeCommand = null; } + + if (ShellDirectory is null) Write("[WARNING] Could not save config to shell location. Any changes will be ignored."); + else Config.SaveConfig(ShellDirectory); + + ReloadDirectoryInfo(); return; } } diff --git a/SrcMod/Shell/Shell.csproj b/SrcMod/Shell/Shell.csproj index d662f45..253835f 100644 --- a/SrcMod/Shell/Shell.csproj +++ b/SrcMod/Shell/Shell.csproj @@ -13,6 +13,7 @@ false Logo.ico true + true @@ -31,6 +32,7 @@ + diff --git a/SrcMod/Shell/Tools.cs b/SrcMod/Shell/Tools.cs index 3fea015..c90265d 100644 --- a/SrcMod/Shell/Tools.cs +++ b/SrcMod/Shell/Tools.cs @@ -1,16 +1,17 @@ -using System.Text; +namespace SrcMod.Shell; -namespace SrcMod.Shell; - -public static class Tools +internal static class Tools { - private static int loadingPosition = -1; - private static int lastLoadingBufferSize = 0; - private static int lastLoadingValue = -1; - private static float loadingBarValue = 0; - private static ConsoleColor loadingBarColor = Console.ForegroundColor; + public static JsonSerializer Serializer { get; private set; } - public static bool LoadingBarEnabled { get; private set; } + static Tools() + { + Serializer = JsonSerializer.Create(new() + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore + }); + } public static void DisplayWithPages(IEnumerable lines, ConsoleColor? color = null) { @@ -73,73 +74,6 @@ public static class Tools return allFiles; } - public static void LoadingBarEnd(bool clear = true) - { - if (loadingPosition == -1) throw new("No loading bar is active."); - - if (clear) - { - Int2 oldPos = (Console.CursorLeft, Console.CursorTop); - - Console.CursorLeft = 0; - Console.CursorTop = loadingPosition; - Console.Write(new string(' ', Console.BufferWidth)); - Console.CursorLeft = 0; - - Console.SetCursorPosition(oldPos.x, oldPos.y); - } - loadingPosition = -1; - LoadingBarEnabled = false; - } - public static void LoadingBarSet(float value, ConsoleColor? color = null) - { - const string left = " --- [", - right = "] --- "; - int barSize = Console.BufferWidth - left.Length - right.Length, - filled = (int)(barSize * value); - - if (filled == lastLoadingValue) return; - lastLoadingValue = filled; - - Int2 oldPos = (Console.CursorLeft, Console.CursorTop); - - loadingBarValue = value; - loadingBarColor = color ?? Console.ForegroundColor; - - // Erase last bar. - Console.SetCursorPosition(0, loadingPosition); - Console.Write(new string(' ', lastLoadingBufferSize)); - Console.CursorLeft = 0; - - // Add new bar. - lastLoadingBufferSize = Console.BufferWidth; - - Write(left, newLine: false); - ConsoleColor oldFore = Console.ForegroundColor; - - if (color is not null) Console.ForegroundColor = color.Value; - Write(new string('=', filled), newLine: false); - if (color is not null) Console.ForegroundColor = oldFore; - Write(new string(' ', barSize - filled), newLine: false); - Write(right, newLine: false); - - if (oldPos.y == Console.CursorTop) oldPos.y++; - while (oldPos.y >= Console.BufferHeight) - { - Console.WriteLine(); - oldPos.y--; - loadingPosition--; - } - Console.SetCursorPosition(oldPos.x, oldPos.y); - } - public static void LoadingBarStart(float value = 0, int? position = null, ConsoleColor? color = null) - { - if (loadingPosition != -1) throw new("The loading bar has already been enabled."); - loadingPosition = position ?? Console.CursorTop; - LoadingBarEnabled = true; - LoadingBarSet(value, color); - } - public static void Write(object? message, ConsoleColor? col = null, bool newLine = true) { ConsoleColor prevCol = Console.ForegroundColor; @@ -150,24 +84,45 @@ public static class Tools Console.ForegroundColor = prevCol; - if (newLine && LoadingBarEnabled && Console.CursorTop >= Console.BufferHeight - 1) + if (newLine && LoadingBar.Enabled && Console.CursorTop >= Console.BufferHeight - 1) { - loadingPosition--; - LoadingBarSet(loadingBarValue, loadingBarColor); + LoadingBar.position--; + LoadingBar.Set(LoadingBar.value, LoadingBar.color); } } public static bool ValidateUnsafe() { - Write("You are about to execute an unsafe command.\nProceed? > ", ConsoleColor.DarkYellow, false); + switch (Config.LoadedConfig.RunUnsafeCommands) + { + case AskMode.Always: + Write("[INFO] The shell has been configured to always run unsafe commands. " + + "This can be changed in the config.", ConsoleColor.DarkGray); + return true; - Console.ForegroundColor = ConsoleColor.Yellow; - Console.CursorVisible = true; - string result = Console.ReadLine()!.Trim().ToLower(); - Console.CursorVisible = false; - Console.ResetColor(); + case AskMode.Never: + Write("[ERROR] The shell has been configured to never run unsafe commands. " + + "This can be changed in the config.", ConsoleColor.Red); + return false; - return result == "y" || result == "yes" || result == "t" || - result == "true" || result == "p" || result == "proceed"; + case AskMode.Ask or _: + Write("You are about to execute an unsafe command.\nProceed? > ", ConsoleColor.DarkYellow, false); + Int2 start = (Console.CursorLeft, Console.CursorTop); + Write("\nTip: You can disable this dialog in the config.", ConsoleColor.DarkGray); + int finish = Console.CursorTop; + + Console.SetCursorPosition(start.x, start.y); + + Console.ForegroundColor = ConsoleColor.Yellow; + Console.CursorVisible = true; + string result = Console.ReadLine()!.Trim().ToLower(); + Console.CursorVisible = false; + Console.ResetColor(); + + Console.SetCursorPosition(0, finish); + + return result == "y" || result == "yes" || result == "t" || + result == "true" || result == "p" || result == "proceed"; + } } }