Skip to content

Commit

Permalink
Merge pull request #23 from BenoitZugmeyer/drop-commander
Browse files Browse the repository at this point in the history
deps: drop commander
  • Loading branch information
BenoitZugmeyer authored Sep 9, 2024
2 parents 31c2065 + 1daf6ea commit d2e7e4a
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 57 deletions.
1 change: 0 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.25",
"cardinal": "^2.1.1",
"commander": "^2.20.0",
"terser": "^5.31.6"
},
"devDependencies": {
Expand Down
29 changes: 12 additions & 17 deletions src/__tests__/parseArguments.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,38 +10,33 @@ test("parseArguments", (t: TestContext) => {
});
t.assert.snapshot(stdout);
t.assert.deepStrictEqual(exit, {
status: undefined,
status: 1,
});
}

{
const { stdout, exit } = withStdout(() => {
parseArguments(`x x --version`.split(" "));
parseArguments(`--version`.split(" "));
});
t.assert.deepStrictEqual(exit, { status: 0 });
t.assert.match(stdout, /^\d+\.\d+\.\d+.*\n$/);
}

t.assert.deepStrictEqual(
parseArguments(`x x https://foo.com:1:1`.split(" ")),
{
debug: undefined,
sourceURL: "https://foo.com",
position: { line: 1, column: 1 },
beforeContext: undefined,
afterContext: undefined,
useSourceMap: true,
},
);
t.assert.deepStrictEqual(parseArguments(`https://foo.com:1:1`.split(" ")), {
debug: false,
sourceURL: "https://foo.com",
position: { line: 1, column: 1 },
beforeContext: 5,
afterContext: 5,
useSourceMap: true,
});

const parsedArguments = parseArguments(
`x x -C 2 https://foo.com:1:1`.split(" "),
);
const parsedArguments = parseArguments(`-C 2 https://foo.com:1:1`.split(" "));
t.assert.strictEqual(parsedArguments.beforeContext, 2);
t.assert.strictEqual(parsedArguments.afterContext, 2);

t.assert.strictEqual(
parseArguments(`x x -d https://foo.com:1:1`.split(" ")).debug,
parseArguments(`-d https://foo.com:1:1`.split(" ")).debug,
true,
);
});
Expand Down
154 changes: 117 additions & 37 deletions src/parseArguments.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,143 @@
import commander from "commander";
import { readFileSync } from "fs";
import { parseArgs } from "node:util";

import CLIError from "./CLIError.ts";
import type { Configuration } from "./types.ts";
import { fileURLToPath } from "node:url";

export default function parseArguments(argv = process.argv): Configuration {
const pkg = getPackageInfos();
const program = new commander.Command();
program.name(pkg.name);
program.description(pkg.description);
program.option(
"-A, --after-context <num>",
"print <num> lines of trailing context after the selected line",
parseInteger,
);
program.option(
"-B, --before-context <num>",
"print <num> lines of leading context before the selected line",
parseInteger,
);
program.option(
"-C, --context <num>",
"print <num> lines of leading and trailing context surrounding the selected line",
parseInteger,
);
program.option("--no-source-map", "don't try to use a source map");
program.option("-d, --debug", "output extra debugging");
program.version(pkg.version);
program.arguments("<URL:LINE:COLUMN>");
program.parse(argv);
const OPTIONS = {
"after-context": {
type: "string",
short: "A",
description:
"print <num> lines of trailing context after the selected line",
},
"before-context": {
type: "string",
short: "B",
description:
"print <num> lines of leading context before the selected line",
},
context: {
type: "string",
short: "C",
description:
"print <num> lines of leading and trailing context surrounding the selected line",
default: "5",
},
"no-source-map": {
type: "boolean",
description: "don't try to use a source map",
default: false,
},
debug: {
type: "boolean",
short: "d",
description: "output extra debugging",
default: false,
},
version: {
type: "boolean",
short: "V",
description: "output the version number",
default: false,
},
help: {
type: "boolean",
short: "h",
description: "output usage information",
default: false,
},
} as const;

export default function parseArguments(
args = process.argv.slice(2),
): Configuration {
const parseArgsConfig = {
args,
options: OPTIONS,
allowPositionals: true,
} as const;
let parseArgsResult: ReturnType<typeof parseArgs<typeof parseArgsConfig>>;
try {
parseArgsResult = parseArgs(parseArgsConfig);
} catch (error) {
if (error instanceof Error) {
throw new CLIError(error.message);
}
throw error;
}

const { values, positionals } = parseArgsResult;

if (values.version) {
const pkg = getPackageInfos();
console.log(pkg.version);
process.exit(0);
}

const arg = program.args[0];
if (!arg) program.help();
if (values.help) {
printHelp();
process.exit(0);
}

const arg = positionals[0];
if (!arg) {
printHelp();
process.exit(1);
}

const matches = /^(.*):(\d+):(\d+)$/.exec(arg);
if (!matches) {
program.help();
throw "unreachable";
printHelp();
process.exit(1);
}

const [, sourceURL, line, column] = matches;

const opts = program.opts();
const beforeContext =
opts.beforeContext !== undefined ? opts.beforeContext : opts.context;
const afterContext =
opts.afterContext !== undefined ? opts.afterContext : opts.context;
const beforeContext = parseInteger(
values["before-context"] !== undefined
? values["before-context"]
: values.context!,
);
const afterContext = parseInteger(
values["after-context"] !== undefined
? values["after-context"]
: values.context!,
);

return {
debug: program.debug,
debug: values.debug!,
sourceURL,
position: { line: Number(line), column: Number(column) },
beforeContext,
afterContext,
useSourceMap: program.sourceMap,
useSourceMap: !values["no-source-map"]!,
};
}

function printHelp() {
const pkg = getPackageInfos();
let message = `\
Usage: ${pkg.name} [options] <URL:LINE:COLUMN>
${pkg.description}
Options:`;
for (const [name, option] of Object.entries(OPTIONS)) {
let names = "";
if ("short" in option) {
names += `-${option.short}, `;
}
names += `--${name}`;
if (option.type === "string") {
names += " <num>"; // for now, all 'string' types are numbers
}
message += `\n ${names.padEnd(27)} ${option.description}`;
}
console.log(message);
}

function getPackageInfos() {
let input;
for (const path of [
Expand Down
10 changes: 9 additions & 1 deletion tests/bin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ afterEach(() => {

test("fails if no argument is given", async (t: TestContext) => {
t.assert.deepStrictEqual(await runBin(), {
code: 0,
code: 1,
stderr: ``,
stdout: r`
Usage: in-situ [options] <URL:LINE:COLUMN>
Expand All @@ -56,6 +56,14 @@ test("fails if no argument is given", async (t: TestContext) => {
});
});

test("fails if invalid argument is given", async (t: TestContext) => {
t.assert.deepStrictEqual(await runBin("-x"), {
code: 1,
stderr: `Unknown option '-x'. To specify a positional argument starting with a '-', place it at the end of the command after '--', as in '-- "-x"\n`,
stdout: "",
});
});

test("context options", async (t: TestContext) => {
const url = await withServer({
"/": generatedCode,
Expand Down

0 comments on commit d2e7e4a

Please sign in to comment.