Skip to content

Commit

Permalink
Add ArgsParser
Browse files Browse the repository at this point in the history
  • Loading branch information
Hexagon committed Mar 15, 2024
1 parent 72b512c commit 529f074
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 3 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/bun.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Bun CI

on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: antongolub/[email protected]
with:
bun-version: v1.x # Uses latest bun 1
- run: bun x jsr add @cross/test @std/assert @cross/runtime # Installs dependencies
- run: bun test # Runs the tests
22 changes: 22 additions & 0 deletions .github/workflows/node.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Node.js CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:

runs-on: ubuntu-latest

strategy:
matrix:
node-version: [18.x, 21.x]

steps:
- uses: actions/checkout@v3
- run: npx jsr add @cross/test @std/assert @cross/runtime
- run: "echo '{ \"type\": \"module\" }' > package.json" # Needed for tsx to work
- run: npx --yes tsx --test utils/*.test.ts
2 changes: 1 addition & 1 deletion deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"./exit": "./utils/exit.ts"
},
"imports": {
"@cross/runtime": "jsr:@cross/runtime@^0.0.16",
"@cross/runtime": "jsr:@cross/runtime@^0.0.17",
"@cross/test": "jsr:@cross/test@^0.0.8",
"@std/assert": "jsr:@std/assert@^0.219.1"
}
Expand Down
2 changes: 1 addition & 1 deletion mod.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { exit } from "./utils/exit.ts";
export { args } from "./utils/args.ts";
export { args, ArgsParser } from "./utils/args.ts";
export { Colors, Cursor, stripAnsi } from "./utils/ansi.ts";
21 changes: 20 additions & 1 deletion utils/ansi.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,30 @@
import { test } from "@cross/test";
import { assertEquals } from "@std/assert";

import { stripAnsi } from "./ansi.ts";
import { Colors, Cursor, stripAnsi } from "./ansi.ts";

test("Strip ansi characters", () => {
const text =
"\x1b[31mThis is colored\x1b[0m and \x1b[2J\x1b[1;20Hsome more text";
const strippedText = stripAnsi(text);
assertEquals(strippedText, "This is colored and some more text");
});

test("Apply bold formatting", () => {
assertEquals(Colors.bold("hello"), "\x1b[1mhello\x1b[0m");
});

test("Set foreground color (RGB)", () => {
assertEquals(
Colors.rgb(255, 128, 0, "orange"),
"\x1b[38;2;255;128;0morange\x1b[0m",
);
});

test("Set background color (green)", () => {
assertEquals(Colors.bgGreen("grass"), "\x1b[42mgrass\x1b[0m");
});

test("Move cursor up", () => {
assertEquals(Cursor.up(3), "\x1b[3A");
});
15 changes: 15 additions & 0 deletions utils/ansi.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
/**
* This module provides utilities for styling console output and manipulating the cursor, including
* ANSI color codes, text formatting, cursor movement and screen clearing.
*/

