python.ts

This commit is contained in:
2026-01-25 19:34:50 +08:00
parent 997fedfec5
commit bd77dc5182

View File

@@ -1,14 +1,16 @@
#!/usr/bin/env runts -- --allow-env --allow-run
import {
execCommand,
execCommandShell,
existsPath,
joinPath,
log,
readFileToString,
resolveFilename,
} from "https://global.hatter.ink/script/get/@12/deno-commons-mod.ts";
} from "https://global.hatter.ink/script/get/@16/deno-commons-mod.ts";
import { parseArgs } from "jsr:@std/cli/parse-args";
import { spawn } from "node:child_process";
import { writeStringToFile } from "../libraries/deno-commons-mod.ts";
const PYTHON_CONFIG_FILE = "~/.config/python-config.json";
const PYTHON_VENV_DEFAULT_BASE_DIR = "~/.venv/";
@@ -42,6 +44,14 @@ async function loadPythonConfig(): Promise<PythonConfig> {
return JSON.parse(pythonConfigJson) as PythonConfig;
}
async function savePythonConfig(pythonConfig: PythonConfig): Promise<void> {
const pythonConfigFile = resolveFilename(PYTHON_CONFIG_FILE);
await writeStringToFile(
pythonConfigFile,
JSON.stringify(pythonConfig, null, 2),
);
}
async function findVirtualEnv(pythonVenvVersion: string): Promise<PythonVenv> {
const pythonConfig = await loadPythonConfig();
if (!pythonConfig.profiles) {
@@ -86,28 +96,14 @@ async function newVirtualEnv(pythonVersion: string | null, pythonVenv: string) {
}
// python3 -m venv myenv
const python3Cmd = await getPythonFromPath(pythonVersionProfile.path);
const python3Cmd = pythonVersionProfile.path;
log.success(`Found Python: ${python3Cmd}`);
const pythonVenvArgs = ["-m", "vent", pythonVenvDir];
const pythonVenvArgs = ["-m", "venv", pythonVenvDir];
log.info(
`Create Python venv, python: ${python3Cmd}, args: ${pythonVenvArgs}`,
);
spawn(python3Cmd, pythonVenvArgs, {
shell: true,
stdio: ["inherit", "inherit", "inherit"],
});
}
async function getPythonFromPath(path: string): Promise<string> {
if (await isFile(path)) {
return path;
}
const python3Cmd = joinPath(pythonVersionProfile.path, "bin", "python3");
if (await isFile(python3Cmd)) {
return python3Cmd;
}
throw new Error(`Python path not found, path: ${path}`);
await execCommandShell(python3Cmd, pythonVenvArgs);
}
async function isFile(path: string): Promise<boolean> {
@@ -116,7 +112,17 @@ async function isFile(path: string): Promise<boolean> {
}
function handleHelp(_args: string[]) {
console.log("Help message");
const help = [];
help.push(
`python.ts - Python version and virtual environment management tool
python.ts python - management python version [alias: py]
python.ts add-python - add python version [alias: py]
python.ts venv - management python virtual environment`,
);
// source <(cat ~/.venv-python-3.13.5/bin/activate)
// source <(python.ts venv test1)
console.log(help.join("\n"));
}
async function handlePython(args: string[]) {
@@ -168,6 +174,77 @@ async function handlePython(args: string[]) {
// TODO
}
async function getPythonVersion(pythonBinPath: string): Promise<string> {
const pythonVersion = await execCommand(pythonBinPath, ["--version"]);
const pythonVersionLower = pythonVersion.assertSuccess()
.getStdoutAsStringThenTrim().toLowerCase();
if (!pythonVersionLower.startsWith("python ")) {
throw new Error(`Invalid Python version: ${pythonVersionLower}`);
}
return pythonVersionLower.substring("python ".length).trim();
}
async function handleAddPython(args: string[]) {
const flags = parseArgs(Deno.args, {
boolean: ["help"],
string: ["path"],
});
if (flags.help) {
console.log("Help massage for python add-python");
return;
}
if (!flags.path) {
throw new Error("Path is empty");
}
const path = flags.path as string;
let pythonPath = path;
if (!path.includes("/")) {
const whichPath = await execCommand("which", [path]);
pythonPath = whichPath.assertSuccess().getStdoutAsStringThenTrim();
}
log.info(`Python path: ${pythonPath}`);
const isPythonPathFile = await isFile(pythonPath);
let pythonBinPath = "";
if (!isPythonPathFile) {
const pythonPath1 = joinPath(pythonPath, "bin", "python3");
if (await isFile(pythonPath1)) {
pythonBinPath = pythonPath1;
} else {
const pythonPath2 = joinPath(pythonPath, "bin", "python");
if (await isFile(pythonPath2)) {
pythonBinPath = pythonPath2;
} else {
throw new Error(
`Python bin path not found, python path: ${pythonPath}`,
);
}
}
} else {
pythonBinPath = pythonPath;
}
const pythonVersion = await getPythonVersion(pythonBinPath);
log.info(`Python version: ${pythonVersion}`);
const pythonConfig = await loadPythonConfig();
if (!pythonConfig.versions) {
pythonConfig.versions = {};
}
if (pythonConfig.versions[pythonVersion]) {
throw new Error(`Python version ${pythonVersion} exists`);
}
const pyVersion: PythonVersion = {
version: pythonVersion,
path: pythonBinPath,
comment: `Python v${pythonVersion}`,
};
log.info("Found Python version", pyVersion);
pythonConfig.versions[pythonVersion] = pyVersion;
log.info("Save to Python config");
await savePythonConfig(pythonConfig);
}
async function handleVenv(args: string[]) {
const flags = parseArgs(Deno.args, {
boolean: ["help"],
@@ -194,7 +271,7 @@ async function main() {
const [subcommand, ...remainingArgs] = args._;
if (!subcommand) {
log.warn("Subcommand not found");
log.warn("Subcommand not found, `python.ts help` for help message");
return;
}
@@ -206,6 +283,10 @@ async function main() {
case "python":
await handlePython(remainingArgs);
break;
case "add-py":
case "add-python":
await handleAddPython(remainingArgs);
break;
case "venv":
await handleVenv(remainingArgs);
break;