From 82f5c181292892ad5f4a7b58aea6080cbd4cc2d6 Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 22 Jan 2025 03:58:52 -0700 Subject: [PATCH] Overhaul the argument parsing API --- lib/argparse/Args.dart | 51 ++++++++---- lib/argparse/CLIHelper.dart | 138 +++++++++---------------------- lib/argparse/Parser.dart | 52 ++++++++++++ lib/argparse/types/Bool.dart | 15 +++- lib/argparse/types/Integers.dart | 42 +++++++--- lib/argparse/types/String.dart | 21 +++-- lib/consts.dart | 3 +- pubspec.yaml | 2 +- test/args_test.dart | 36 ++++---- 9 files changed, 207 insertions(+), 153 deletions(-) create mode 100644 lib/argparse/Parser.dart diff --git a/lib/argparse/Args.dart b/lib/argparse/Args.dart index 6dc15e3..e4d0fe7 100644 --- a/lib/argparse/Args.dart +++ b/lib/argparse/Args.dart @@ -3,7 +3,6 @@ import 'package:libac_dart/argparse/types/Integers.dart'; import 'package:libac_dart/argparse/types/String.dart'; abstract class Argument { - bool hasValue = false; String name; String description; @@ -46,53 +45,75 @@ abstract class Argument { } ArgumentType getType(); + + bool hasValue(); } enum ArgumentType { STRING, BOOL, INTEGER, DOUBLE } class Arguments { - Map _args = {}; + Map args = {}; void setArg(Argument arg) { - _args[arg.name] = arg; + args[arg.name] = arg; } - int get count => _args.length; + int get count => args.length; List getArgNames() { - List args = []; - for (MapEntry entry in _args.entries) { - args.add(entry.key); + List argsx = []; + for (MapEntry entry in args.entries) { + argsx.add(entry.key); } - return args; + return argsx; + } + + /// Removes the argument if present. Otherwise does nothing. + /// + /// Returns true if the argument was present and removed. False otherwise. + bool removeArg(String name) { + if (args.containsKey(name)) { + args.remove(name); + return true; + } else + return false; } Argument? getArg(String name) { if (hasArg(name)) - return _args[name]; + return args[name]; else return null; } bool hasArg(String name) { - return _args.containsKey(name); + return args.containsKey(name); } bool argHasValue(String name) { if (hasArg(name)) { Argument arg = getArg(name)!; - return arg.hasValue; + return arg.hasValue(); } else throw new Exception("Warning: No such argument"); } Arguments clone() { - Arguments args = Arguments(); - for (MapEntry entry in _args.entries) { - args.setArg(entry.value.clone()); + Arguments argsx = Arguments(); + for (MapEntry entry in args.entries) { + argsx.setArg(entry.value.clone()); } - return args; + return argsx; + } + + List getArgumentsList() { + List argsx = []; + for (var entry in args.entries) { + argsx.add(entry.value); + } + + return argsx; } } diff --git a/lib/argparse/CLIHelper.dart b/lib/argparse/CLIHelper.dart index 4c9f4f7..17c8e89 100644 --- a/lib/argparse/CLIHelper.dart +++ b/lib/argparse/CLIHelper.dart @@ -1,109 +1,51 @@ -import 'package:libac_dart/argparse/types/Bool.dart'; -import 'package:libac_dart/argparse/types/Integers.dart'; -import 'package:libac_dart/argparse/types/String.dart'; -import 'package:libac_dart/nbt/Stream.dart'; +import 'package:libac_dart/argparse/Args.dart'; -import 'Args.dart'; - -class CLIHelper { - /// Generates usage info +class ArgumentHelpers { + /// Generates a command-line help message for the provided arguments. /// - /// This will create a standard CLI Usage info string that can be directly printed to the console after the program header - static String makeArgCLIHelp(Arguments defaults) { - StringBuilder builder = StringBuilder(); - List argNames = defaults.getArgNames(); + /// [arguments] The list of arguments to generate help for. + /// [programName] The name of the program. + /// Returns a string containing the help message. + static String generateHelpMessage( + List> arguments, String programName) { + final StringBuffer helpMessage = StringBuffer(); + helpMessage.writeln('Usage: $programName [options]'); - for (String name in argNames) { - builder.append("--${name}"); - - Argument arg = defaults.getArg(name)!; - if (arg.hasValue) { - builder.append("=<...>"); - } - - builder.append( - "\t\t\t${arg.description} ${(arg.getValue().toString() == "%" || arg.getType() == ArgumentType.BOOL) ? "" : "[Default: ${arg.getValue()}]"}\n"); + for (var arg in arguments) { + final description = _getArgumentDescription(arg); + final valueType = arg.getType().toString(); + helpMessage.writeln( + ' --${arg.name} [${valueType.split('.').last}] $description'); } - return builder.toString(); + return helpMessage.toString(); } - /// Parses and returns the arguments list with keeping defaults in mind - static Future parseArgs( - List args, Arguments defaults) async { - Arguments arguments = defaults.clone(); - for (int i = 0; i < args.length; i++) { - Argument arg = await _parseArgument(args[i]); - Argument? defArg; - if (defaults.hasArg(arg.name)) defArg = defaults.getArg(arg.name)!; + /// Gets a description for an argument. This can be extended to provide more info. + /// + /// [argument] The argument for which to generate a description. + /// Returns a description of the argument. + static String _getArgumentDescription(Argument argument) { + // You can extend this to add more detailed descriptions for specific arguments + return argument.hasValue() + ? 'Default value: ${argument.getValue()}' + : 'No default value assigned'; + } - if (!arg.hasValue) { - if (defArg != null) { - arg = defArg; - } - } - - arguments.setArg(arg); + /// Retrieves the type of an argument in a human-readable format. + /// + /// [argument] The argument to get the type for. + /// Returns a string describing the argument type. + static String _getArgumentType(Argument argument) { + switch (argument.getType()) { + case ArgumentType.STRING: + return 'string'; + case ArgumentType.INTEGER: + return 'integer'; + case ArgumentType.BOOL: + return 'boolean'; + case ArgumentType.DOUBLE: + return 'double'; } - - return arguments; - } - - static Future _parseArgument(String arg) async { - if (arg.startsWith("--")) { - var parts = arg.split("="); - String name = await _getNamePart(parts[0]); - if (parts.length == 1) - return BoolArgument(name: name); - else if (parts.length == 2) { - String value = await _getValuePart(parts[1]); - ArgumentType typeOfArg = await _getArgumentType(value); - - switch (typeOfArg) { - case ArgumentType.INTEGER: - { - return IntegerArgument(name: name, value: int.parse(value)); - } - case ArgumentType.BOOL: - { - return BoolArgument(name: name); - } - case ArgumentType.DOUBLE: - { - return DoubleArgument(name: name, value: double.parse(value)); - } - case ArgumentType.STRING: - { - return StringArgument(name: name, value: value); - } - } - } else - throw new Exception("Argument is malformed"); - } else - throw new Exception("Not in a valid format"); - } - - static Future _getNamePart(String value) async { - return value.substring(2); - } - - static Future _getValuePart(String entry) async { - return entry; - } - - static Future _getArgumentType(String input) async { - try { - int.parse(input); - return ArgumentType.INTEGER; - } catch (E) {} - try { - double.parse(input); - return ArgumentType.DOUBLE; - } catch (E) {} - - if (input.isEmpty) - return ArgumentType.BOOL; - else - return ArgumentType.STRING; } } diff --git a/lib/argparse/Parser.dart b/lib/argparse/Parser.dart new file mode 100644 index 0000000..3f3e8a4 --- /dev/null +++ b/lib/argparse/Parser.dart @@ -0,0 +1,52 @@ +import 'package:libac_dart/argparse/Args.dart'; +import 'package:libac_dart/argparse/types/Bool.dart'; +import 'package:libac_dart/argparse/types/Integers.dart'; +import 'package:libac_dart/argparse/types/String.dart'; + +class ArgumentParser { + /// Parses a list of arguments. + /// + /// [args] The list of arguments to parse. + /// Returns an Arguments object representing the input. + static Arguments parse(List args) { + final ret = Arguments(); + for (var i = 0; i < args.length; i++) { + final arg = args[i]; + if (arg.startsWith('--')) { + var key = arg.substring(2); // Remove the '--' part of the argument + dynamic value; + + // Check if the argument has a value attached (either --arg=value or --arg value) + if (i + 1 < args.length && !args[i + 1].startsWith('--')) { + value = args[i + 1]; // --arg value + i++; // Skip the next argument as it is the value + } else if (arg.contains('=')) { + value = arg.substring(arg.indexOf('=') + 1); // --arg=value + key = key.substring(0, key.indexOf('=')); + } + + // Determine the argument type and add it to the list + if (int.tryParse(value?.toString() ?? '') != null) { + ret.setArg( + IntegerArgument(name: key, value: int.parse(value.toString()))); + } else if (value?.toString().toLowerCase() == 'true' || + value?.toString().toLowerCase() == 'false' || + value == null) { + if (value != null) + ret.setArg(BoolArgument( + name: key, value: value.toString().toLowerCase() == 'true')); + else + ret.setArg(BoolArgument(name: key, value: true)); + } else if (double.tryParse(value?.toString() ?? '') != null) { + ret.setArg( + DoubleArgument(name: key, value: double.parse(value.toString()))); + } else { + // Default to StringArgument if no matching type is found + ret.setArg(StringArgument(name: key, value: value?.toString() ?? '')); + } + } + } + + return ret; + } +} diff --git a/lib/argparse/types/Bool.dart b/lib/argparse/types/Bool.dart index 8a9adc0..dba7c60 100644 --- a/lib/argparse/types/Bool.dart +++ b/lib/argparse/types/Bool.dart @@ -2,9 +2,13 @@ import 'package:libac_dart/argparse/Args.dart'; class BoolArgument extends Argument { bool _value = false; - BoolArgument({required super.name, super.description = ""}) { - hasValue = false; - _value = true; + bool _hasValue = true; + + BoolArgument({required super.name, bool? value = true}) { + if (value != null) + this._value = value; + else + _hasValue = false; } @override @@ -21,4 +25,9 @@ class BoolArgument extends Argument { String toString() { return "BooleanArgument{ ${name}=${_value} }"; } + + @override + bool hasValue() { + return _hasValue; + } } diff --git a/lib/argparse/types/Integers.dart b/lib/argparse/types/Integers.dart index d1c96c5..1deb389 100644 --- a/lib/argparse/types/Integers.dart +++ b/lib/argparse/types/Integers.dart @@ -1,16 +1,25 @@ import 'package:libac_dart/argparse/Args.dart'; class IntegerArgument extends Argument { - final int value; + int _value = 0; + bool _hasValue = false; - IntegerArgument( - {required super.name, required this.value, super.description = ""}) { - hasValue = value != 0; + IntegerArgument({required super.name, int? value}) { + if (value != null) { + this._value = value; + _hasValue = true; + } else + _hasValue = false; + } + + @override + bool hasValue() { + return _hasValue; } @override int getValue() { - return value; + return _value; } @override @@ -20,16 +29,25 @@ class IntegerArgument extends Argument { @override String toString() { - return "IntegerArgument{ ${name}=${value} }"; + return "IntegerArgument{ ${name}=${_value} }"; } } class DoubleArgument extends Argument { - final double value; + double _value = 0.0; + bool _hasValue = false; - DoubleArgument( - {required super.name, required this.value, super.description = ""}) { - hasValue = value != 0.0; + DoubleArgument({required super.name, double? value}) { + if (value != null) { + _hasValue = true; + _value = value; + } else + _hasValue = false; + } + + @override + bool hasValue() { + return _hasValue; } @override @@ -39,11 +57,11 @@ class DoubleArgument extends Argument { @override double getValue() { - return value; + return _value; } @override String toString() { - return "DoubleArgument{ ${name}=${value} }"; + return "DoubleArgument{ ${name}=${_value} }"; } } diff --git a/lib/argparse/types/String.dart b/lib/argparse/types/String.dart index 5ac7a8f..c2e4ad4 100644 --- a/lib/argparse/types/String.dart +++ b/lib/argparse/types/String.dart @@ -1,16 +1,25 @@ import 'package:libac_dart/argparse/Args.dart'; class StringArgument extends Argument { - final String value; + String _value = ""; + bool _hasValue = false; - StringArgument( - {required super.name, required this.value, super.description}) { - hasValue = value.isNotEmpty; + StringArgument({required super.name, String? value}) { + if (value != null) { + _hasValue = true; + _value = value; + } else + _hasValue = false; + } + + @override + bool hasValue() { + return _hasValue; } @override String getValue() { - return value; + return _value; } @override @@ -20,6 +29,6 @@ class StringArgument extends Argument { @override String toString() { - return "StringArgument{ ${name}=${value} }"; + return "StringArgument{ ${name}=${_value} }"; } } diff --git a/lib/consts.dart b/lib/consts.dart index 185c20e..189300c 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -1,4 +1,5 @@ class Constants { - static const VERSION = "1.3.012225+0304"; + static const VERSION = "1.4.012225+0357"; static const NBT_REVISION = "1.3.012225+0304"; + static const ARGS_REVISION = "1.4.012225+0357"; } diff --git a/pubspec.yaml b/pubspec.yaml index ee74a5c..14dc57e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: libac_dart description: "Aria's Creations code library" -version: 1.3.012225+0304 +version: 1.4.012225+0357 homepage: "https://zontreck.com" environment: diff --git a/test/args_test.dart b/test/args_test.dart index 97873f3..7064880 100644 --- a/test/args_test.dart +++ b/test/args_test.dart @@ -1,6 +1,7 @@ import 'package:libac_dart/argparse/Args.dart'; import 'package:libac_dart/argparse/Builder.dart'; import 'package:libac_dart/argparse/CLIHelper.dart'; +import 'package:libac_dart/argparse/Parser.dart'; import 'package:libac_dart/argparse/types/Bool.dart'; import 'package:libac_dart/argparse/types/String.dart'; import 'package:test/expect.dart'; @@ -8,33 +9,34 @@ import 'package:test/scaffolding.dart'; void main() { test("Test parsing a argument", () async { - List testArgs = ["--test=12", "--enable", "--put=Here"]; + List testArgs = ["--test", "12", "--enable", "--put=Here"]; - Arguments defaults = Arguments(); + Arguments parsed = ArgumentParser.parse(testArgs); - Arguments parsed = await CLIHelper.parseArgs(testArgs, defaults); + expect(parsed.hasArg("test"), true); + expect(parsed.getArg("test")!.getValue() as int, 12); + expect( + parsed.getArg("test")!.getType(), + ArgumentType.INTEGER, + ); - expect(true, parsed.hasArg("test")); - expect(12, parsed.getArg("test")!.getValue() as int); - expect(ArgumentType.INTEGER, parsed.getArg("test")!.getType()); + expect(parsed.hasArg("enable"), true); + expect( + parsed.getArg("enable")!.getType(), + ArgumentType.BOOL, + ); - expect(true, parsed.hasArg("enable")); - expect(ArgumentType.BOOL, parsed.getArg("enable")!.getType()); - - expect("Here", parsed.getArg("put")!.getValue() as String); + expect(parsed.getArg("put")!.getValue() as String, "Here"); }); test("Test printing Usage", () async { Arguments testArgs = ArgumentsBuilder.builder() - .withArgument(BoolArgument( - name: "use_legacy", description: "Uses legacy settings")) - .withArgument(StringArgument( - name: "output", - value: "./out", - description: "Where to put the output files")) + .withArgument(BoolArgument(name: "use_legacy")) + .withArgument(StringArgument(name: "output", value: "./out")) .build(); - String usage = CLIHelper.makeArgCLIHelp(testArgs); + String usage = ArgumentHelpers.generateHelpMessage( + testArgs.getArgumentsList(), "Testsuite"); print("LibAC_Dart\nTestsuite - 1.0\n\n${usage}"); }); }