parse-args.ts

 1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
 2//
 3// SPDX-License-Identifier: GPL-3.0-or-later
 4
 5export interface ParsedArgs {
 6  command?: string;
 7  options: Record<string, string | boolean>;
 8  positional: string[];
 9}
10
11export function parseArgs(args: string[]): ParsedArgs {
12  const [, , command, ...rest] = args;
13  const options: Record<string, string | boolean> = {};
14  const positional: string[] = [];
15
16  for (let i = 0; i < rest.length; i += 1) {
17    const arg = rest[i];
18    if (!arg) continue;
19
20    if (arg.startsWith("--")) {
21      const eqIndex = arg.indexOf("=", 2);
22      let key: string;
23      let value: string | undefined;
24      if (eqIndex !== -1) {
25        key = arg.slice(2, eqIndex);
26        value = arg.slice(eqIndex + 1);
27      } else {
28        key = arg.slice(2);
29      }
30      if (!key) continue;
31      if (value !== undefined) {
32        options[key] = value;
33      } else if (rest[i + 1] && !rest[i + 1]?.startsWith("-")) {
34        options[key] = rest[i + 1] as string;
35        i += 1;
36      } else {
37        options[key] = true;
38      }
39    } else if (arg.startsWith("-")) {
40      const short = arg.slice(1);
41      if (short === "u") {
42        if (rest[i + 1] && !rest[i + 1]!.startsWith("-")) {
43          options["uri"] = rest[i + 1] as string;
44          i += 1;
45        }
46        // else: -u with no value — uri stays unset, command handler validates
47      } else if (short === "f") {
48        options["full"] = true;
49      } else if (short === "v") {
50        options["version"] = true;
51      } else {
52        options[short] = true;
53      }
54    } else {
55      positional.push(arg);
56    }
57  }
58
59  return { command, options, positional };
60}