Skip to content

Commit

Permalink
Add aliases to ArgsParser. Bump to 0.8
Browse files Browse the repository at this point in the history
  • Loading branch information
Hexagon committed Mar 24, 2024
1 parent b5840a4 commit 3a9553e
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 16 deletions.
8 changes: 6 additions & 2 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@cross/utils",
"version": "0.7.1",
"version": "0.8.0",
"exports": {
".": "./mod.ts",
"./ansi": "./utils/ansi.ts",
Expand All @@ -12,10 +12,14 @@
},
"imports": {
"@cross/runtime": "jsr:@cross/runtime@^1.0.0",
"@cross/test": "jsr:@cross/test@^0.0.8",
"@cross/test": "jsr:@cross/test@^0.0.9",
"@std/assert": "jsr:@std/assert@^0.220.1"
},
"publish": {
"exclude": [".github", "*.test.ts"]
},
"tasks": {
"check": "deno fmt --check && deno lint && deno check mod.ts && deno doc --lint mod.ts && deno run -A mod.ts --slim --ignore-unused",
"check-deps": "deno run -rA jsr:@check/deps"
}
}
81 changes: 80 additions & 1 deletion utils/args.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ test("Test ArgsParser methods", () => {
];
const parser = new ArgsParser(cmdArgs);

assertEquals(parser.getArray("v"), [true, true]);
assertEquals(parser.getArray("v"), ["", ""]);
assertEquals(parser.get("port"), "8080");
assertEquals(parser.count("config-file"), 1);
assertEquals(parser.count("nonexistent"), 0);
Expand Down Expand Up @@ -169,3 +169,82 @@ test("Handle the '--' delimiter at the end", () => {
assertEquals(parser.getRest(), "");
assertEquals(parser.hasRest(), false);
});

test("Test Boolean Conversion with true values", () => {
const parser = new ArgsParser(["--enabled", "Yes", "-t", "1"]);
assertEquals(parser.getBoolean("enabled"), true);
assertEquals(parser.getBoolean("t"), true);
});

test("Test Boolean Conversion with false values", () => {
const parser = new ArgsParser(["--foo", "NO", "-d", "0"]);
assertEquals(parser.getBoolean("foo"), false);
assertEquals(parser.getBoolean("d"), false);
});

test("Test Boolean Conversion with case sensitivity", () => {
const parser = new ArgsParser(["--Debug", "tRuE", "-e", "No"]);
assertEquals(parser.getBoolean("Debug"), true);
assertEquals(parser.getBoolean("e"), false);
});

test("Test String with case sensitivity", () => {
const parser = new ArgsParser(["--Debug", "tRuE", "-E", "No"]);
assertEquals(parser.get("debug"), undefined);
assertEquals(parser.get("e"), undefined);
});

test("Handle argument aliases", () => {
const cmdArgs = ["--db-host", "localhost", "-p", "3306"];
const options = { aliases: { "db-host": "host", "p": "port" } }; // Define aliases
const parsedArgs = ArgsParser.parseArgs(cmdArgs, options);

assertEquals(parsedArgs, {
args: {
host: ["localhost"],
port: ["3306"],
},
loose: [],
rest: "",
});

const parser = new ArgsParser(cmdArgs, options);
assertEquals(parser.get("host"), "localhost");
assertEquals(parser.get("port"), "3306");
});

test("Aliasing long and short arguments", () => {
const cmdArgs = ["--file", "config.txt", "-d"];
const options = { aliases: { "f": "file", "debug": "d" } };
const parsedArgs = ArgsParser.parseArgs(cmdArgs, options);

assertEquals(parsedArgs, {
args: {
file: ["config.txt"],
d: [true],
},
loose: [],
rest: "",
});

const parser = new ArgsParser(cmdArgs, options);
assertEquals(parser.get("f"), "config.txt");
assertEquals(parser.getBoolean("d"), true);
});

test("Aliases don't override original arguments", () => {
const cmdArgs = ["--file", "config.txt", "-f", "other.txt"];
const options = { aliases: { "f": "file" } };
const parsedArgs = ArgsParser.parseArgs(cmdArgs, options);

assertEquals(parsedArgs, {
args: {
file: ["config.txt", "other.txt"],
},
loose: [],
rest: "",
});

const parser = new ArgsParser(cmdArgs, options);
assertEquals(parser.getArray("file").length, 2);
});
76 changes: 63 additions & 13 deletions utils/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ export function args(all: boolean = false): string[] {
}
}

interface ArgsParserOptions {
aliases?: Record<string, string>;
}

