192 lines · 6.8 KB
import type { Dependency } from "./manifests-json";

const REQUIREMENT_LINE = /^([A-Za-z0-9][A-Za-z0-9._-]*)(?:\[[^\]]*\])?\s*(.*)$/;

function pep508(spec: string, scope: Dependency["scope"], direct: boolean): Dependency | null {
  const clean = spec.split(";")[0].trim();
  const match = REQUIREMENT_LINE.exec(clean);
  if (!match) return null;
  return { name: match[1].toLowerCase(), requirement: match[2].trim() || null, scope, direct };
}

export function parseRequirementsTxt(text: string, scope: Dependency["scope"]): Dependency[] {
  return text.split("\n").flatMap((raw) => {
    const line = raw.split(" #")[0].trim();
    if (!line || line.startsWith("#") || line.startsWith("-") || line.includes("://")) return [];
    const dependency = pep508(line, scope, true);
    return dependency ? [dependency] : [];
  });
}

type TomlTable = { name: string; lines: string[] };

/** Linear comment stripper: `\s+#.*$` backtracks quadratically on long runs of whitespace (INT-A10). */
export function stripTomlComment(raw: string): string {
  let quote: string | null = null;
  for (let index = 0; index < raw.length; index++) {
    const char = raw[index];
    if (quote) {
      if (char === "\\" && quote === '"') index++;
      else if (char === quote) quote = null;
    } else if (char === '"' || char === "'") quote = char;
    else if (char === "#") return raw.slice(0, index);
  }
  return raw;
}

/** Splits TOML into tables by header; enough for dependency sections without a full TOML parser. */
function tomlTables(text: string): TomlTable[] {
  const tables: TomlTable[] = [{ name: "", lines: [] }];
  for (const raw of text.split("\n")) {