#!/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; profiles?: Map; } 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 { 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 { 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 { 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 { 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));