From 7c3230685bf03d738b94cbb6221dbb4429e4f8f7 Mon Sep 17 00:00:00 2001 From: Tony Bark Date: Sun, 8 Jan 2023 22:27:21 -0500 Subject: [PATCH] Rewritten IProgram interface and CST to support arguments. - With the help of ChatGPT, the IProgram interface has been rewritten to handle command line arguments. - Speaking of, the CST parser has been rewritten to finally support arguments with the help of ChatGPT too. - Separately from the ChatGPT changes, the Run method has been renamed to Entry. - Terminal's entry code is now top-level. See ChangeLog.md for more details. --- Changelog.md | 9 +- README.md | 6 +- src/Tomas.Core/Programs/Clear.cs | 16 +- src/Tomas.Core/Programs/Commands.cs | 8 +- src/Tomas.Core/Programs/FenSay.cs | 32 ++-- src/Tomas.Core/Usings.cs | 2 - src/Tomas.Interface/Globalization/IUIText.cs | 23 +++ src/Tomas.Interface/IArguments.cs | 23 +++ src/Tomas.Interface/IProgram.cs | 23 ++- src/Tomas.Interface/IShell.cs | 6 +- src/Tomas.Kernel/GitInfo.txt | 2 +- src/Tomas.Kernel/Globalization/CST.cs | 165 ++++++++++--------- src/Tomas.Kernel/Globalization/IUIText.cs | 23 --- src/Tomas.Kernel/Globalization/UIText.cs | 152 ++++++++--------- src/Tomas.Kernel/Kernel.cs | 40 +++-- src/Tomas.Kernel/Programs/About.cs | 22 ++- src/Tomas.Kernel/Shell.cs | 146 +++++++++++++++- src/Tomas.Kernel/Usings.cs | 5 +- src/Tomas.Terminal/GitInfo.txt | 1 - src/Tomas.Terminal/Program.cs | 91 ++++++---- src/Tomas.Terminal/Programs/About.cs | 15 +- src/Tomas.Terminal/Shell.cs | 27 +-- src/Tomas.Terminal/TermMeta.cs | 52 ------ src/Tomas.Terminal/Tomas.Terminal.csproj | 6 +- src/Tomas.Terminal/Usings.cs | 2 - src/Tomas.Tests/Shell/MockProgram.cs | 17 +- src/Tomas.Tests/Shell/MockShell.cs | 5 + src/Tomas.Tests/ShellTests.cs | 13 +- src/Tomas.Tests/Usings.cs | 2 - 29 files changed, 580 insertions(+), 354 deletions(-) create mode 100644 src/Tomas.Interface/Globalization/IUIText.cs create mode 100644 src/Tomas.Interface/IArguments.cs delete mode 100644 src/Tomas.Kernel/Globalization/IUIText.cs delete mode 100644 src/Tomas.Terminal/GitInfo.txt delete mode 100644 src/Tomas.Terminal/TermMeta.cs diff --git a/Changelog.md b/Changelog.md index 038027e..bd42f86 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,11 @@ # Change Log -## v23.0 +## 23.5 + +- With the help of ChatGPT, the ``IProgram`` interface has been rewritten to handle command line arguments. Being ChatGPT derived, it's still rough around the edges (not sure what to do with ``IArguments`` right now), but it's one hell of a jumping start! +- Speaking of, the CST parser has been rewritten to finally support arguments with the help of ChatGPT too. While I could have always looked at FreeSO's implantation for reference, that code is just awful. It will be ported back upstream ASAP! + +## 23.0 - Split versioning systems between kernal and terminal - Calendar versioning, `YY.MINOR.MICRO`, for kernal @@ -10,7 +15,7 @@ Due to the huge time skip and architectural changes, I've (retroactively) switched to calendar versioning with ``v0.1`` now known as ``v20.1`` as well. -## v20.1 +## 20.1 - Filesystem (based on the Cosmos Wiki [guide](https://csos-guide-to-cosmos.fandom.com/wiki/Getting_Started_-_Materials_and_Setting_Up)) - Semantic versioning diff --git a/README.md b/README.md index 0a66f0d..12e81c7 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,11 @@ TOMAS (**To**ny's **Ma**naged Operating **S**ystem) is a hobby operating system based on the [COSMOS](https://github.com/CosmosOS/Cosmos) framework that comes with a respective terminal emulator. -## Requirements +## Fictional version -### Prerequisites +Within [Casey Universe](https://github.com/tonytins/cugame), TOMAS is an operating system that serves as Anthony's tool to explore the galaxy while working for the Akonos Corporation, a company that builds artificial worlds. He can hack into any system he pleases and uses that + +## Requirements - Windows 10 or later - Visual Studio 2022 diff --git a/src/Tomas.Core/Programs/Clear.cs b/src/Tomas.Core/Programs/Clear.cs index 080ae07..601fd2c 100644 --- a/src/Tomas.Core/Programs/Clear.cs +++ b/src/Tomas.Core/Programs/Clear.cs @@ -8,9 +8,15 @@ namespace Tomas.Core.Programs; public class Clear : IProgram { - public bool Run(IShell shell) - { - Console.Clear(); - return true; - } + public string Name { get; set; } + + public string Description { get; set; } + + public IEnumerable Arguments { get; set; } + + public bool Entry(IShell shell, IEnumerable> arguments) + { + Console.Clear(); + return true; + } } \ No newline at end of file diff --git a/src/Tomas.Core/Programs/Commands.cs b/src/Tomas.Core/Programs/Commands.cs index 70e0348..fef9587 100644 --- a/src/Tomas.Core/Programs/Commands.cs +++ b/src/Tomas.Core/Programs/Commands.cs @@ -8,7 +8,13 @@ namespace Tomas.Core.Programs; public class Commands : IProgram { - public bool Run(IShell shell) + public string Name { get; set; } + + public string Description { get; set; } + + public IEnumerable Arguments { get; set; } + + public bool Entry(IShell shell, IEnumerable> arguments) { Console.WriteLine($"Commands:"); var progs = shell.Programs; diff --git a/src/Tomas.Core/Programs/FenSay.cs b/src/Tomas.Core/Programs/FenSay.cs index 9b227cf..4cbc65c 100644 --- a/src/Tomas.Core/Programs/FenSay.cs +++ b/src/Tomas.Core/Programs/FenSay.cs @@ -9,10 +9,10 @@ namespace Tomas.Core.Programs; public class FenSay : IProgram { - /// - /// Fennec art by Todd Vargo - /// - const string _fennec = @" \/ + /// + /// Fennec art by Todd Vargo + /// + const string _fennec = @" \/ /\ /\ //\\_//\\ ____ \_ _/ / / @@ -23,19 +23,25 @@ public class FenSay : IProgram [ [ / \/ _/ _[ [ \ /_/"; - readonly string[] _phrases = - { + readonly string[] _phrases = + { "[SCREAMS IN FENNEC]", "Some people call me a coffee fox.", "Drink Soda. It makes you see faster.", "10/10, Wouldn't Recommend." }; - public bool Run(IShell shell) - { - var rng = new Random(); - var phrases = _phrases[rng.Next(_phrases.Length)]; - Console.WriteLine($"{phrases}{Environment.NewLine}{_fennec}"); - return true; - } + public string Name { get; set; } + + public string Description { get; set; } + + public IEnumerable Arguments { get; set; } + + public bool Entry(IShell shell, IEnumerable> arguments) + { + var rng = new Random(); + var phrases = _phrases[rng.Next(_phrases.Length)]; + Console.WriteLine($"{phrases}{Environment.NewLine}{_fennec}"); + return true; + } } \ No newline at end of file diff --git a/src/Tomas.Core/Usings.cs b/src/Tomas.Core/Usings.cs index 511e2b8..501a02b 100644 --- a/src/Tomas.Core/Usings.cs +++ b/src/Tomas.Core/Usings.cs @@ -4,6 +4,4 @@ and related or neighboring rights for to this project. In areas where these waivers are not recognized, BSD-3-Clause is enforced. See the (UN)LICENSE file in the project root for more information. */ -global using System.Diagnostics.CodeAnalysis; -global using System.Diagnostics; global using Tomas.Interface; diff --git a/src/Tomas.Interface/Globalization/IUIText.cs b/src/Tomas.Interface/Globalization/IUIText.cs new file mode 100644 index 0000000..2edf101 --- /dev/null +++ b/src/Tomas.Interface/Globalization/IUIText.cs @@ -0,0 +1,23 @@ +/* +In jurisdictions that recognize copyright waivers, I've waived all copyright +and related or neighboring rights for to this project. In areas where these +waivers are not recognized, BSD-3-Clause is enforced. +See the (UN)LICENSE file in the project root for more information. +*/ +namespace Tomas.Interface.Globalization; + +public interface IUIText +{ + /// + /// The base directory for the language files. + /// + string[] BasePath { get; set; } + + /// + /// Get the text for the given id and key. + /// + /// The id of the text. + /// The key of the text. + /// The text for the given id and key. + string GetText(int id, int key); +} diff --git a/src/Tomas.Interface/IArguments.cs b/src/Tomas.Interface/IArguments.cs new file mode 100644 index 0000000..9a1ebfe --- /dev/null +++ b/src/Tomas.Interface/IArguments.cs @@ -0,0 +1,23 @@ +/* +In jurisdictions that recognize copyright waivers, I've waived all copyright +and related or neighboring rights for to this project. In areas where these +waivers are not recognized, BSD-3-Clause is enforced. +See the (UN)LICENSE file in the project root for more information. +*/ +namespace Tomas.Interface; + +// Represents an argument that a program expects +public interface IArguments +{ + // The name of the argument + string Name { get; } + + // A description of the argument + string Description { get; } + + // The type of the argument + Type Type { get; } + + // Whether the argument is required or optional + bool IsRequired { get; } +} diff --git a/src/Tomas.Interface/IProgram.cs b/src/Tomas.Interface/IProgram.cs index dd7cf90..6a117e2 100644 --- a/src/Tomas.Interface/IProgram.cs +++ b/src/Tomas.Interface/IProgram.cs @@ -6,13 +6,20 @@ See the (UN)LICENSE file in the project root for more information. */ namespace Tomas.Interface; +// Represents a program that can be run by the shell public interface IProgram { - /// - /// The program's main entry point. Boolean behaves as an exit point. - /// True and False are the equivalent to C's 0 and 1, i.e. "Success" and "Failure," respectfully. - /// - /// Allows the program to interact with the shell. - /// Exit back to shell. - bool Run(IShell shell); -} \ No newline at end of file + // The name of the program + string Name { get; } + + // A description of the program + string Description { get; } + + // The arguments that the program expects + IEnumerable Arguments { get; } + + // The main entry point of the program + // Takes a shell object to allow the program to interact with the shell, + // and a dictionary of arguments passed to the program by the shell + bool Entry(IShell shell, IEnumerable> arguments); +} diff --git a/src/Tomas.Interface/IShell.cs b/src/Tomas.Interface/IShell.cs index 398eb91..b5882cf 100644 --- a/src/Tomas.Interface/IShell.cs +++ b/src/Tomas.Interface/IShell.cs @@ -8,7 +8,9 @@ namespace Tomas.Interface; public interface IShell { - string ReadLine { get; } + string ReadLine { get; } - Dictionary Programs { get; } + Dictionary Programs { get; } + + IEnumerable>? ParseArguments(IProgram program, string[] arguments); } \ No newline at end of file diff --git a/src/Tomas.Kernel/GitInfo.txt b/src/Tomas.Kernel/GitInfo.txt index 4266d86..b7742e5 100644 --- a/src/Tomas.Kernel/GitInfo.txt +++ b/src/Tomas.Kernel/GitInfo.txt @@ -1 +1 @@ -23.0 \ No newline at end of file +23.5 \ No newline at end of file diff --git a/src/Tomas.Kernel/Globalization/CST.cs b/src/Tomas.Kernel/Globalization/CST.cs index 1063563..deeb5be 100644 --- a/src/Tomas.Kernel/Globalization/CST.cs +++ b/src/Tomas.Kernel/Globalization/CST.cs @@ -4,97 +4,112 @@ and related or neighboring rights for to this project. In areas where these waivers are not recognized, BSD-3-Clause is enforced. See the (UN)LICENSE file in the project root for more information. */ +using System.Text.RegularExpressions; + namespace Tomas.Kernel.Globalization; public class CST { - const char CARET = '^'; - const string LF = "\u000A"; - const string CR = "\u000D"; - const string CRLF = "\u000D\u000A"; - const string LS = "\u2028"; + const char CARET = '^'; + const string LF = "\u000A"; + const string CR = "\u000D"; + const string CRLF = "\u000D\u000A"; + const string LS = "\u2028"; - /// - /// Gets the value from the digit-based key. - /// - /// Returns the entry - public static string Parse(string content, int key) => Parse(content, key.ToString()); + /// + /// Gets the value from the digit-based key. + /// + /// Returns the entry + public static string Parse(string content, int key) => Parse(content, key.ToString()); - /// - /// Gets the value from the string-based key. - /// - /// Returns the entry - public static string Parse(string content, string key) - { - var entries = NormalizeEntries(content); - return GetEntry(entries, key); - } + /// + /// Gets the value from the string-based key. + /// + /// Returns the entry + public static string Parse(string content, string key) + { + var entries = NormalizeEntries(content); + return GetEntry(entries, key); + } - /// - /// Replaces the document's line endings with the native system line endings. - /// - /// This stage ensures there are no crashes during parsing. - /// The content of the document. - /// The document's content with native system line endings. - static IEnumerable NormalizeEntries(string content) - { - // Check if the document already uses native system line endings. - if (!content.Contains(Environment.NewLine)) - { - // If not, check for and replace other line ending types. - if (content.Contains(LF)) - content = content.Replace(LF, Environment.NewLine); + /// + /// Replaces the document's line endings with the native system line endings. + /// + /// This stage ensures there are no crashes during parsing. + /// The content of the document. + /// The document's content with native system line endings. + static IEnumerable NormalizeEntries(string content) + { + // Check if the document already uses native system line endings. + if (!content.Contains(Environment.NewLine)) + { + // If not, check for and replace other line ending types. + if (content.Contains(LF)) + content = content.Replace(LF, Environment.NewLine); - if (content.Contains(CR)) - content = content.Replace(CR, Environment.NewLine); + if (content.Contains(CR)) + content = content.Replace(CR, Environment.NewLine); - if (content.Contains(CRLF)) - content = content.Replace(CRLF, Environment.NewLine); + if (content.Contains(CRLF)) + content = content.Replace(CRLF, Environment.NewLine); - if (content.Contains(LS)) - content = content.Replace(LS, Environment.NewLine); - } + if (content.Contains(LS)) + content = content.Replace(LS, Environment.NewLine); + } - // Split the content by the caret and newline characters. - var lines = content.Split(new[] { $"{CARET}{Environment.NewLine}" }, - StringSplitOptions.RemoveEmptyEntries); + // Split the content by the caret and newline characters. + var lines = content.Split(new[] { $"{CARET}{Environment.NewLine}" }, + StringSplitOptions.RemoveEmptyEntries); - // Filter out any lines that start with "//", "#", "/*", or end with "*/". - return lines.Where(line => - !line.StartsWith("//") && - !line.StartsWith("#") && - !line.StartsWith("/*") && - !line.EndsWith("*/")) - .AsEnumerable(); - } + // Filter out any lines that start with "//", "#", "/*", or end with "*/". + return lines.Where(line => + !line.StartsWith("//") && + !line.StartsWith("#") && + !line.StartsWith("/*") && + !line.EndsWith("*/")) + .AsEnumerable(); + } - /// - /// Retrieves the value for the specified key from the given entries. - /// - /// The entries to search through. - /// The key to search for. - /// The value for the specified key, or a default string if not found. - static string GetEntry(IEnumerable entries, string key) - { - // Iterate through the entries. - foreach (var entry in entries) - { - // If the line doesn't start with the key, keep searching. - if (!entry.StartsWith(key)) - continue; + // Retrieves the value for the specified key from the given entries. + // Replaces any occurrences of % followed by a number with the corresponding argument value extracted from the entry string. + static string GetEntry(IEnumerable entries, string key) + { + // Iterate through the entries. + foreach (var entry in entries) + { + // If the line doesn't start with the key, keep searching. + if (!entry.StartsWith(key)) + continue; - // Locate the index of the caret character. - var startIndex = entry.IndexOf(CARET); - // Get the line from the caret character to the end of the string. - var line = entry[startIndex..]; + // Locate the index of the caret character. + var startIndex = entry.IndexOf(CARET); + // Get the line from the caret character to the end of the string. + var line = entry[startIndex..]; - // Return the line with the caret characters trimmed. - return line.TrimStart(CARET).TrimEnd(CARET); - } + // Extract the arguments from the entry string using a regular expression + var arguments = Regex.Matches(line, @"%(\d+)").Cast().Select(m => m.Groups[1].Value).ToArray(); + + // Replace any occurrences of % followed by a number with the corresponding argument value + for (int i = 0; i < arguments.Length; i++) + { + line = line.Replace($"%{arguments[i]}", GetArgumentValue(arguments[i])); + } + + // Return the line with the caret characters trimmed. + return line.TrimStart(CARET).TrimEnd(CARET); + } + + // If no entry is found, return a default string. + return "***MISSING***"; + } + + // Retrieves the value for the specified argument. + static string GetArgumentValue(string argument) + { + // TODO: Implement logic to get the value for the specified argument. + return "***ARGUMENT VALUE***"; + } - // If no entry is found, return a default string. - return "***MISSING***"; - } } diff --git a/src/Tomas.Kernel/Globalization/IUIText.cs b/src/Tomas.Kernel/Globalization/IUIText.cs deleted file mode 100644 index b570366..0000000 --- a/src/Tomas.Kernel/Globalization/IUIText.cs +++ /dev/null @@ -1,23 +0,0 @@ -/* -In jurisdictions that recognize copyright waivers, I've waived all copyright -and related or neighboring rights for to this project. In areas where these -waivers are not recognized, BSD-3-Clause is enforced. -See the (UN)LICENSE file in the project root for more information. -*/ -namespace Tomas.Kernel.Globalization; - -public interface IUIText -{ - /// - /// The base directory for the language files. - /// - string[] BasePath { get; set; } - - /// - /// Get the text for the given id and key. - /// - /// The id of the text. - /// The key of the text. - /// The text for the given id and key. - string GetText(int id, int key); -} diff --git a/src/Tomas.Kernel/Globalization/UIText.cs b/src/Tomas.Kernel/Globalization/UIText.cs index 4c9c3fe..c05e3ca 100644 --- a/src/Tomas.Kernel/Globalization/UIText.cs +++ b/src/Tomas.Kernel/Globalization/UIText.cs @@ -8,97 +8,97 @@ namespace Tomas.Kernel.Globalization; public class UIText : IUIText { - /// - /// The language of the text. - /// - string Language { get; set; } = "english"; + /// + /// The language of the text. + /// + string Language { get; set; } = "english"; - /// - /// The base directory for the language files. - /// - public string[] BasePath { get; set; } = { AppContext.BaseDirectory, "uitext" }; + /// + /// The base directory for the language files. + /// + public string[] BasePath { get; set; } = { AppContext.BaseDirectory, "translations" }; - /// - /// Constructor for the UIText class. - /// - public UIText() { } + /// + /// Constructor for the UIText class. + /// + public UIText() { } - /// - /// Constructor for the UIText class. - /// Loads the language file for the specified language. - /// - /// Language to load - public UIText(string language) - { - Language = language; - } + /// + /// Constructor for the UIText class. + /// Loads the language file for the specified language. + /// + /// Language to load + public UIText(string language) + { + Language = language; + } - /// - /// Constructor for the UIText class. - /// Loads the language file for the specified language and base directory. - /// - /// Language to load - /// Base directory for the language files. - public UIText(string language, params string[] baseBath) - { - Language = language; - BasePath = baseBath; - } + /// + /// Constructor for the UIText class. + /// Loads the language file for the specified language and base directory. + /// + /// Language to load + /// Base directory for the language files. + public UIText(string language, params string[] baseBath) + { + Language = language; + BasePath = baseBath; + } - /// - /// Get the text for the given id and key. - /// - /// The id of the text. - /// The key of the text. - /// The text for the given id and key. - public string GetText(int id, int key) => GetText(id, key.ToString()); + /// + /// Get the text for the given id and key. + /// + /// The id of the text. + /// The key of the text. + /// The text for the given id and key. + public string GetText(int id, int key) => GetText(id, key.ToString()); - /// - /// Get the text for the given id and key. - /// - /// The id of the text. - /// The key of the text. - /// The text for the given id and key. - public string GetText(int id, string key) - { - // Combine the base path and language path to get the full path of the language file. - var basePath = Path.Combine(BasePath); - var langPath = Path.Combine(basePath, $"{Language}.dir"); + /// + /// Get the text for the given id and key. + /// + /// The id of the text. + /// The key of the text. + /// The text for the given id and key. + public string GetText(int id, string key) + { + // Combine the base path and language path to get the full path of the language file. + var basePath = Path.Combine(BasePath); + var langPath = Path.Combine(basePath, $"{Language}.dir"); - // Get all the files in the language directory. - var files = Directory.GetFiles(langPath); + // Get all the files in the language directory. + var files = Directory.GetFiles(langPath); - // Iterate through the files in the language directory. - foreach (var file in files) - { - // Skip files that do not have the ".cst" extension. - if (!file.Contains(".cst")) - continue; + // Iterate through the files in the language directory. + foreach (var file in files) + { + // Skip files that do not have the ".cst" extension. + if (!file.Contains(".cst")) + continue; - // Get the id of the current file. - var ids = Path.GetFileName(file); - var second = ids.IndexOf("_", 1, StringComparison.InvariantCultureIgnoreCase); + // Get the id of the current file. + var ids = Path.GetFileName(file); + var second = ids.IndexOf("_", 1, StringComparison.InvariantCultureIgnoreCase); - if (second == -1) - continue; + if (second == -1) + continue; - ids = ids.Substring(1, second - 1); + ids = ids.Substring(1, second - 1); - // If the id of the current file does not match the id passed to the function, - // skip to the next file. - if (ids != id.ToString()) - continue; + // If the id of the current file does not match the id passed to the function, + // skip to the next file. + if (ids != id.ToString()) + continue; - // Read the content of the current file. - var content = File.ReadAllText(file); + // Read the content of the current file. + var content = File.ReadAllText(file); - // Return the text for the specified key. - return CST.Parse(content, key); - } + // Return the text for the specified key. + return CST.Parse(content, key); + } - // If no text is found, return a default string. - return "***MISSING***"; - } + // If no text is found, return a default string. + return "***MISSING***"; + } } diff --git a/src/Tomas.Kernel/Kernel.cs b/src/Tomas.Kernel/Kernel.cs index 91048a7..09447c4 100644 --- a/src/Tomas.Kernel/Kernel.cs +++ b/src/Tomas.Kernel/Kernel.cs @@ -22,7 +22,6 @@ public class Kernel : Os.Kernel Console.WriteLine($"{SysMeta.NAME} booted successfully."); } - // This method is the main loop of the kernel, which handles input and runs programs protected override void Run() { // Run the loop indefinitely @@ -34,34 +33,53 @@ public class Kernel : Os.Kernel // Read a line of input from the user var command = shell.ReadLine; + // Split the command into words + var words = command.Split(' '); + + // If there are no words, skip this iteration + if (words.Length == 0) + continue; + + // Get the program name + var programName = words[0]; + // Get the dictionary of programs from the shell var programs = shell.Programs; - // If the command is not a key in the dictionary of programs, print an error message - // and continue to the next iteration of the loop - if (!programs.TryGetValue(command, out var program)) + // If the program doesn't exist, display an error message + if (!programs.TryGetValue(programName, out var program)) { - Console.WriteLine("Command Not Found."); + Console.WriteLine($"{programName}: command not found"); + continue; + } + + // Get the arguments + var arguments = words.Skip(1).ToArray(); + + // Parse and validate the arguments + var parsedArguments = shell.ParseArguments(program, arguments); + if (parsedArguments == null) + { + // If the arguments are invalid, display an error message + Console.WriteLine($"{programName}: invalid arguments"); continue; } // Try to run the program and handle any exceptions that may be thrown try { - // Run the program and store the returned value in the 'start' variable - var start = program.Run(shell); + // Run the program and store the returned value in the 'result' variable + var result = program.Entry(shell, parsedArguments); - // Check the value of 'start' and take the appropriate action - switch (start) + switch (result) { case true: - // If 'start' is true, continue to the next iteration of the loop continue; case false: - // If 'start' is false, print an error message and continue to the next iteration of the loop Console.WriteLine("Program closed unexpectedly."); continue; } + } catch (Exception err) { diff --git a/src/Tomas.Kernel/Programs/About.cs b/src/Tomas.Kernel/Programs/About.cs index 83fcb68..dda50fb 100644 --- a/src/Tomas.Kernel/Programs/About.cs +++ b/src/Tomas.Kernel/Programs/About.cs @@ -8,13 +8,19 @@ namespace Tomas.Kernel.Programs; public class About : IProgram { - public bool Run(IShell shell) - { - Console.WriteLine($"TOMAS v{SysMeta.VERSION} ({SysMeta.BuildNumber}) is a hobby operating system written in C# using the COSMOS framework.{Environment.NewLine}Commands:"); - var progs = shell.Programs; - foreach (var commands in progs.Keys) - Console.WriteLine(commands); + public string Name { get; set; } - return true; - } + public string Description { get; set; } + + public IEnumerable Arguments { get; set; } + + public bool Entry(IShell shell, IEnumerable> arguments) + { + Console.WriteLine($"TOMAS v{SysMeta.VERSION} ({SysMeta.BuildNumber}) is a hobby operating system written in C# using the COSMOS framework.{Environment.NewLine}Commands:"); + var progs = shell.Programs; + foreach (var commands in progs.Keys) + Console.WriteLine(commands); + + return true; + } } \ No newline at end of file diff --git a/src/Tomas.Kernel/Shell.cs b/src/Tomas.Kernel/Shell.cs index 85b109a..503c4cc 100644 --- a/src/Tomas.Kernel/Shell.cs +++ b/src/Tomas.Kernel/Shell.cs @@ -13,13 +13,18 @@ public class Shell : IShell // A dictionary containing the programs available to the shell, with the keys being the program names // and the values being the program objects - public Dictionary Programs => new() + public Dictionary Programs { get; } + + public Shell() { - {"about", new About() }, - {"fensay", new FenSay() }, - {"clear", new Clear() }, - {"commands", new Commands() } - }; + Programs = new Dictionary + { + {"about", new About() }, + {"fensay", new FenSay() }, + {"clear", new Clear() }, + {"commands", new Commands() } + }; + } // A property that allows the shell to read a line of input from the user public string ReadLine @@ -36,5 +41,132 @@ public class Shell : IShell return readl; } } -} + public void Run() + { + while (true) + { + // Read a line of input from the user + var input = ReadLine; + + // Split the input into words + var words = input.Split(' '); + + // If there are no words, skip this iteration + if (words.Length == 0) + continue; + + // Get the program name + var programName = words[0]; + + // Get the arguments + var arguments = words.Skip(1).ToArray(); + + // Check if the program exists + if (!Programs.TryGetValue(programName, out var program)) + { + // If the program doesn't exist, display an error message + Console.WriteLine($"{programName}: command not found"); + continue; + } + + // Parse and validate the arguments + var parsedArguments = ParseArguments(program, arguments); + if (parsedArguments == null) + { + // If the arguments are invalid, display an error message + Console.WriteLine($"{programName}: invalid arguments"); + continue; + } + + // Run the program + var result = program.Entry(this, parsedArguments); + + // Handle the result + if (result) + { + // If the program was successful, display a success message + Console.WriteLine($"{programName}: success"); + } + else + { + // If the program failed, display a failure message + Console.WriteLine($"{programName}: failure"); + } + } + } + + public IEnumerable>? ParseArguments(IProgram program, string[] arguments) + { + // Create a dictionary to store the parsed arguments + var parsedArguments = new Dictionary(); + + // Create a list of required arguments + var requiredArguments = program.Arguments.Where(x => x.IsRequired).ToList(); + + // Iterate over the arguments + for (int i = 0; i < arguments.Length; i++) + { + // Get the current argument + var argument = arguments[i]; + + // Check if the argument is a flag or a value + if (argument.StartsWith("-")) + { + // If it's a flag, get the flag name and value + var flagName = argument.Substring(1); + object flagValue = true; + if (flagName.EndsWith("=")) + { + // If the flag has a value, extract it + var flagNameValue = flagName.Split('='); + flagName = flagNameValue[0]; + flagValue = flagNameValue[1]; + } + + // Get the argument definition + var argumentDefinition = program.Arguments.FirstOrDefault(x => x.Name == flagName); + if (argumentDefinition == null) + { + // If the argument is not defined, return null + return null; + } + + // Add the argument to the dictionary + parsedArguments[flagName] = flagValue; + + // Remove the argument from the required arguments list + requiredArguments.Remove(argumentDefinition); + } + else + { + // If it's a value, check if there are any required arguments left + if (requiredArguments.Count == 0) + { + // If there are no required arguments left, return null + return null; + } + + // Get the next required argument + var requiredArgument = requiredArguments[0]; + + // Convert the value to the correct type + var value = Convert.ChangeType(argument, requiredArgument.Type); + + // Add the argument to the dictionary + parsedArguments[requiredArgument.Name] = value; + + // Remove the argument from the required arguments list + requiredArguments.RemoveAt(0); + } + } + + // If there are any required arguments left, return null + if (requiredArguments.Count > 0) + return null; + + // Return the parsed arguments + return parsedArguments; + } + +} \ No newline at end of file diff --git a/src/Tomas.Kernel/Usings.cs b/src/Tomas.Kernel/Usings.cs index b1bfac8..f734689 100644 --- a/src/Tomas.Kernel/Usings.cs +++ b/src/Tomas.Kernel/Usings.cs @@ -4,12 +4,9 @@ and related or neighboring rights for to this project. In areas where these waivers are not recognized, BSD-3-Clause is enforced. See the (UN)LICENSE file in the project root for more information. */ -global using System.Diagnostics.CodeAnalysis; -global using System.Diagnostics; global using Tomas.Core.Programs; global using Tomas.Interface; +global using Tomas.Interface.Globalization; global using Tomas.Kernel.Programs; global using Cosmos.System.FileSystem; -global using Cosmos.System.FileSystem.VFS; -global using Tomas.Core; global using Os = Cosmos.System; \ No newline at end of file diff --git a/src/Tomas.Terminal/GitInfo.txt b/src/Tomas.Terminal/GitInfo.txt deleted file mode 100644 index ceab6e1..0000000 --- a/src/Tomas.Terminal/GitInfo.txt +++ /dev/null @@ -1 +0,0 @@ -0.1 \ No newline at end of file diff --git a/src/Tomas.Terminal/Program.cs b/src/Tomas.Terminal/Program.cs index f95cf7d..e40ca29 100644 --- a/src/Tomas.Terminal/Program.cs +++ b/src/Tomas.Terminal/Program.cs @@ -4,40 +4,69 @@ and related or neighboring rights for to this project. In areas where these waivers are not recognized, BSD-3-Clause is enforced. See the (UN)LICENSE file in the project root for more information. */ -namespace Tomas.Terminal; -class Program +// Run the loop indefinitely +using Tomas.Terminal; + +while (true) { - static void Main() - { - while (true) - { - var shell = new Shell(); - var command = shell.ReadLine; - var programs = shell.Programs; + // Create a new instance of the Shell class + var shell = new Shell(); - if (!programs.TryGetValue(command, out var program)) - { - Console.WriteLine("Command Not Found."); - continue; - } + // Read a line of input from the user + var command = shell.ReadLine; - try - { - var start = program.Run(shell); - switch (start) + // Split the command into words + var words = command.Split(' '); + + // If there are no words, skip this iteration + if (words.Length == 0) + continue; + + // Get the program name + var programName = words[0]; + + // Get the dictionary of programs from the shell + var programs = shell.Programs; + + // If the program doesn't exist, display an error message + if (!programs.TryGetValue(programName, out var program)) { - case true: - continue; - case false: - Console.WriteLine("Program closed unexpectedly."); - continue; + Console.WriteLine($"{programName}: command not found"); + continue; } - } - catch (Exception err) - { - Console.WriteLine(err.Message); - } - } - } -} \ No newline at end of file + + // Get the arguments + var arguments = words.Skip(1).ToArray(); + + // Parse and validate the arguments + var parsedArguments = shell.ParseArguments(program, arguments); + if (parsedArguments == null) + { + // If the arguments are invalid, display an error message + Console.WriteLine($"{programName}: invalid arguments"); + continue; + } + + // Try to run the program and handle any exceptions that may be thrown + try + { + // Run the program and store the returned value in the 'result' variable + var result = program.Entry(shell, parsedArguments); + + switch (result) + { + case true: + continue; + case false: + Console.WriteLine("Program closed unexpectedly."); + continue; + } + + } + catch (Exception err) + { + // If an exception is caught, print the error message and continue to the next iteration of the loop + Console.WriteLine(err.Message); + } +} diff --git a/src/Tomas.Terminal/Programs/About.cs b/src/Tomas.Terminal/Programs/About.cs index b6cb3b7..f1f9209 100644 --- a/src/Tomas.Terminal/Programs/About.cs +++ b/src/Tomas.Terminal/Programs/About.cs @@ -8,9 +8,14 @@ namespace Tomas.Terminal.Programs; public class About : IProgram { - public bool Run(IShell shell) - { - Console.WriteLine($"{TermMeta.NAME} Terminal Emulator v{TermMeta.VERSION}"); - return true; - } + public string Name { get; set; } + + public string Description { get; set; } + + public IEnumerable Arguments { get; set; } + + public bool Entry(IShell shell, IEnumerable> arguments) + { + return true; + } } \ No newline at end of file diff --git a/src/Tomas.Terminal/Shell.cs b/src/Tomas.Terminal/Shell.cs index f9a3989..8c5726d 100644 --- a/src/Tomas.Terminal/Shell.cs +++ b/src/Tomas.Terminal/Shell.cs @@ -10,9 +10,9 @@ namespace Tomas.Terminal; public class Shell : IShell { - const char SYMBOL = '$'; + const char SYMBOL = '$'; - public Dictionary Programs => new() + public Dictionary Programs => new() { {"about", new About()}, {"fensay", new FenSay()}, @@ -20,13 +20,18 @@ public class Shell : IShell {"commands", new Commands()} }; - public string ReadLine - { - get - { - Console.Write(SYMBOL); - var readl = Console.ReadLine(); - return readl; - } - } + public string ReadLine + { + get + { + Console.Write(SYMBOL); + var readl = Console.ReadLine(); + return readl; + } + } + + public IEnumerable>? ParseArguments(IProgram program, string[] arguments) + { + throw new NotImplementedException(); + } } \ No newline at end of file diff --git a/src/Tomas.Terminal/TermMeta.cs b/src/Tomas.Terminal/TermMeta.cs deleted file mode 100644 index 8451cdb..0000000 --- a/src/Tomas.Terminal/TermMeta.cs +++ /dev/null @@ -1,52 +0,0 @@ -/* -In jurisdictions that recognize copyright waivers, I've waived all copyright -and related or neighboring rights for to this project. In areas where these -waivers are not recognized, BSD-3-Clause is enforced. -See the (UN)LICENSE file in the project root for more information. -*/ -using System.Text; - -namespace Tomas.Terminal; - -/// -/// System metdata, such as name, version and build number. -/// -public struct TermMeta -{ - /// - /// The name of the operating system. - /// - public const string NAME = "TOMAS Emulator"; - - /// - /// The version of the operating system, in the Calendar Versioning format: "yy.minor.patch". - /// The year, minor, and patch version numbers are automatically extracted from the Git repository - /// using the ThisAssembly.Git.SemVer object. - /// - public const string VERSION = $"{ThisAssembly.Git.SemVer.Major}.{ThisAssembly.Git.SemVer.Minor}.{ThisAssembly.Git.SemVer.Patch}"; - - /// - /// The build number of the operating system, generated from the commit hash. - /// The build number is a 6-digit number, with the first 3 digits being the first 3 digits of the commit hash - /// converted to a uint, and the last 3 digits being the last 3 digits of the commit hash converted to a uint. - /// - public static string BuildNumber = $"Build {BuildNumFromCommit}"; - - /// - /// Generates the build number from the commit hash. - /// - /// The build number as a uint. - static uint BuildNumFromCommit - { - get - { - // Get the bytes of the commit hash as a UTF-8 encoded string - var commit = Encoding.UTF8.GetBytes(ThisAssembly.Git.Commit); - - // Convert the first 4 bytes of the commit hash to a uint and return it modulo 1000000 - // (this will give us a 6-digit number with the first 3 digits being the first 3 digits of the commit hash - // and the last 3 digits being the last 3 digits of the commit hash) - return BitConverter.ToUInt32(commit, 0) % 1000000; - } - } -} diff --git a/src/Tomas.Terminal/Tomas.Terminal.csproj b/src/Tomas.Terminal/Tomas.Terminal.csproj index 91627f1..417b114 100644 --- a/src/Tomas.Terminal/Tomas.Terminal.csproj +++ b/src/Tomas.Terminal/Tomas.Terminal.csproj @@ -1,4 +1,4 @@ - + Exe @@ -8,10 +8,6 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/src/Tomas.Terminal/Usings.cs b/src/Tomas.Terminal/Usings.cs index bbcdb99..d08f99e 100644 --- a/src/Tomas.Terminal/Usings.cs +++ b/src/Tomas.Terminal/Usings.cs @@ -4,7 +4,5 @@ and related or neighboring rights for to this project. In areas where these waivers are not recognized, BSD-3-Clause is enforced. See the (UN)LICENSE file in the project root for more information. */ -global using System.Diagnostics.CodeAnalysis; -global using System.Diagnostics; global using Tomas.Core.Programs; global using Tomas.Interface; \ No newline at end of file diff --git a/src/Tomas.Tests/Shell/MockProgram.cs b/src/Tomas.Tests/Shell/MockProgram.cs index 3077976..d48f233 100644 --- a/src/Tomas.Tests/Shell/MockProgram.cs +++ b/src/Tomas.Tests/Shell/MockProgram.cs @@ -8,9 +8,22 @@ namespace Tomas.Tests.Shell; internal class MockProgram : IProgram { - public bool Run(IShell shell) + public string Name { get; set; } + + public string Description { get; set; } + + public IEnumerable Arguments { get; set; } + + public bool Entry(IShell shell, IEnumerable> arguments) { - Debug.WriteLine("Test Program."); + // Iterate through the arguments + foreach (var argument in arguments) + { + // Print the argument name and value + Debug.WriteLine($"Argument name: {argument.Key}, Argument value: {argument.Value}"); + } + + // Return true to indicate success return true; } } diff --git a/src/Tomas.Tests/Shell/MockShell.cs b/src/Tomas.Tests/Shell/MockShell.cs index 0ea450a..d52f527 100644 --- a/src/Tomas.Tests/Shell/MockShell.cs +++ b/src/Tomas.Tests/Shell/MockShell.cs @@ -14,4 +14,9 @@ internal class MockShell : IShell { { "test", new MockProgram() }, }; + + public IEnumerable>? ParseArguments(IProgram program, string[] arguments) + { + throw new NotImplementedException(); + } } diff --git a/src/Tomas.Tests/ShellTests.cs b/src/Tomas.Tests/ShellTests.cs index 0f7c04f..f376988 100644 --- a/src/Tomas.Tests/ShellTests.cs +++ b/src/Tomas.Tests/ShellTests.cs @@ -19,8 +19,15 @@ public class ShellTests // Create a mock program instance var program = new MockProgram(); - // Assert that the Run method of the program and returns true when passed the shell object. - Assert.True(program.Run(_mockShell)); - } + // Create a dictionary of arguments to pass to the program + var arguments = new Dictionary + { + {"arg1", "value1"}, + {"arg2", 123}, + {"arg3", true}, + }; + // Assert that the Run method of the program returns true when passed the shell object and the arguments dictionary. + Assert.True(program.Entry(_mockShell, arguments)); + } } \ No newline at end of file diff --git a/src/Tomas.Tests/Usings.cs b/src/Tomas.Tests/Usings.cs index d97cace..e6a117b 100644 --- a/src/Tomas.Tests/Usings.cs +++ b/src/Tomas.Tests/Usings.cs @@ -5,7 +5,5 @@ waivers are not recognized, BSD-3-Clause is enforced. See the (UN)LICENSE file in the project root for more information. */ global using Xunit; -global using System.Diagnostics.CodeAnalysis; global using System.Diagnostics; -global using Tomas.Core; global using Tomas.Interface; \ No newline at end of file