Overhaul the argument parsing API

This commit is contained in:
zontreck 2025-01-22 03:58:52 -07:00
parent 62d26082c9
commit 82f5c18129
9 changed files with 207 additions and 153 deletions

View file

@ -3,7 +3,6 @@ import 'package:libac_dart/argparse/types/Integers.dart';
import 'package:libac_dart/argparse/types/String.dart'; import 'package:libac_dart/argparse/types/String.dart';
abstract class Argument<T> { abstract class Argument<T> {
bool hasValue = false;
String name; String name;
String description; String description;
@ -46,53 +45,75 @@ abstract class Argument<T> {
} }
ArgumentType getType(); ArgumentType getType();
bool hasValue();
} }
enum ArgumentType { STRING, BOOL, INTEGER, DOUBLE } enum ArgumentType { STRING, BOOL, INTEGER, DOUBLE }
class Arguments { class Arguments {
Map<String, Argument> _args = {}; Map<String, Argument> args = {};
void setArg(Argument arg) { void setArg(Argument arg) {
_args[arg.name] = arg; args[arg.name] = arg;
} }
int get count => _args.length; int get count => args.length;
List<String> getArgNames() { List<String> getArgNames() {
List<String> args = []; List<String> argsx = [];
for (MapEntry<String, Argument> entry in _args.entries) { for (MapEntry<String, Argument> entry in args.entries) {
args.add(entry.key); 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) { Argument? getArg(String name) {
if (hasArg(name)) if (hasArg(name))
return _args[name]; return args[name];
else else
return null; return null;
} }
bool hasArg(String name) { bool hasArg(String name) {
return _args.containsKey(name); return args.containsKey(name);
} }
bool argHasValue(String name) { bool argHasValue(String name) {
if (hasArg(name)) { if (hasArg(name)) {
Argument arg = getArg(name)!; Argument arg = getArg(name)!;
return arg.hasValue; return arg.hasValue();
} else } else
throw new Exception("Warning: No such argument"); throw new Exception("Warning: No such argument");
} }
Arguments clone() { Arguments clone() {
Arguments args = Arguments(); Arguments argsx = Arguments();
for (MapEntry<String, Argument> entry in _args.entries) { for (MapEntry<String, Argument> entry in args.entries) {
args.setArg(entry.value.clone()); argsx.setArg(entry.value.clone());
} }
return args; return argsx;
}
List<Argument> getArgumentsList() {
List<Argument> argsx = [];
for (var entry in args.entries) {
argsx.add(entry.value);
}
return argsx;
} }
} }

View file

@ -1,109 +1,51 @@
import 'package:libac_dart/argparse/types/Bool.dart'; import 'package:libac_dart/argparse/Args.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 'Args.dart'; class ArgumentHelpers {
/// Generates a command-line help message for the provided arguments.
class CLIHelper {
/// Generates usage info
/// ///
/// This will create a standard CLI Usage info string that can be directly printed to the console after the program header /// [arguments] The list of arguments to generate help for.
static String makeArgCLIHelp(Arguments defaults) { /// [programName] The name of the program.
StringBuilder builder = StringBuilder(); /// Returns a string containing the help message.
List<String> argNames = defaults.getArgNames(); static String generateHelpMessage(
List<Argument<dynamic>> arguments, String programName) {
final StringBuffer helpMessage = StringBuffer();
helpMessage.writeln('Usage: $programName [options]');
for (String name in argNames) { for (var arg in arguments) {
builder.append("--${name}"); final description = _getArgumentDescription(arg);
final valueType = arg.getType().toString();
Argument arg = defaults.getArg(name)!; helpMessage.writeln(
if (arg.hasValue) { ' --${arg.name} [${valueType.split('.').last}] $description');
builder.append("=<...>");
}
builder.append(
"\t\t\t${arg.description} ${(arg.getValue().toString() == "%" || arg.getType() == ArgumentType.BOOL) ? "" : "[Default: ${arg.getValue()}]"}\n");
} }
return builder.toString(); return helpMessage.toString();
} }
/// Parses and returns the arguments list with keeping defaults in mind /// Gets a description for an argument. This can be extended to provide more info.
static Future<Arguments> parseArgs( ///
List<String> args, Arguments defaults) async { /// [argument] The argument for which to generate a description.
Arguments arguments = defaults.clone(); /// Returns a description of the argument.
for (int i = 0; i < args.length; i++) { static String _getArgumentDescription(Argument<dynamic> argument) {
Argument arg = await _parseArgument(args[i]); // You can extend this to add more detailed descriptions for specific arguments
Argument? defArg; return argument.hasValue()
if (defaults.hasArg(arg.name)) defArg = defaults.getArg(arg.name)!; ? 'Default value: ${argument.getValue()}'
: 'No default value assigned';
}
if (!arg.hasValue) { /// Retrieves the type of an argument in a human-readable format.
if (defArg != null) { ///
arg = defArg; /// [argument] The argument to get the type for.
} /// Returns a string describing the argument type.
} static String _getArgumentType(Argument<dynamic> argument) {
switch (argument.getType()) {
arguments.setArg(arg); case ArgumentType.STRING:
return 'string';
case ArgumentType.INTEGER:
return 'integer';
case ArgumentType.BOOL:
return 'boolean';
case ArgumentType.DOUBLE:
return 'double';
} }
return arguments;
}
static Future<Argument> _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<String> _getNamePart(String value) async {
return value.substring(2);
}
static Future<String> _getValuePart(String entry) async {
return entry;
}
static Future<ArgumentType> _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;
} }
} }

52
lib/argparse/Parser.dart Normal file
View file

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

View file

@ -2,9 +2,13 @@ import 'package:libac_dart/argparse/Args.dart';
class BoolArgument extends Argument<bool> { class BoolArgument extends Argument<bool> {
bool _value = false; bool _value = false;
BoolArgument({required super.name, super.description = ""}) { bool _hasValue = true;
hasValue = false;
_value = true; BoolArgument({required super.name, bool? value = true}) {
if (value != null)
this._value = value;
else
_hasValue = false;
} }
@override @override
@ -21,4 +25,9 @@ class BoolArgument extends Argument<bool> {
String toString() { String toString() {
return "BooleanArgument{ ${name}=${_value} }"; return "BooleanArgument{ ${name}=${_value} }";
} }
@override
bool hasValue() {
return _hasValue;
}
} }

View file

@ -1,16 +1,25 @@
import 'package:libac_dart/argparse/Args.dart'; import 'package:libac_dart/argparse/Args.dart';
class IntegerArgument extends Argument<int> { class IntegerArgument extends Argument<int> {
final int value; int _value = 0;
bool _hasValue = false;
IntegerArgument( IntegerArgument({required super.name, int? value}) {
{required super.name, required this.value, super.description = ""}) { if (value != null) {
hasValue = value != 0; this._value = value;
_hasValue = true;
} else
_hasValue = false;
}
@override
bool hasValue() {
return _hasValue;
} }
@override @override
int getValue() { int getValue() {
return value; return _value;
} }
@override @override
@ -20,16 +29,25 @@ class IntegerArgument extends Argument<int> {
@override @override
String toString() { String toString() {
return "IntegerArgument{ ${name}=${value} }"; return "IntegerArgument{ ${name}=${_value} }";
} }
} }
class DoubleArgument extends Argument<double> { class DoubleArgument extends Argument<double> {
final double value; double _value = 0.0;
bool _hasValue = false;
DoubleArgument( DoubleArgument({required super.name, double? value}) {
{required super.name, required this.value, super.description = ""}) { if (value != null) {
hasValue = value != 0.0; _hasValue = true;
_value = value;
} else
_hasValue = false;
}
@override
bool hasValue() {
return _hasValue;
} }
@override @override
@ -39,11 +57,11 @@ class DoubleArgument extends Argument<double> {
@override @override
double getValue() { double getValue() {
return value; return _value;
} }
@override @override
String toString() { String toString() {
return "DoubleArgument{ ${name}=${value} }"; return "DoubleArgument{ ${name}=${_value} }";
} }
} }

View file

@ -1,16 +1,25 @@
import 'package:libac_dart/argparse/Args.dart'; import 'package:libac_dart/argparse/Args.dart';
class StringArgument extends Argument<String> { class StringArgument extends Argument<String> {
final String value; String _value = "";
bool _hasValue = false;
StringArgument( StringArgument({required super.name, String? value}) {
{required super.name, required this.value, super.description}) { if (value != null) {
hasValue = value.isNotEmpty; _hasValue = true;
_value = value;
} else
_hasValue = false;
}
@override
bool hasValue() {
return _hasValue;
} }
@override @override
String getValue() { String getValue() {
return value; return _value;
} }
@override @override
@ -20,6 +29,6 @@ class StringArgument extends Argument<String> {
@override @override
String toString() { String toString() {
return "StringArgument{ ${name}=${value} }"; return "StringArgument{ ${name}=${_value} }";
} }
} }

View file

@ -1,4 +1,5 @@
class Constants { 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 NBT_REVISION = "1.3.012225+0304";
static const ARGS_REVISION = "1.4.012225+0357";
} }

View file

@ -1,6 +1,6 @@
name: libac_dart name: libac_dart
description: "Aria's Creations code library" description: "Aria's Creations code library"
version: 1.3.012225+0304 version: 1.4.012225+0357
homepage: "https://zontreck.com" homepage: "https://zontreck.com"
environment: environment:

View file

@ -1,6 +1,7 @@
import 'package:libac_dart/argparse/Args.dart'; import 'package:libac_dart/argparse/Args.dart';
import 'package:libac_dart/argparse/Builder.dart'; import 'package:libac_dart/argparse/Builder.dart';
import 'package:libac_dart/argparse/CLIHelper.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/Bool.dart';
import 'package:libac_dart/argparse/types/String.dart'; import 'package:libac_dart/argparse/types/String.dart';
import 'package:test/expect.dart'; import 'package:test/expect.dart';
@ -8,33 +9,34 @@ import 'package:test/scaffolding.dart';
void main() { void main() {
test("Test parsing a argument", () async { test("Test parsing a argument", () async {
List<String> testArgs = ["--test=12", "--enable", "--put=Here"]; List<String> 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(parsed.hasArg("enable"), true);
expect(12, parsed.getArg("test")!.getValue() as int); expect(
expect(ArgumentType.INTEGER, parsed.getArg("test")!.getType()); parsed.getArg("enable")!.getType(),
ArgumentType.BOOL,
);
expect(true, parsed.hasArg("enable")); expect(parsed.getArg("put")!.getValue() as String, "Here");
expect(ArgumentType.BOOL, parsed.getArg("enable")!.getType());
expect("Here", parsed.getArg("put")!.getValue() as String);
}); });
test("Test printing Usage", () async { test("Test printing Usage", () async {
Arguments testArgs = ArgumentsBuilder.builder() Arguments testArgs = ArgumentsBuilder.builder()
.withArgument(BoolArgument( .withArgument(BoolArgument(name: "use_legacy"))
name: "use_legacy", description: "Uses legacy settings")) .withArgument(StringArgument(name: "output", value: "./out"))
.withArgument(StringArgument(
name: "output",
value: "./out",
description: "Where to put the output files"))
.build(); .build();
String usage = CLIHelper.makeArgCLIHelp(testArgs); String usage = ArgumentHelpers.generateHelpMessage(
testArgs.getArgumentsList(), "Testsuite");
print("LibAC_Dart\nTestsuite - 1.0\n\n${usage}"); print("LibAC_Dart\nTestsuite - 1.0\n\n${usage}");
}); });
} }