/**
* Provides command-line argument parsing functionality.
*
Expand All @@ -56,6 +60,7 @@ export class ArgsParser {
private readonly parsedArgs: Record<string, (string | boolean)[]>;
private readonly looseArgs: string[];
private readonly restCommand: string = "";
private readonly aliases: Record<string, string> = {};

/**
* Parses command-line arguments.
Expand All @@ -68,6 +73,7 @@ export class ArgsParser {
*/
public static parseArgs(
cmdArgs: string[],
options: ArgsParserOptions = {},
): {
args: Record<string, (string | boolean)[]>;
loose: string[];
Expand All @@ -86,7 +92,11 @@ export class ArgsParser {
collectingRest = true;
} else if (arg.startsWith("--") || arg.startsWith("-")) {
const parts = arg.slice(arg.startsWith("--") ? 2 : 1).split("=");
const key = parts[0];
let key = parts[0];

// Handle aliases
key = options.aliases?.[key] ? options.aliases?.[key] : key;

let value: string | boolean = true; // Default to boolean for flags

if (parts.length > 1) {
Expand Down Expand Up @@ -128,32 +138,71 @@ export class ArgsParser {
* const isVerbose = argsParser.get('verbose');
* const looseArgs = argsParser.getLoose();
*/
constructor(cmdArgs: string[]) {
const result = ArgsParser.parseArgs(cmdArgs);
constructor(cmdArgs: string[], options: ArgsParserOptions = {}) {
if (options?.aliases) {
this.aliases = options.aliases;
}
const result = ArgsParser.parseArgs(cmdArgs, options);
this.parsedArgs = result.args;
this.looseArgs = result.loose;
this.restCommand = result.rest;
}

/**
* Retrieves an array of values associated with a given argument name.
* Retrieves an array of values associated with a given argument name. A boolean arguments will be returned as an empty string.
*
* @param {string} argName The argument name.
* @returns {(string)[]} An array of values. Returns an empty array if the argument is not found.
*/
getArray(argName: string): string[] {
const canonicalArgName = this.aliases[argName] || argName;
const values = this.parsedArgs[canonicalArgName] || []; // Handle undefined case
return values.map((value) => {
if (typeof value === "boolean") {
return ""; // Convert boolean to empty string
} else {
return value ? value.toString() : ""; // Handle all other values, including potential undefined
}
});
}

/**
* Retrieves the first value associated with a given argument name, any string value will be converted to true, except "false" and "no" and "n".
*
* @param {string} argName The argument name.
* @returns {(string | boolean)[]} An array of values. Returns an empty array if the argument is not found.
* @returns {boolean}
*/
getArray(argName: string): (string | boolean)[] {
return this.parsedArgs[argName] || [];
getBoolean(argName: string): boolean {
const canonicalArgName = this.aliases[argName] || argName;
const value = this.parsedArgs[canonicalArgName];
const firstValue = Array.isArray(value) ? value[0] : value;
const isString = value === undefined ? false : true;
if (isString) {
const firstValueClean = firstValue.toString().toLowerCase();
if (
firstValueClean === "no" || firstValueClean === "n" ||
firstValueClean == "false" || firstValueClean == "0"
) {
return false;
} else {
return true;
}
}
return !!firstValue;
}

/**
* Retrieves the first value associated with a given argument name.
* Retrieves the first string value associated with a given argument name, boolean values will be converted to string.
*
* @param {string} argName The argument name.
* @returns {string|boolean|undefined} The first value, or undefined if the argument is not found.
* @returns {string|undefined} The first value, or undefined if the argument is not found.
*/
get(argName: string): string | boolean | undefined {
const value = this.parsedArgs[argName];
return Array.isArray(value) ? value[0] : value;
get(argName: string): string | undefined {
const canonicalArgName = this.aliases[argName] || argName;
console.log(canonicalArgName, this.parsedArgs);
const value = this.parsedArgs[canonicalArgName];
const firstValue = Array.isArray(value) ? value[0] : value;
return firstValue ? firstValue.toString() : undefined;
}

/**
Expand All @@ -163,7 +212,8 @@ export class ArgsParser {
* @returns {number} The number of occurrences (0 if not found).
*/
count(argName: string): number {
const value = this.parsedArgs[argName];
const canonicalArgName = this.aliases[argName] || argName;
const value = this.parsedArgs[canonicalArgName];
return Array.isArray(value) ? value.length : (value ? 1 : 0);
}

Expand Down

0 comments on commit 3a9553e

Please sign in to comment.