diff --git a/python-ts/main.ts b/python-ts/main.ts index a3a80f4..b06d4e9 100644 --- a/python-ts/main.ts +++ b/python-ts/main.ts @@ -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 { return JSON.parse(pythonConfigJson) as PythonConfig; } +async function savePythonConfig(pythonConfig: PythonConfig): Promise { + const pythonConfigFile = resolveFilename(PYTHON_CONFIG_FILE); + await writeStringToFile( + pythonConfigFile, + JSON.stringify(pythonConfig, null, 2), + ); +} + async function findVirtualEnv(pythonVenvVersion: string): Promise { 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 { - 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 { @@ -116,7 +112,17 @@ async function isFile(path: string): Promise { } 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 { + 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;