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.
This commit is contained in:
Tony Bark 2023-01-08 22:27:21 -05:00
parent 5888771e20
commit 7c3230685b
29 changed files with 580 additions and 354 deletions

View file

@ -1,6 +1,11 @@
# Change Log # 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 - Split versioning systems between kernal and terminal
- Calendar versioning, `YY.MINOR.MICRO`, for kernal - 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. 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)) - Filesystem (based on the Cosmos Wiki [guide](https://csos-guide-to-cosmos.fandom.com/wiki/Getting_Started_-_Materials_and_Setting_Up))
- Semantic versioning - Semantic versioning

View file

@ -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. 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 - Windows 10 or later
- Visual Studio 2022 - Visual Studio 2022

View file

@ -8,9 +8,15 @@ namespace Tomas.Core.Programs;
public class Clear : IProgram public class Clear : IProgram
{ {
public bool Run(IShell shell) public string Name { get; set; }
{
Console.Clear(); public string Description { get; set; }
return true;
} public IEnumerable<IArguments> Arguments { get; set; }
public bool Entry(IShell shell, IEnumerable<KeyValuePair<string, object>> arguments)
{
Console.Clear();
return true;
}
} }

View file

@ -8,7 +8,13 @@ namespace Tomas.Core.Programs;
public class Commands : IProgram public class Commands : IProgram
{ {
public bool Run(IShell shell) public string Name { get; set; }
public string Description { get; set; }
public IEnumerable<IArguments> Arguments { get; set; }
public bool Entry(IShell shell, IEnumerable<KeyValuePair<string, object>> arguments)
{ {
Console.WriteLine($"Commands:"); Console.WriteLine($"Commands:");
var progs = shell.Programs; var progs = shell.Programs;

View file

@ -9,10 +9,10 @@ namespace Tomas.Core.Programs;
public class FenSay : IProgram public class FenSay : IProgram
{ {
/// <summary> /// <summary>
/// Fennec art by Todd Vargo /// Fennec art by Todd Vargo
/// </summary> /// </summary>
const string _fennec = @" \/ const string _fennec = @" \/
/\ /\ /\ /\
//\\_//\\ ____ //\\_//\\ ____
\_ _/ / / \_ _/ / /
@ -23,19 +23,25 @@ public class FenSay : IProgram
[ [ / \/ _/ [ [ / \/ _/
_[ [ \ /_/"; _[ [ \ /_/";
readonly string[] _phrases = readonly string[] _phrases =
{ {
"[SCREAMS IN FENNEC]", "[SCREAMS IN FENNEC]",
"Some people call me a coffee fox.", "Some people call me a coffee fox.",
"Drink Soda. It makes you see faster.", "Drink Soda. It makes you see faster.",
"10/10, Wouldn't Recommend." "10/10, Wouldn't Recommend."
}; };
public bool Run(IShell shell) public string Name { get; set; }
{
var rng = new Random(); public string Description { get; set; }
var phrases = _phrases[rng.Next(_phrases.Length)];
Console.WriteLine($"{phrases}{Environment.NewLine}{_fennec}"); public IEnumerable<IArguments> Arguments { get; set; }
return true;
} public bool Entry(IShell shell, IEnumerable<KeyValuePair<string, object>> arguments)
{
var rng = new Random();
var phrases = _phrases[rng.Next(_phrases.Length)];
Console.WriteLine($"{phrases}{Environment.NewLine}{_fennec}");
return true;
}
} }

View file

@ -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. waivers are not recognized, BSD-3-Clause is enforced.
See the (UN)LICENSE file in the project root for more information. 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; global using Tomas.Interface;

View file

@ -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
{
/// <summary>
/// The base directory for the language files.
/// </summary>
string[] BasePath { get; set; }
/// <summary>
/// Get the text for the given id and key.
/// </summary>
/// <param name="id">The id of the text.</param>
/// <param name="key">The key of the text.</param>
/// <returns>The text for the given id and key.</returns>
string GetText(int id, int key);
}

View file

@ -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; }
}

View file

@ -6,13 +6,20 @@ See the (UN)LICENSE file in the project root for more information.
*/ */
namespace Tomas.Interface; namespace Tomas.Interface;
// Represents a program that can be run by the shell
public interface IProgram public interface IProgram
{ {
/// <summary> // The name of the program
/// The program's main entry point. Boolean behaves as an exit point. string Name { get; }
/// True and False are the equivalent to C's 0 and 1, i.e. "Success" and "Failure," respectfully.
/// </summary> // A description of the program
/// <param name="shell">Allows the program to interact with the shell.</param> string Description { get; }
/// <returns>Exit back to shell.</returns>
bool Run(IShell shell); // The arguments that the program expects
} IEnumerable<IArguments> 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<KeyValuePair<string, object>> arguments);
}

View file

@ -8,7 +8,9 @@ namespace Tomas.Interface;
public interface IShell public interface IShell
{ {
string ReadLine { get; } string ReadLine { get; }
Dictionary<string, IProgram> Programs { get; } Dictionary<string, IProgram> Programs { get; }
IEnumerable<KeyValuePair<string, object>>? ParseArguments(IProgram program, string[] arguments);
} }

View file

@ -1 +1 @@
23.0 23.5

View file

@ -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. waivers are not recognized, BSD-3-Clause is enforced.
See the (UN)LICENSE file in the project root for more information. See the (UN)LICENSE file in the project root for more information.
*/ */
using System.Text.RegularExpressions;
namespace Tomas.Kernel.Globalization; namespace Tomas.Kernel.Globalization;
public class CST public class CST
{ {
const char CARET = '^'; const char CARET = '^';
const string LF = "\u000A"; const string LF = "\u000A";
const string CR = "\u000D"; const string CR = "\u000D";
const string CRLF = "\u000D\u000A"; const string CRLF = "\u000D\u000A";
const string LS = "\u2028"; const string LS = "\u2028";
/// <summary> /// <summary>
/// Gets the value from the digit-based key. /// Gets the value from the digit-based key.
/// </summary> /// </summary>
/// <returns>Returns the entry</returns> /// <returns>Returns the entry</returns>
public static string Parse(string content, int key) => Parse(content, key.ToString()); public static string Parse(string content, int key) => Parse(content, key.ToString());
/// <summary> /// <summary>
/// Gets the value from the string-based key. /// Gets the value from the string-based key.
/// </summary> /// </summary>
/// <returns>Returns the entry</returns> /// <returns>Returns the entry</returns>
public static string Parse(string content, string key) public static string Parse(string content, string key)
{ {
var entries = NormalizeEntries(content); var entries = NormalizeEntries(content);
return GetEntry(entries, key); return GetEntry(entries, key);
} }
/// <summary> /// <summary>
/// Replaces the document's line endings with the native system line endings. /// Replaces the document's line endings with the native system line endings.
/// </summary> /// </summary>
/// <remarks>This stage ensures there are no crashes during parsing.</remarks> /// <remarks>This stage ensures there are no crashes during parsing.</remarks>
/// <param name="content">The content of the document.</param> /// <param name="content">The content of the document.</param>
/// <returns>The document's content with native system line endings.</returns> /// <returns>The document's content with native system line endings.</returns>
static IEnumerable<string> NormalizeEntries(string content) static IEnumerable<string> NormalizeEntries(string content)
{ {
// Check if the document already uses native system line endings. // Check if the document already uses native system line endings.
if (!content.Contains(Environment.NewLine)) if (!content.Contains(Environment.NewLine))
{ {
// If not, check for and replace other line ending types. // If not, check for and replace other line ending types.
if (content.Contains(LF)) if (content.Contains(LF))
content = content.Replace(LF, Environment.NewLine); content = content.Replace(LF, Environment.NewLine);
if (content.Contains(CR)) if (content.Contains(CR))
content = content.Replace(CR, Environment.NewLine); content = content.Replace(CR, Environment.NewLine);
if (content.Contains(CRLF)) if (content.Contains(CRLF))
content = content.Replace(CRLF, Environment.NewLine); content = content.Replace(CRLF, Environment.NewLine);
if (content.Contains(LS)) if (content.Contains(LS))
content = content.Replace(LS, Environment.NewLine); content = content.Replace(LS, Environment.NewLine);
} }
// Split the content by the caret and newline characters. // Split the content by the caret and newline characters.
var lines = content.Split(new[] { $"{CARET}{Environment.NewLine}" }, var lines = content.Split(new[] { $"{CARET}{Environment.NewLine}" },
StringSplitOptions.RemoveEmptyEntries); StringSplitOptions.RemoveEmptyEntries);
// Filter out any lines that start with "//", "#", "/*", or end with "*/". // Filter out any lines that start with "//", "#", "/*", or end with "*/".
return lines.Where(line => return lines.Where(line =>
!line.StartsWith("//") && !line.StartsWith("//") &&
!line.StartsWith("#") && !line.StartsWith("#") &&
!line.StartsWith("/*") && !line.StartsWith("/*") &&
!line.EndsWith("*/")) !line.EndsWith("*/"))
.AsEnumerable(); .AsEnumerable();
} }
/// <summary> // Retrieves the value for the specified key from the given entries.
/// 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.
/// </summary> static string GetEntry(IEnumerable<string> entries, string key)
/// <param name="entries">The entries to search through.</param> {
/// <param name="key">The key to search for.</param> // Iterate through the entries.
/// <returns>The value for the specified key, or a default string if not found.</returns> foreach (var entry in entries)
static string GetEntry(IEnumerable<string> entries, string key) {
{ // If the line doesn't start with the key, keep searching.
// Iterate through the entries. if (!entry.StartsWith(key))
foreach (var entry in entries) continue;
{
// If the line doesn't start with the key, keep searching.
if (!entry.StartsWith(key))
continue;
// Locate the index of the caret character. // Locate the index of the caret character.
var startIndex = entry.IndexOf(CARET); var startIndex = entry.IndexOf(CARET);
// Get the line from the caret character to the end of the string. // Get the line from the caret character to the end of the string.
var line = entry[startIndex..]; var line = entry[startIndex..];
// Return the line with the caret characters trimmed. // Extract the arguments from the entry string using a regular expression
return line.TrimStart(CARET).TrimEnd(CARET); var arguments = Regex.Matches(line, @"%(\d+)").Cast<Match>().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***";
}
} }

View file

@ -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
{
/// <summary>
/// The base directory for the language files.
/// </summary>
string[] BasePath { get; set; }
/// <summary>
/// Get the text for the given id and key.
/// </summary>
/// <param name="id">The id of the text.</param>
/// <param name="key">The key of the text.</param>
/// <returns>The text for the given id and key.</returns>
string GetText(int id, int key);
}

View file

@ -8,97 +8,97 @@ namespace Tomas.Kernel.Globalization;
public class UIText : IUIText public class UIText : IUIText
{ {
/// <summary> /// <summary>
/// The language of the text. /// The language of the text.
/// </summary> /// </summary>
string Language { get; set; } = "english"; string Language { get; set; } = "english";
/// <summary> /// <summary>
/// The base directory for the language files. /// The base directory for the language files.
/// </summary> /// </summary>
public string[] BasePath { get; set; } = { AppContext.BaseDirectory, "uitext" }; public string[] BasePath { get; set; } = { AppContext.BaseDirectory, "translations" };
/// <summary> /// <summary>
/// Constructor for the UIText class. /// Constructor for the UIText class.
/// </summary> /// </summary>
public UIText() { } public UIText() { }
/// <summary> /// <summary>
/// Constructor for the UIText class. /// Constructor for the UIText class.
/// Loads the language file for the specified language. /// Loads the language file for the specified language.
/// </summary> /// </summary>
/// <param name="language">Language to load</param> /// <param name="language">Language to load</param>
public UIText(string language) public UIText(string language)
{ {
Language = language; Language = language;
} }
/// <summary> /// <summary>
/// Constructor for the UIText class. /// Constructor for the UIText class.
/// Loads the language file for the specified language and base directory. /// Loads the language file for the specified language and base directory.
/// </summary> /// </summary>
/// <param name="language">Language to load</param> /// <param name="language">Language to load</param>
/// <param name="basePath">Base directory for the language files.</param> /// <param name="basePath">Base directory for the language files.</param>
public UIText(string language, params string[] baseBath) public UIText(string language, params string[] baseBath)
{ {
Language = language; Language = language;
BasePath = baseBath; BasePath = baseBath;
} }
/// <summary> /// <summary>
/// Get the text for the given id and key. /// Get the text for the given id and key.
/// </summary> /// </summary>
/// <param name="id">The id of the text.</param> /// <param name="id">The id of the text.</param>
/// <param name="key">The key of the text.</param> /// <param name="key">The key of the text.</param>
/// <returns>The text for the given id and key.</returns> /// <returns>The text for the given id and key.</returns>
public string GetText(int id, int key) => GetText(id, key.ToString()); public string GetText(int id, int key) => GetText(id, key.ToString());
/// <summary> /// <summary>
/// Get the text for the given id and key. /// Get the text for the given id and key.
/// </summary> /// </summary>
/// <param name="id">The id of the text.</param> /// <param name="id">The id of the text.</param>
/// <param name="key">The key of the text.</param> /// <param name="key">The key of the text.</param>
/// <returns>The text for the given id and key.</returns> /// <returns>The text for the given id and key.</returns>
public string GetText(int id, string key) public string GetText(int id, string key)
{ {
// Combine the base path and language path to get the full path of the language file. // Combine the base path and language path to get the full path of the language file.
var basePath = Path.Combine(BasePath); var basePath = Path.Combine(BasePath);
var langPath = Path.Combine(basePath, $"{Language}.dir"); var langPath = Path.Combine(basePath, $"{Language}.dir");
// Get all the files in the language directory. // Get all the files in the language directory.
var files = Directory.GetFiles(langPath); var files = Directory.GetFiles(langPath);
// Iterate through the files in the language directory. // Iterate through the files in the language directory.
foreach (var file in files) foreach (var file in files)
{ {
// Skip files that do not have the ".cst" extension. // Skip files that do not have the ".cst" extension.
if (!file.Contains(".cst")) if (!file.Contains(".cst"))
continue; continue;
// Get the id of the current file. // Get the id of the current file.
var ids = Path.GetFileName(file); var ids = Path.GetFileName(file);
var second = ids.IndexOf("_", 1, StringComparison.InvariantCultureIgnoreCase); var second = ids.IndexOf("_", 1, StringComparison.InvariantCultureIgnoreCase);
if (second == -1) if (second == -1)
continue; 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, // If the id of the current file does not match the id passed to the function,
// skip to the next file. // skip to the next file.
if (ids != id.ToString()) if (ids != id.ToString())
continue; continue;
// Read the content of the current file. // Read the content of the current file.
var content = File.ReadAllText(file); var content = File.ReadAllText(file);
// Return the text for the specified key. // Return the text for the specified key.
return CST.Parse(content, key); return CST.Parse(content, key);
} }
// If no text is found, return a default string. // If no text is found, return a default string.
return "***MISSING***"; return "***MISSING***";
} }
} }

View file

@ -22,7 +22,6 @@ public class Kernel : Os.Kernel
Console.WriteLine($"{SysMeta.NAME} booted successfully."); 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() protected override void Run()
{ {
// Run the loop indefinitely // Run the loop indefinitely
@ -34,34 +33,53 @@ public class Kernel : Os.Kernel
// Read a line of input from the user // Read a line of input from the user
var command = shell.ReadLine; 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 // Get the dictionary of programs from the shell
var programs = shell.Programs; var programs = shell.Programs;
// If the command is not a key in the dictionary of programs, print an error message // If the program doesn't exist, display an error message
// and continue to the next iteration of the loop if (!programs.TryGetValue(programName, out var program))
if (!programs.TryGetValue(command, 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; continue;
} }
// Try to run the program and handle any exceptions that may be thrown // Try to run the program and handle any exceptions that may be thrown
try try
{ {
// Run the program and store the returned value in the 'start' variable // Run the program and store the returned value in the 'result' variable
var start = program.Run(shell); var result = program.Entry(shell, parsedArguments);
// Check the value of 'start' and take the appropriate action switch (result)
switch (start)
{ {
case true: case true:
// If 'start' is true, continue to the next iteration of the loop
continue; continue;
case false: case false:
// If 'start' is false, print an error message and continue to the next iteration of the loop
Console.WriteLine("Program closed unexpectedly."); Console.WriteLine("Program closed unexpectedly.");
continue; continue;
} }
} }
catch (Exception err) catch (Exception err)
{ {

View file

@ -8,13 +8,19 @@ namespace Tomas.Kernel.Programs;
public class About : IProgram public class About : IProgram
{ {
public bool Run(IShell shell) public string Name { get; set; }
{
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; public string Description { get; set; }
}
public IEnumerable<IArguments> Arguments { get; set; }
public bool Entry(IShell shell, IEnumerable<KeyValuePair<string, object>> 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;
}
} }

View file

@ -13,13 +13,18 @@ public class Shell : IShell
// A dictionary containing the programs available to the shell, with the keys being the program names // A dictionary containing the programs available to the shell, with the keys being the program names
// and the values being the program objects // and the values being the program objects
public Dictionary<string, IProgram> Programs => new() public Dictionary<string, IProgram> Programs { get; }
public Shell()
{ {
{"about", new About() }, Programs = new Dictionary<string, IProgram>
{"fensay", new FenSay() }, {
{"clear", new Clear() }, {"about", new About() },
{"commands", new Commands() } {"fensay", new FenSay() },
}; {"clear", new Clear() },
{"commands", new Commands() }
};
}
// A property that allows the shell to read a line of input from the user // A property that allows the shell to read a line of input from the user
public string ReadLine public string ReadLine
@ -36,5 +41,132 @@ public class Shell : IShell
return readl; 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<KeyValuePair<string, object>>? ParseArguments(IProgram program, string[] arguments)
{
// Create a dictionary to store the parsed arguments
var parsedArguments = new Dictionary<string, object>();
// 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;
}
}

View file

@ -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. waivers are not recognized, BSD-3-Clause is enforced.
See the (UN)LICENSE file in the project root for more information. 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.Core.Programs;
global using Tomas.Interface; global using Tomas.Interface;
global using Tomas.Interface.Globalization;
global using Tomas.Kernel.Programs; global using Tomas.Kernel.Programs;
global using Cosmos.System.FileSystem; global using Cosmos.System.FileSystem;
global using Cosmos.System.FileSystem.VFS;
global using Tomas.Core;
global using Os = Cosmos.System; global using Os = Cosmos.System;

View file

@ -1 +0,0 @@
0.1

View file

@ -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. waivers are not recognized, BSD-3-Clause is enforced.
See the (UN)LICENSE file in the project root for more information. 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() // Create a new instance of the Shell class
{ var shell = new Shell();
while (true)
{
var shell = new Shell();
var command = shell.ReadLine;
var programs = shell.Programs;
if (!programs.TryGetValue(command, out var program)) // Read a line of input from the user
{ var command = shell.ReadLine;
Console.WriteLine("Command Not Found.");
continue;
}
try // Split the command into words
{ var words = command.Split(' ');
var start = program.Run(shell);
switch (start) // 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: Console.WriteLine($"{programName}: command not found");
continue; continue;
case false:
Console.WriteLine("Program closed unexpectedly.");
continue;
} }
}
catch (Exception err) // Get the arguments
{ var arguments = words.Skip(1).ToArray();
Console.WriteLine(err.Message);
} // 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);
}
}

View file

@ -8,9 +8,14 @@ namespace Tomas.Terminal.Programs;
public class About : IProgram public class About : IProgram
{ {
public bool Run(IShell shell) public string Name { get; set; }
{
Console.WriteLine($"{TermMeta.NAME} Terminal Emulator v{TermMeta.VERSION}"); public string Description { get; set; }
return true;
} public IEnumerable<IArguments> Arguments { get; set; }
public bool Entry(IShell shell, IEnumerable<KeyValuePair<string, object>> arguments)
{
return true;
}
} }

View file

@ -10,9 +10,9 @@ namespace Tomas.Terminal;
public class Shell : IShell public class Shell : IShell
{ {
const char SYMBOL = '$'; const char SYMBOL = '$';
public Dictionary<string, IProgram> Programs => new() public Dictionary<string, IProgram> Programs => new()
{ {
{"about", new About()}, {"about", new About()},
{"fensay", new FenSay()}, {"fensay", new FenSay()},
@ -20,13 +20,18 @@ public class Shell : IShell
{"commands", new Commands()} {"commands", new Commands()}
}; };
public string ReadLine public string ReadLine
{ {
get get
{ {
Console.Write(SYMBOL); Console.Write(SYMBOL);
var readl = Console.ReadLine(); var readl = Console.ReadLine();
return readl; return readl;
} }
} }
public IEnumerable<KeyValuePair<string, object>>? ParseArguments(IProgram program, string[] arguments)
{
throw new NotImplementedException();
}
} }

View file

@ -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;
/// <summary>
/// System metdata, such as name, version and build number.
/// </summary>
public struct TermMeta
{
/// <summary>
/// The name of the operating system.
/// </summary>
public const string NAME = "TOMAS Emulator";
/// <summary>
/// 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.
/// </summary>
public const string VERSION = $"{ThisAssembly.Git.SemVer.Major}.{ThisAssembly.Git.SemVer.Minor}.{ThisAssembly.Git.SemVer.Patch}";
/// <summary>
/// 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.
/// </summary>
public static string BuildNumber = $"Build {BuildNumFromCommit}";
/// <summary>
/// Generates the build number from the commit hash.
/// </summary>
/// <returns>The build number as a uint.</returns>
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;
}
}
}

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
@ -8,10 +8,6 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="GitInfo" Version="2.3.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="NLua" Version="1.6.0" /> <PackageReference Include="NLua" Version="1.6.0" />
</ItemGroup> </ItemGroup>

View file

@ -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. waivers are not recognized, BSD-3-Clause is enforced.
See the (UN)LICENSE file in the project root for more information. 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.Core.Programs;
global using Tomas.Interface; global using Tomas.Interface;

View file

@ -8,9 +8,22 @@ namespace Tomas.Tests.Shell;
internal class MockProgram : IProgram internal class MockProgram : IProgram
{ {
public bool Run(IShell shell) public string Name { get; set; }
public string Description { get; set; }
public IEnumerable<IArguments> Arguments { get; set; }
public bool Entry(IShell shell, IEnumerable<KeyValuePair<string, object>> 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; return true;
} }
} }

View file

@ -14,4 +14,9 @@ internal class MockShell : IShell
{ {
{ "test", new MockProgram() }, { "test", new MockProgram() },
}; };
public IEnumerable<KeyValuePair<string, object>>? ParseArguments(IProgram program, string[] arguments)
{
throw new NotImplementedException();
}
} }

View file

@ -19,8 +19,15 @@ public class ShellTests
// Create a mock program instance // Create a mock program instance
var program = new MockProgram(); var program = new MockProgram();
// Assert that the Run method of the program and returns true when passed the shell object. // Create a dictionary of arguments to pass to the program
Assert.True(program.Run(_mockShell)); var arguments = new Dictionary<string, object>
} {
{"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));
}
} }

View file

@ -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. See the (UN)LICENSE file in the project root for more information.
*/ */
global using Xunit; global using Xunit;
global using System.Diagnostics.CodeAnalysis;
global using System.Diagnostics; global using System.Diagnostics;
global using Tomas.Core;
global using Tomas.Interface; global using Tomas.Interface;