164 lines · 2.9 KB · TypeScript
RawDownload
function isWhitespace(ch) {	return ch === " " || ch === "\t" || ch === "\n" || ch === "\r";} function splitPipes(input) {	const parts = [];	let current = "";	let quote = null; 	for (let i = 0; i < input.length; i++) {		const ch = input[i]; 		if (quote) {			if (ch === "\\") {				const next = input[i + 1];				if (next) {					current += ch + next;					i++;					continue;				}			}			current += ch;			if (ch === quote) {				quote = null;			}			continue;		} 		if (ch === '"' || ch === "'") {			quote = ch;			current += ch;			continue;		} 		if (ch === "|") {			parts.push(current.trim());			current = "";			continue;		} 		current += ch;	} 	if (quote) throw new Error("Unclosed quote");	if (current.trim().length > 0) parts.push(current.trim());	return parts;} function tokenizeCommand(input) {	const tokens = [];	let current = "";	let quote = null; 	const push = () => {		if (current.length > 0) tokens.push(current);		current = "";	}; 	for (let i = 0; i < input.length; i++) {		const ch = input[i]; 		if (quote) {			if (quote === "'") {				if (ch === "\\" && input[i + 1] === quote) {					current += quote;					i++;					continue;				}				if (ch === quote) {					quote = null;					continue;				}				current += ch;				continue;			} 			// Double-quoted mode: preserve unknown escapes (\n, \t, etc) while			// unescaping only shell-like quote/backslash escapes.			if (ch === "\\") {				const next = input[i + 1];				if (next === '"' || next === "\\" || next === "$" || next === "`") {					current += next;					i++;					continue;				}				if (next === "\n") {					i++;					continue;				}				current += ch;				continue;			} 			if (ch === quote) {				quote = null;				continue;			} 			current += ch;			continue;		} 		if (ch === '"' || ch === "'") {			quote = ch;			continue;		} 		if (isWhitespace(ch)) {			push();			continue;		} 		current += ch;	} 	if (quote) throw new Error("Unclosed quote");	push();	return tokens;} function parseArgs(tokens) {	const args = { _: [] }; 	for (let i = 0; i < tokens.length; i++) {		const tok = tokens[i]; 		if (tok.startsWith("--")) {			const eq = tok.indexOf("=");			if (eq !== -1) {				const key = tok.slice(2, eq);				const value = tok.slice(eq + 1);				args[key] = value;				continue;			} 			const key = tok.slice(2);			const next = tokens[i + 1];			if (!next || next.startsWith("--")) {				args[key] = true;				continue;			}			args[key] = next;			i++;			continue;		} 		args._.push(tok);	} 	return args;} export function parsePipeline(input) {	const stages = splitPipes(input);	if (stages.length === 0) throw new Error("Empty pipeline"); 	return stages.map((stage) => {		const tokens = tokenizeCommand(stage);		if (tokens.length === 0) throw new Error("Empty command stage");		const name = tokens[0];		const args = parseArgs(tokens.slice(1));		return { name, args, raw: stage };	});}