/**
* ANSI escape codes for styling text.
* @private
*/
const enum AnsiCodes {
Reset = "\x1b[0m",
Expand Down Expand Up @@ -31,6 +37,10 @@ const enum AnsiCodes {
}

export class Colors {
/**
* Contains methods for applying various ANSI text styles to console output.
*/

/**
* Applies bold formatting to text.
* @param {string} text The text to format.
Expand Down Expand Up @@ -252,6 +262,11 @@ export class Colors {
}

export class Cursor {
/**
* Contains methods for controlling cursor behavior in the console, like movement, visibility,
* or clearing the screen.
*/

/**
* Moves the cursor up a specified number of lines.
* @param {number} lines The number of lines to move up (default: 1).
Expand Down
113 changes: 113 additions & 0 deletions utils/args.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { test } from "@cross/test";
import { assertEquals } from "@std/assert";

import { ArgsParser } from "./args.ts";

test("Parse arguments using space as separator", () => {
const cmdArgs = ["--port", "8080", "-v", "--configFile", "app.config"];
const parsedArgs = ArgsParser.parseArgs(cmdArgs);

assertEquals(parsedArgs, {
args: {
port: ["8080"],
v: [true],
configFile: ["app.config"],
},
loose: [],
});
});

test("Parse arguments using equal sign as separator", () => {
const cmdArgs = ["--arg=asd"];
const parsedArgs = ArgsParser.parseArgs(cmdArgs);

assertEquals(parsedArgs, {
args: {
arg: ["asd"],
},
loose: [],
});
});

test("Handle flags with no values", () => {
const cmdArgs = ["-v", "--debug"];
const parsedArgs = ArgsParser.parseArgs(cmdArgs);

assertEquals(parsedArgs, {
args: {
v: [true],
debug: [true],
},
loose: [],
});
});

test("Handle an argument at the end", () => {
const cmdArgs = ["--port", "8080", "app.config"];
const parsedArgs = ArgsParser.parseArgs(cmdArgs);

assertEquals(parsedArgs, {
args: {
port: ["8080"],
},
loose: ["app.config"],
});
});

test("Handle empty arguments", () => {
const cmdArgs = ["--flag", ""];
const parsedArgs = ArgsParser.parseArgs(cmdArgs);

assertEquals(parsedArgs, {
args: {
flag: [""],
},
loose: [],
});
});

test("Handle arguments with embedded equals signs", () => {
const cmdArgs = ["--path", "/my/path=with/equals"];
const parsedArgs = ArgsParser.parseArgs(cmdArgs);

assertEquals(parsedArgs, {
args: {
path: ["/my/path=with/equals"],
},
loose: [],
});
});

test("Handle multiple occurrences of a flag", () => {
const cmdArgs = ["-v", "-v", "--config", "prod.config"];
const parsedArgs = ArgsParser.parseArgs(cmdArgs);

assertEquals(parsedArgs, {
args: {
v: [true, true],
config: ["prod.config"],
},
loose: [],
});
});

test("Test ArgsParser methods", () => {
const cmdArgs = [
"-v",
"-v",
"--port",
"8080",
"--config-file",
"prod.config",
"file.txt",
];
const parser = new ArgsParser(cmdArgs);

assertEquals(parser.getArray("v"), [true, true]);
assertEquals(parser.get("port"), "8080");
assertEquals(parser.count("config-file"), 1);
assertEquals(parser.count("nonexistent"), 0);

// Add a method to get loose arguments for completeness (optional)
assertEquals(parser.getLoose(), ["file.txt"]);
});
105 changes: 105 additions & 0 deletions utils/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,108 @@ export function args(all: boolean = false): string[] {
return [];
}
}

export class ArgsParser {
private readonly parsedArgs: Record<string, (string | boolean)[]>;
private readonly looseArgs: string[];

/**
* Parses command-line arguments.
*
* @param {string[]} cmdArgs The array of command-line arguments.
*
* @returns {Object} An object with the following properties:
* - args: An object where keys represent argument names and values are their corresponding values.
* - loose: An array of loose arguments.
*/
public static parseArgs(
cmdArgs: string[],
): { args: Record<string, (string | boolean)[]>; loose: string[] } {
const parsedArgs: Record<string, (string | boolean)[]> = {};
const looseArgs: string[] = [];

for (let i = 0; i < cmdArgs.length; i++) {
const arg = cmdArgs[i];
if (arg.startsWith("--") || arg.startsWith("-")) {
const parts = arg.slice(arg.startsWith("--") ? 2 : 1).split("=");
const key = parts[0];
let value: string | boolean = true; // Default to boolean for flags

if (parts.length > 1) {
value = parts[1];
} else if (i + 1 < cmdArgs.length && !cmdArgs[i + 1].startsWith("-")) {
value = cmdArgs[i + 1];
i++;
}

// Handle multiple values for a flag
if (key in parsedArgs) {
const existingValue = parsedArgs[key];
parsedArgs[key] = Array.isArray(existingValue)
? [...existingValue, value]
: [existingValue, value];
} else {
parsedArgs[key] = [value];
}
} else {
looseArgs.push(arg);
}
}

return { args: parsedArgs, loose: looseArgs };
}

constructor(cmdArgs: string[]) {
const result = ArgsParser.parseArgs(cmdArgs);
this.parsedArgs = result.args;
this.looseArgs = result.loose;
}

/**
* Retrieves an array of values associated with a given argument name.
*
* @param {string} argName The argument name.
* @returns {(string | boolean)[]} An array of values. Returns an empty array if the argument is not found.
*/
getArray(argName: string): (string | boolean)[] {
return this.parsedArgs[argName] || [];
}

/**
* Retrieves the first value associated with a given argument name.
*
* @param {string} argName The argument name.
* @returns {string|boolean|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;
}

/**
* Counts the occurrences of a given argument name.
*
* @param {string} argName The argument name.
* @returns {number} The number of occurrences (0 if not found).
*/
count(argName: string): number {
const value = this.parsedArgs[argName];
return Array.isArray(value) ? value.length : (value ? 1 : 0);
}

/**
* Returns an array of loose arguments
* @returns {string[]} An array of loose arguments
*/
getLoose(): string[] {
return this.looseArgs; // Assuming looseArgs is a private array
}

/**
* Counts the number of loose arguments
* @returns {number} The number of loose arguments
*/
countLoose(): number {
return this.looseArgs.length;
}
}

0 comments on commit 529f074

Please sign in to comment.