Files
ts-scripts/python-ts/main.ts
2026-01-23 01:26:19 +08:00

219 lines
6.2 KiB
TypeScript

#!/usr/bin/env runts -- --allow-env --allow-run
import {
existsPath,
joinPath,
log,
readFileToString,
resolveFilename,
} from "https://global.hatter.ink/script/get/@12/deno-commons-mod.ts";
import { parseArgs } from "jsr:@std/cli/parse-args";
import { spawn } from "node:child_process";
const PYTHON_CONFIG_FILE = "~/.config/python-config.json";
const PYTHON_VENV_DEFAULT_BASE_DIR = "~/.venv/";
interface PythonConfig {
default_version?: string;
default_profile?: string;
python_venv_base_path?: string;
versions: Map<string, PythonVersion>;
profiles?: Map<string, PythonVenv>;
}
interface PythonVersion {
version: string;
path: string; // Python path dir or Python binary file
comment?: string;
}
interface PythonVenv {
version: string;
path: string;
comment?: string;
}
async function loadPythonConfig(): Promise<PythonConfig> {
const pythonConfigFile = resolveFilename(PYTHON_CONFIG_FILE);
const pythonConfigJson = await readFileToString(pythonConfigFile);
if (!pythonConfigJson) {
throw `Could not read python config file: ${pythonConfigFile}`;
}
return JSON.parse(pythonConfigJson) as PythonConfig;
}
async function findVirtualEnv(pythonVenvVersion: string): Promise<PythonVenv> {
const pythonConfig = await loadPythonConfig();
if (!pythonConfig.profiles) {
throw "No Python venvs configured";
}
const pythonVenv = pythonConfig.profiles[pythonVenvVersion];
if (!pythonVenv) {
throw `Python venv not found: ${pythonVenvVersion}`;
}
return pythonVenv;
}
async function newVirtualEnv(pythonVersion: string | null, pythonVenv: string) {
const pythonConfig = await loadPythonConfig();
const selectedPythonVersion = pythonVersion ||
pythonConfig.default_version || null;
if (!selectedPythonVersion) {
throw `No Python version assigned.`;
}
if (!selectedPythonVersion) {
throw `No Python venv assigned.`;
}
const pythonVersionProfile = pythonConfig.versions[pythonVersion];
if (!pythonVersionProfile) {
throw `Python version: ${pythonVersion} not found`;
}
log.success(`Found Python version: ${pythonVersion}`);
const pythonVenvProfile = pythonConfig.profiles[pythonVenv];
if (pythonVenvProfile) {
throw `Python venv already exists: ${pythonVenv}`;
}
const pythonVenvBaseDir = resolveFilename(
pythonConfig.python_venv_base_path || PYTHON_VENV_DEFAULT_BASE_DIR,
);
if (!existsPath(pythonVenvBaseDir)) {
log.info(`Make python venv base dir: ${pythonVenvBaseDir}`);
Deno.mkdirSync(pythonVenvBaseDir);
}
const pythonVenvDir = joinPath(pythonVenvBaseDir, pythonVenv);
if (existsPath(pythonVenvDir)) {
throw `Python venv: ${pythonVenvDir} already exists`;
}
// python3 -m venv myenv
const python3Cmd = await getPythonFromPath(pythonVersionProfile.path);
log.success(`Found Python: ${python3Cmd}`);
const pythonVenvArgs = ["-m", "vent", 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}`);
}
async function isFile(path: string): Promise<boolean> {
const fileInfo = await Deno.stat(path);
return fileInfo?.isFile;
}
function handleHelp(_args: string[]) {
console.log("Help message");
}
async function handlePython(args: string[]) {
const flags = parseArgs(Deno.args, {
boolean: ["help"],
string: ["version"],
alias: {
V: "version",
},
});
if (flags.help) {
console.log("Help massage for python");
return;
}
if (!flags.version) {
const pythonConfig = await loadPythonConfig();
if (!pythonConfig.versions) {
log.error("No Python versions configured");
return;
}
const versions = [];
let maxVersionLength = 0;
for (let version in pythonConfig.versions) {
versions.push(version);
if (version.length > maxVersionLength) {
maxVersionLength = version.length;
}
}
versions.sort();
console.log(`Found ${versions.length} Python version(s)`);
for (let version in pythonConfig.versions) {
const pythonVersion = pythonConfig.versions[version];
const versionPadding = " ".repeat(
maxVersionLength - version.length,
);
console.log(
`- Python ${version} ${versionPadding}: ${pythonVersion.path} [version: ${
pythonVersion.version || "unknown"
}]`,
);
}
return;
}
const pythonVirtualEnv = await findVirtualEnv(flags.version);
// TODO
}
async function handleVenv(args: string[]) {
const flags = parseArgs(Deno.args, {
boolean: ["help"],
string: ["version"],
alias: {
V: "version",
},
});
if (flags.help) {
console.log("Help massage for venv");
return;
}
if (!flags.version) {
log.warn("Version missing");
return;
}
const pythonVirtualEnv = await findVirtualEnv(flags.version);
// TODO
}
async function main() {
const args = parseArgs(Deno.args);
const [subcommand, ...remainingArgs] = args._;
if (!subcommand) {
log.warn("Subcommand not found");
return;
}
switch (subcommand) {
case "help":
handleHelp(remainingArgs);
break;
case "py":
case "python":
await handlePython(remainingArgs);
break;
case "venv":
await handleVenv(remainingArgs);
break;
default:
log.error(`Unknown subcommand: ${subcommand}`);
break;
}
}
main().catch((e) => log.error(e));