mirror of
https://bitbucket.org/siakitem/my-pi.git
synced 2026-10-26 10:45:22 +00:00
139 lines
3.3 KiB
TypeScript
139 lines
3.3 KiB
TypeScript
import { advanceQuoteEscapeState, readShellChars, type QuoteEscapeState } from "./shell-quote-state.js";
|
|
|
|
interface WindowsBashCompatibilityResult {
|
|
command: string;
|
|
applied: string[];
|
|
}
|
|
|
|
interface LeadingCdSlashDParse {
|
|
rawPath: string;
|
|
operator: string;
|
|
tail: string;
|
|
}
|
|
|
|
const PYTHON_UTF8_ENV_PREFIX = "PYTHONIOENCODING=utf-8";
|
|
|
|
function normalizeWindowsPathForBash(rawPath: string): string {
|
|
const trimmed = rawPath.trim();
|
|
const unquoted =
|
|
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
(trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
? trimmed.slice(1, -1)
|
|
: trimmed;
|
|
return unquoted.replace(/\\/g, "/");
|
|
}
|
|
|
|
function quoteForBash(value: string): string {
|
|
const escaped = value.replace(/"/g, '\\"');
|
|
return `"${escaped}"`;
|
|
}
|
|
|
|
function parseLeadingCdSlashD(command: string): LeadingCdSlashDParse | null {
|
|
const prefixMatch = command.match(/^\s*cd\s+\/d\s+/i);
|
|
if (!prefixMatch) {
|
|
return null;
|
|
}
|
|
|
|
const pathStart = prefixMatch[0].length;
|
|
const state: QuoteEscapeState = { quote: null, escaped: false };
|
|
|
|
for (let index = pathStart; index < command.length; index += 1) {
|
|
const { character, nextCharacter } = readShellChars(command, index);
|
|
|
|
if (advanceQuoteEscapeState(state, character, "\"'")) {
|
|
continue;
|
|
}
|
|
|
|
if (character === "&" && nextCharacter === "&") {
|
|
return {
|
|
rawPath: command.slice(pathStart, index),
|
|
operator: "&&",
|
|
tail: command.slice(index + 2),
|
|
};
|
|
}
|
|
|
|
if (character === "|" && nextCharacter === "|") {
|
|
return {
|
|
rawPath: command.slice(pathStart, index),
|
|
operator: "||",
|
|
tail: command.slice(index + 2),
|
|
};
|
|
}
|
|
|
|
if (character === "|" || character === ";") {
|
|
return {
|
|
rawPath: command.slice(pathStart, index),
|
|
operator: character,
|
|
tail: command.slice(index + 1),
|
|
};
|
|
}
|
|
}
|
|
|
|
return {
|
|
rawPath: command.slice(pathStart),
|
|
operator: "",
|
|
tail: "",
|
|
};
|
|
}
|
|
|
|
function rewriteLeadingCdSlashD(command: string): { command: string; changed: boolean } {
|
|
const parsed = parseLeadingCdSlashD(command);
|
|
if (!parsed) {
|
|
return { command, changed: false };
|
|
}
|
|
|
|
const normalizedPath = quoteForBash(normalizeWindowsPathForBash(parsed.rawPath));
|
|
if (!parsed.operator) {
|
|
return {
|
|
command: `cd ${normalizedPath}`,
|
|
changed: true,
|
|
};
|
|
}
|
|
|
|
return {
|
|
command: `cd ${normalizedPath} ${parsed.operator} ${parsed.tail.trimStart()}`,
|
|
changed: true,
|
|
};
|
|
}
|
|
|
|
function ensurePythonUtf8(command: string): { command: string; changed: boolean } {
|
|
if (/\bPYTHONIOENCODING\s*=/.test(command)) {
|
|
return { command, changed: false };
|
|
}
|
|
|
|
if (!/(^|[;&|]\s*|&&\s*|\|\|\s*)python(?:3(?:\.\d+)?)?\b/i.test(command)) {
|
|
return { command, changed: false };
|
|
}
|
|
|
|
return {
|
|
command: `${PYTHON_UTF8_ENV_PREFIX} ${command}`,
|
|
changed: true,
|
|
};
|
|
}
|
|
|
|
export function applyWindowsBashCompatibilityFixes(
|
|
command: string,
|
|
platform: string = process.platform,
|
|
): WindowsBashCompatibilityResult {
|
|
if (platform !== "win32") {
|
|
return { command, applied: [] };
|
|
}
|
|
|
|
let nextCommand = command;
|
|
const applied: string[] = [];
|
|
|
|
const cdFix = rewriteLeadingCdSlashD(nextCommand);
|
|
if (cdFix.changed) {
|
|
nextCommand = cdFix.command;
|
|
applied.push("cd-/d");
|
|
}
|
|
|
|
const pythonFix = ensurePythonUtf8(nextCommand);
|
|
if (pythonFix.changed) {
|
|
nextCommand = pythonFix.command;
|
|
applied.push("python-utf8");
|
|
}
|
|
|
|
return { command: nextCommand, applied };
|
|
}
|