diff --git a/.vscode/launch.json b/.vscode/launch.json index 5e9f299..c5b91fc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,18 +5,15 @@ "version": "0.2.0", "configurations": [ { - "name": ".NET Core Launch (console)", + "name": "Argument Parsing", "type": "coreclr", "request": "launch", - "preLaunchTask": "build", "program": "${workspaceFolder}/examples/Args/bin/Debug/net8.0/Args.dll", - "args": ["a", "--bar", "a", "a"], + "args": ["argument", "-ab", "-c"], "cwd": "${workspaceFolder}", "stopAtEntry": false, - "console": "externalTerminal", + "console": "integratedTerminal", + "preLaunchTask": "build Argument parsing Example", } - - - ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 8fe9072..cae51f2 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,15 +1,14 @@ { - "version": "2.0.0", - "tasks": [ - { - "type": "dotnet", - "task": "build", - "group": { - "kind": "build", - "isDefault": true - }, - "problemMatcher": [], - "label": "build" - }, - ] + "version": "2.0.0", + "tasks": [ + { + "label": "build Argument parsing Example", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/examples/Args/Args.csproj" + ], + } + ] } \ No newline at end of file diff --git a/README.md b/README.md index 826e739..f39a6fe 100644 --- a/README.md +++ b/README.md @@ -8,5 +8,6 @@ It also supports logging. - [Colors](https://github.com/dedouwe26/Terminal/tree/main/examples/Colors/Program.cs) (colors and text decoration) - [Keypresses](https://github.com/dedouwe26/Terminal/tree/main/examples/Keypresses/Program.cs) (waiting for keypress, reading keypresses) - [Logging](https://github.com/dedouwe26/Terminal/tree/main/examples/Logging/Program.cs) (also with sub-loggers.) -- [Window](https://github.com/dedouwe26/Terminal/tree/main/examples/Window/Program.cs) (Creates a terminal window, Only works in GUI mode) +- [Assertions](https://github.com/dedouwe26/Terminal/tree/main/examples/Assertion/Program.cs) (one-liners) - [Argument parsing](https://github.com/dedouwe26/Terminal/tree/main/examples/Args/Program.cs) +- [Window](https://github.com/dedouwe26/Terminal/tree/main/examples/Window/Program.cs) (doesn't work) diff --git a/Terminal.sln b/Terminal.sln index 8732767..5a755ce 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -17,6 +17,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Window", "examples\Window\W EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Args", "examples\Args\Args.csproj", "{3F6DC96D-963F-443B-8ABC-FE5B07F3B72D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assertion", "examples\Assertion\Assertion.csproj", "{87F6A6F3-7365-4DA5-8A9B-46F7B24327DD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -50,6 +52,10 @@ Global {3F6DC96D-963F-443B-8ABC-FE5B07F3B72D}.Debug|Any CPU.Build.0 = Debug|Any CPU {3F6DC96D-963F-443B-8ABC-FE5B07F3B72D}.Release|Any CPU.ActiveCfg = Release|Any CPU {3F6DC96D-963F-443B-8ABC-FE5B07F3B72D}.Release|Any CPU.Build.0 = Release|Any CPU + {87F6A6F3-7365-4DA5-8A9B-46F7B24327DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {87F6A6F3-7365-4DA5-8A9B-46F7B24327DD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {87F6A6F3-7365-4DA5-8A9B-46F7B24327DD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {87F6A6F3-7365-4DA5-8A9B-46F7B24327DD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {70C53AE0-A9EB-4551-807A-B4D31599C92E} = {5363993F-0762-4307-9658-82BF54A21CB6} @@ -57,5 +63,6 @@ Global {61D6E9FD-4FDA-4386-AF0A-B54DB90E8210} = {5363993F-0762-4307-9658-82BF54A21CB6} {1C553047-F904-48E8-A84D-7273D71C79DD} = {5363993F-0762-4307-9658-82BF54A21CB6} {3F6DC96D-963F-443B-8ABC-FE5B07F3B72D} = {5363993F-0762-4307-9658-82BF54A21CB6} + {87F6A6F3-7365-4DA5-8A9B-46F7B24327DD} = {5363993F-0762-4307-9658-82BF54A21CB6} EndGlobalSection EndGlobal diff --git a/Terminal/ANSI.cs b/Terminal/ANSI.cs index efdadfa..c7a0c14 100644 --- a/Terminal/ANSI.cs +++ b/Terminal/ANSI.cs @@ -5,11 +5,11 @@ namespace OxDED.Terminal; /// public static class ANSI { /// - /// (1B) + /// (0x1B) /// public const string ESC = "\x1B"; /// - /// CSI + /// CSI (ESC + [) /// public const string CSI = ESC+"["; @@ -24,7 +24,7 @@ public static class ANSI { /// /// Will return as CSI{row};{column}R /// Where CSI is CSI, - /// {row} is the row, + /// {row} is the row and /// {column} is the column. /// public const string RequestCursorPosition = CSI+"6n"; diff --git a/Terminal/Arguments/Argument.cs b/Terminal/Arguments/Argument.cs index ff3dc42..b6e8c45 100644 --- a/Terminal/Arguments/Argument.cs +++ b/Terminal/Arguments/Argument.cs @@ -1,147 +1,22 @@ +using static OxDED.Terminal.Arguments.ArgumentFormatter; + namespace OxDED.Terminal.Arguments; /// -/// Represents an optional argument (-f, --foo). +/// Represents a parsed option. /// -public class Argument : ICloneable, IEquatable { - /// - /// The keys of this argument (f, foo). - /// - public string[] keys; - /// - /// The parameters of this argument. - /// - public ArgumentParameter[] parameters; - /// - /// The description of this argument. - /// - public string? description = null; - /// - /// Creates an argument. - /// - /// The key of this argument. - /// The description of this argument (optional). - /// The parameters of this argument (default: empty). - public Argument(string key, string? description = null, IEnumerable? parameters = null) { - keys = [key]; - this.description = description; - this.parameters = parameters == null ? [] : [.. parameters]; - } +public class Argument { /// - /// Creates an argument. + /// The format of this argument. /// - /// The keys of this argument. - /// The description of this argument (optional). - /// The parameters of this argument (default: empty). - public Argument(IEnumerable keys, string? description = null, IEnumerable? parameters = null) { - this.keys = [.. keys]; - this.description = description; - this.parameters = parameters == null ? [] : [.. parameters]; - } - /// - /// Sets the key of this argument. - /// - /// The new key. - /// This argument. - public Argument Key(string key) { - keys = [key]; - return this; - } + public readonly ArgumentFormat Format; /// - /// Sets the keys of this argument. + /// The content of this argument. /// - /// The new keys. - /// This argument. - public Argument Keys(IEnumerable keys) { - this.keys = [.. keys]; - return this; - } - /// - /// Sets the description of this argument. - /// - /// The new description. - /// This argument. - public Argument Description(string? description) { - this.description = description; - return this; - } - /// - /// Sets the parameters of this argument. - /// - /// The new parameters. - /// This argument. - public Argument Parameters(IEnumerable parameters) { - this.parameters = [.. parameters]; - return this; - } - /// - /// Adds a parameter to this argument. - /// - /// The parameter to add. - /// This argument. - public Argument AddParameter(ArgumentParameter parameter) { - parameters = [.. parameters, parameter]; - return this; - } - /// - /// If this argument's parameters have values (should be yes). - /// - public bool HasValue { get => parameters.All((ArgumentParameter parameter) => parameter.HasValue); } + public readonly string Content; - /// - public static bool operator ==(Argument? left, Argument? right) { - if (left is null && right is null) { - return true; - } else if (left is null) { - return false; - } - return left.Equals(right); - } - /// - public static bool operator !=(Argument? left, Argument? right) { - return !(left == right); - } - /// - /// - /// Checks if the that color is identical to this one. - /// - public bool Equals(Argument? other) { - if (other is null) { - return false; - } - if (ReferenceEquals(this, other)) { - return true; - } - if (GetType() != other.GetType()) { - return false; - } - return keys == other.keys; - } - /// - /// - /// Checks if the that color is identical to this one. - /// - public override bool Equals(object? obj) { - return Equals(obj as Color); - } - /// - public override int GetHashCode() { - return keys.GetHashCode(); - } - /// - /// - /// Calls . - /// - public object Clone() { - return CloneArgument(); - } - - /// - /// Clones this color. - /// - /// The new copy of this color. - /// - public Argument CloneArgument() { - return new Argument(keys, description, parameters); + internal Argument(ArgumentFormat format, string content) { + Format = format; + Content = content; } } \ No newline at end of file diff --git a/Terminal/Arguments/ArgumentFormatter.cs b/Terminal/Arguments/ArgumentFormatter.cs new file mode 100644 index 0000000..b2a7c20 --- /dev/null +++ b/Terminal/Arguments/ArgumentFormatter.cs @@ -0,0 +1,445 @@ +using System.Collections.ObjectModel; + +namespace OxDED.Terminal.Arguments; + +/// +/// Represents a format for arguments and options. +/// +public class ArgumentFormatter { + /// + /// Whether the program should quit when a parsing error occured. + /// + public bool shouldExitOnError = true; + /// + /// The name of this application. Used by the version menu and help menu. + /// + public string? name; + /// + /// The description of this application. Used by the version menu and help menu. + /// + public string? description; + /// + /// The version of this application. Used by the version menu and help menu. + /// + public string? version; + + /// + /// All the categories. + /// + public readonly List Categories = []; + + internal List? allArguments; + /// + /// All the arguments in all categories. + /// + public ReadOnlyCollection AllArguments { get { + if (allArguments == null) { + allArguments = []; + foreach (CategoryFormat category in Categories) { + allArguments.AddRange(category.Arguments); + } + } + return allArguments.AsReadOnly(); + } } + + internal List? allOptions; + /// + /// All the arguments in all categories. + /// + public ReadOnlyCollection AllOptions { get { + if (allOptions == null) { + allOptions = []; + foreach (CategoryFormat category in Categories) { + allOptions.AddRange(category.Options); + } + } + return allOptions.AsReadOnly(); + } } + + private CategoryFormat? generalCategory; + /// + /// The general category. It is going to be created when you call the get method. + /// + public CategoryFormat GeneralCategory { get { + return generalCategory ??= new(this, "General"); + } } + + /// + /// Uses the general category. + /// + /// The category format of the general category. + public CategoryFormat General() { + return GeneralCategory; + } + /// + /// + /// + /// + public CategoryFormat Category() { + return new CategoryFormat(this); + } + + // /// + // /// Creates a new argument format. + // /// + // public ArgumentFormatter() { + // } + + /// + /// Sets whether the program should quit when a parsing error occured. + /// + /// True if it should quit. + /// This argument formatter. + public ArgumentFormatter ShouldExitOnError(bool shouldExit) { + shouldExitOnError = shouldExit; + return this; + } + /// + /// Sets the name of this application. Used by the version menu and help menu. + /// + /// The name of the application. + /// This argument formatter. + public ArgumentFormatter Name(string? name) { + this.name = name; + return this; + } + /// + /// Sets the description of this application. Used by the version menu and help menu. + /// + /// The description of the application. + /// This argument formatter. + public ArgumentFormatter Description(string? description) { + this.description = description; + return this; + } + /// + /// Sets the version of this application. Used by the version menu and help menu. + /// + /// The version of the application. + /// This argument formatter. + public ArgumentFormatter Version(string? version) { + this.version = version; + return this; + } + + /// + /// The currently used help option. + /// + public HelpOptionFormat? CurrentHelpOption { get; internal set; } + /// + /// The currently used version option. + /// + public VersionOptionFormat? CurrentVersionOption { get; internal set; } + + /// + /// Finishes formatting and creates a parser. + /// + /// A new argument parser. + public ArgumentParser Finish() { + return new(this); + } +} + +/// +/// A argument and option category. Used in the help menu. +/// +public class CategoryFormat { + /// + /// The name of the category. + /// + public string name; + /// + /// The arguments of this category. + /// + public readonly List Arguments = []; + /// + /// The arguments of this category. + /// + public readonly List Options = []; + /// + /// The parent format. + /// + public readonly ArgumentFormatter Parent; + + internal CategoryFormat(ArgumentFormatter parent, string name = "Unnamed") { + Parent = parent; + this.name = name; + } + + /// + /// Sets the name of the category. + /// + /// + /// + public CategoryFormat Name(string name) { + this.name = name; + return this; + } + /// + /// Creates a new option. + /// + /// The new option format. + public OptionFormat Option() { + return new(this); + } + /// + /// Creates a new argument. + /// + /// The new argument format. + public ArgumentFormat Argument() { + return new(this); + } + + /// + /// Sets a new help option. + /// + /// The help option format. + public HelpOptionFormat HelpOption() { + return new HelpOptionFormat(this); + } + /// + /// Sets a new version option. + /// + /// The version option format. + public VersionOptionFormat VersionOption() { + return new VersionOptionFormat(this); + } + + /// + /// Finishes and saves this category format. + /// + /// The parent. + public ArgumentFormatter Finish() { + if (!Parent.Categories.Exists((CategoryFormat c) => ReferenceEquals(this, c))) + Parent.Categories.Add(this); + return Parent; + } +} + +/// +/// Represents a format for an argument. +/// +public class ArgumentFormat { + /// + /// The description of this argument. Used by the help menu. + /// + public string description = ""; + + /// + /// The name of this argument. Used by the help menu. + /// + public string name = ""; + + /// + /// The parent format. + /// + public readonly CategoryFormat Parent; + + internal ArgumentFormat(CategoryFormat parent) { + Parent = parent; + } + /// + /// Sets the name of this argument. + /// + /// The new name. + /// This argument. + public ArgumentFormat Name(string name) { + this.name = name; + return this; + } + /// + /// Sets the description of this argument. + /// + /// The new description. + /// This argument. + public ArgumentFormat Description(string description) { + this.description = description; + return this; + } + + /// + /// Finishes and saves this argument format. + /// + /// The parent. + public CategoryFormat Finish() { + Parent.Arguments.Add(this); + Parent.Parent.allArguments = null; + return Parent; + } +} + +/// +/// Represents a format for an option. +/// +public class OptionFormat { + /// + /// All the keys of this option. + /// + public readonly List keys = []; + /// + /// The description of this argument. Used by the help menu. + /// + public string description = ""; + /// + /// All the parameters of this option. + /// + public readonly List parameters = []; + + /// + /// The parent format. + /// + public readonly CategoryFormat Parent; + + internal OptionFormat(CategoryFormat parent) { + Parent = parent; + } + + /// + /// Adds a key to check for. + /// + /// The new key. + /// This option. + public virtual OptionFormat Key(string key) { + keys.Add(key); + return this; + } + /// + /// Adds keys to check for. + /// + /// The new keys to add. + /// This option. + public virtual OptionFormat Keys(IEnumerable keys) { + this.keys.AddRange(keys); + return this; + } + /// + /// Sets the description of this option. + /// + /// The new description. + /// This option. + public virtual OptionFormat Description(string description) { + this.description = description; + return this; + } + /// + /// Adds a new parameter to this option. + /// + /// The parameter format. + public virtual ParameterFormat Parameter() { + return new(this); + } + + /// + /// Finishes and saves this option format. + /// + /// The parent. + public virtual CategoryFormat Finish() { + Parent.Options.Add(this); + Parent.Parent.allOptions = null; + return Parent; + } + /// + /// Represents a format for an option's parameter. + /// + public class ParameterFormat { + /// + /// The name of this parameter. Used by the help menu. + /// + public string name = ""; + /// + /// The description of this argument. Used by the help menu. + /// + public string description = ""; + + /// + /// The parent format. + /// + public readonly OptionFormat OptionFormat; + + internal ParameterFormat(OptionFormat optionBuilder) { + OptionFormat = optionBuilder; + } + + /// + /// Sets the name of this parameter. + /// + /// The new name. + /// This parameter. + public ParameterFormat Name(string name) { + this.name = name; + return this; + } + /// + /// Sets the description of this parameter. + /// + /// The new description. + /// This parameter. + public ParameterFormat Description(string description) { + this.description = description; + return this; + } + + /// + /// Finishes and saves this parameter format. + /// + /// The parent. + public OptionFormat Finish() { + OptionFormat.parameters.Add(this); + return OptionFormat; + } + } +} + +/// +/// Represents a configurable help option. +/// +public class HelpOptionFormat : OptionFormat { + internal HelpOptionFormat(CategoryFormat argumentFormatter) : base(argumentFormatter) { + Keys(["h", "help"]).Description("Displays this help page."); + } + /// + /// Whether the program should quit when this option is used or not. + /// + public bool quit = true; + /// + /// Sets whether the program should quit when this option is used or not. + /// + /// Whether the program should quit when this option is used or not. + /// This option. + public HelpOptionFormat Quit(bool quit) { + this.quit = quit; + return this; + } + /// + public override CategoryFormat Finish() { + Parent.Parent.CurrentHelpOption = this; + return base.Finish(); + } +} + +/// +/// Represents a configurable version option. +/// +public class VersionOptionFormat : OptionFormat { + internal VersionOptionFormat(CategoryFormat argumentFormatter) : base(argumentFormatter) { + Keys(["v", "version"]).Description("Displays the version and description of this program."); + } + /// + /// Whether the program should quit when this option is used or not. + /// + public bool quit = true; + /// + /// Sets whether the program should quit when this option is used or not. + /// + /// Whether the program should quit when this option is used or not. + /// This option. + public VersionOptionFormat Quit(bool quit) { + this.quit = quit; + return this; + } + /// + public override CategoryFormat Finish() { + Parent.Parent.CurrentVersionOption = this; + return base.Finish(); + } +} \ No newline at end of file diff --git a/Terminal/Arguments/ArgumentParameter.cs b/Terminal/Arguments/ArgumentParameter.cs deleted file mode 100644 index c23555e..0000000 --- a/Terminal/Arguments/ArgumentParameter.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace OxDED.Terminal.Arguments; - -/// -/// A parameter for an . -/// -public class ArgumentParameter { - /// - /// The name of this argument parameter. - /// - public string name; - /// - /// The description of this argument parameter. - /// - public string? description; - internal string? value; - /// - /// If this argument parameter has a value (should be yes). - /// - public bool HasValue { get => value != null; } - /// - /// The value of this argument parameter (error if it isn't parsed). - /// - /// - public string Value { get { - if (HasValue) { - return value!; - } else { - throw new InvalidOperationException("This argument parameter has not been parsed."); - } - } } - /// - /// Creates an argument parameter. - /// - /// The name of this parameter. - /// The description of this parameter (optional). - public ArgumentParameter(string name, string? description = null) { - this.name = name; - this.description = description; - } - /// - /// Sets the name of this parameter. - /// - /// The new name of this parameter. - /// This parameter. - public ArgumentParameter Name(string name) { - this.name = name; - return this; - } - /// - /// Sets the description of this parameter. - /// - /// The new description of this parameter. - /// This parameter. - public ArgumentParameter Description(string? description) { - this.description = description; - return this; - } -} \ No newline at end of file diff --git a/Terminal/Arguments/ArgumentParser.cs b/Terminal/Arguments/ArgumentParser.cs index a7d1224..c3b3a92 100644 --- a/Terminal/Arguments/ArgumentParser.cs +++ b/Terminal/Arguments/ArgumentParser.cs @@ -1,420 +1,405 @@ namespace OxDED.Terminal.Arguments; -// TODO: add docs - /// -/// Helps you with parsing arguments. +/// The final stage of argument parsing. /// -public class ArgumentParser -{ - /// - /// This is the name of the application. - /// - public string? name = null; - /// - /// The description of the application. - /// - public string? description = null; - private Argument? versionArgument = null; - private Argument? helpArgument = null; - /// - /// The version of the application. - /// - public string? version = null; - /// - /// The arguments of the parser. - /// - public Dictionary arguments = []; +public class ArgumentParser { /// - /// The positional arguments of the parser. + /// A parser that parses the arguments. /// - public List positionalArguments = []; - /// - /// Sets the help argument of the application parser. - /// - /// - /// - /// - /// - public ArgumentParser Help(IEnumerable? keys = null, bool showDescription = true, bool showVersion = false, bool shouldExit = true) - { - if (helpArgument != null) - { - RemoveArgument(helpArgument); + public class Parser { + /// + /// The identifier that identifies the beginning of an option. + /// + public const char Identifier = '-'; + private readonly ArgumentFormatter format; + /// + /// Creates a new argument parser. + /// + /// The arguments used for parsing. + /// The format of the arguments. + public Parser(string[] arguments, ArgumentFormatter format) { + this.arguments = arguments; + this.format = format; } - helpArgument = new Argument(keys ?? ["h", "help"], "Shows all the available arguments."); - AddArgument(helpArgument, (Argument arg) => - { - WriteHelp(showDescription, showVersion); - if (shouldExit) - { - Environment.Exit(0); + + private int argumentIndex = 0; + private int ArgumentIndex { + get { + return argumentIndex; } - }); - return this; - } - /// - /// Sets the version of the application parser. And adds an argument. - /// - /// The version of the application. - /// The keys for the parameter (default: v, version). - /// - /// - - public ArgumentParser Version(string version, IEnumerable? keys = null, bool shouldExit = true) - { - if (versionArgument != null) - { - RemoveArgument(versionArgument); + set { + if (argumentIndex != value) { + argumentIndex = value; + stream = null; + } + } + } + private StringReader? stream; + private StringReader Stream { + get { + return stream ??= new StringReader(arguments[ArgumentIndex]); + } + } + private readonly string[] arguments; + + /// + /// The parsed options. + /// + public readonly List