From 38450e0f7268c250d64df896bf119499b22cf3c7 Mon Sep 17 00:00:00 2001 From: Marc Cataford Date: Tue, 28 Nov 2023 00:19:34 -0500 Subject: [PATCH] feat: which + version commands (#13) * refactor: simplify logic that determines which Python version is used * feat: which to output path only, version to output full current version meta * fix: command used in shims * chore: bump version to 0.0.7 --- commands.go | 37 +++++++++++++------------------------ pythonversion.go | 40 ++++++++++++++++++---------------------- pythonversion_test.go | 25 ++++++++++++------------- v.go | 6 +++--- 4 files changed, 46 insertions(+), 62 deletions(-) diff --git a/commands.go b/commands.go index 1286709..369a764 100644 --- a/commands.go +++ b/commands.go @@ -21,7 +21,7 @@ var SHIMS = []string{ const DEFAULT_PERMISSION = 0775 func writeShim(shimPath string) error { - shimContent := []byte("#!/bin/bash\n$(v where --raw) $@") + shimContent := []byte("#!/bin/bash\n$(v which --raw) $@") if err := os.WriteFile(shimPath, shimContent, DEFAULT_PERMISSION); err != nil { return err } @@ -99,18 +99,18 @@ func ListVersions(args []string, flags Flags, currentState State) error { return nil } -// Where prints out the system path to the executable being used by `python`. -func Where(args []string, flags Flags, currentState State) error { +// Which prints out the system path to the executable being used by `python`. +func Which(args []string, flags Flags, currentState State) error { selectedVersion, _ := DetermineSelectedPythonVersion(currentState) var printedPath string - if selectedVersion == "SYSTEM" { + if selectedVersion.Source == "system" { _, sysPath := DetermineSystemPython() printedPath = fmt.Sprintf("%s (system)", sysPath) } else { - tag := VersionStringToStruct(selectedVersion) - printedPath = GetStatePath("runtimes", fmt.Sprintf("py-%s", selectedVersion), "bin", fmt.Sprintf("python%s", tag.MajorMinor())) + tag := VersionStringToStruct(selectedVersion.Version) + printedPath = GetStatePath("runtimes", fmt.Sprintf("py-%s", selectedVersion.Version), "bin", fmt.Sprintf("python%s", tag.MajorMinor())) } prefix := "Python path: " @@ -125,28 +125,17 @@ func Where(args []string, flags Flags, currentState State) error { return nil } -// Which prints out the Python version that will be used by shims. It can be invoked -// directly by the `v which` command. -// -// If no version is set (i.e. none is installed, the specified version is not installed), -// the system version is used and 'SYSTEM' is printed by Which. -func Which(args []string, flags Flags, currentState State) error { +// CurrentVersion (called via `v version`) outputs the currently selected version +// and what configures it. If the version is configured by a file, the file is returned +// under "source", if the system Python is used, "system" is returned as a source. +func CurrentVersion(args []string, flags Flags, currentState State) error { selectedVersion, _ := DetermineSelectedPythonVersion(currentState) - printedVersion := selectedVersion - - if selectedVersion == "SYSTEM" { - sysVersion, _ := DetermineSystemPython() - printedVersion = fmt.Sprintf("%s (system)", sysVersion) - } - - prefix := "Python version: " if flags.RawOutput { - prefix = "" - } else { - printedVersion = Bold(printedVersion) + fmt.Println(selectedVersion.Version) + return nil } - fmt.Printf("%s%s\n", prefix, printedVersion) + fmt.Printf("Python version: %s\nSource: %s\n", Bold(selectedVersion.Version), Bold(selectedVersion.Source)) return nil } diff --git a/pythonversion.go b/pythonversion.go index 7cd27d6..66a0ffd 100644 --- a/pythonversion.go +++ b/pythonversion.go @@ -4,13 +4,17 @@ import ( "io/ioutil" "os" "path" - "slices" "strings" ) +type SelectedVersion struct { + Version string + Source string +} + // SearchForPythonVersionFile crawls up to the system root to find any // .python-version file that could set the current version. -func SearchForPythonVersionFile() (string, bool) { +func SearchForPythonVersionFile() (SelectedVersion, bool) { currentPath, _ := os.Getwd() var versionFound string for { @@ -30,7 +34,11 @@ func SearchForPythonVersionFile() (string, bool) { currentPath = nextPath } - return versionFound, versionFound != "" + if versionFound == "" { + return SelectedVersion{}, false + } + + return SelectedVersion{Version: versionFound, Source: path.Join(currentPath, ".python-version")}, true } // DetermineSelectedPythonVersion returns the Python runtime version that should be @@ -40,7 +48,7 @@ func SearchForPythonVersionFile() (string, bool) { // file that would indicate which version is preferred. If none are found, the global // user-defined version (via `v use `) is used. If there is none, the system // Python version is used. -func DetermineSelectedPythonVersion(currentState State) (string, error) { +func DetermineSelectedPythonVersion(currentState State) (SelectedVersion, error) { pythonFileVersion, pythonFileVersionFound := SearchForPythonVersionFile() if pythonFileVersionFound { @@ -48,29 +56,17 @@ func DetermineSelectedPythonVersion(currentState State) (string, error) { } if len(currentState.GlobalVersion) != 0 { - return currentState.GlobalVersion, nil + return SelectedVersion{Version: currentState.GlobalVersion, Source: GetStatePath("state.json")}, nil } - return "SYSTEM", nil + systemVersion, _ := DetermineSystemPython() + return SelectedVersion{Source: "system", Version: systemVersion}, nil } // DetermineSystemPython returns the unshimmed Python version and path. -// This is done by inspected the output of `which` and `python --version` if v's shims -// are not in $PATH. +// It assumes that /bin/python is where system Python lives. func DetermineSystemPython() (string, string) { - currentPathEnv := os.Getenv("PATH") - pathWithoutShims := slices.DeleteFunc(strings.Split(currentPathEnv, ":"), func(element string) bool { - return element == GetStatePath("shims") - }) - - // FIXME: This should be set through RunCommand instead. - os.Setenv("PATH", strings.Join(pathWithoutShims, ":")) - defer os.Setenv("PATH", currentPathEnv) - - whichOut, _ := RunCommand([]string{"which", "python"}, GetStatePath(), true) - versionOut, _ := RunCommand([]string{"python", "--version"}, GetStatePath(), true) - + versionOut, _ := RunCommand([]string{"/bin/python", "--version"}, GetStatePath(), true) detectedVersion, _ := strings.CutPrefix(versionOut, "Python") - - return strings.TrimSpace(detectedVersion), strings.TrimSpace(whichOut) + return strings.TrimSpace(detectedVersion), "/bin/python" } diff --git a/pythonversion_test.go b/pythonversion_test.go index bf29a79..d9c141f 100644 --- a/pythonversion_test.go +++ b/pythonversion_test.go @@ -31,18 +31,17 @@ func TestDetermineSystemPythonGetsUnshimmedPythonRuntime(t *testing.T) { ioutil.WriteFile(GetStatePath("shims", "python"), []byte("#!/bin/bash\necho \"Python 4.5.6\""), 0777) mockSystemPythonPath := t.TempDir() mockSystemPythonExecPath := path.Join(mockSystemPythonPath, "python") - ioutil.WriteFile(mockSystemPythonExecPath, []byte("#!/bin/bash\necho \"Python 1.2.3\""), 0777) oldPath := os.Getenv("PATH") - os.Setenv("PATH", fmt.Sprintf("%s:%s:/usr/bin", GetStatePath("shims"), mockSystemPythonPath)) + os.Setenv("PATH", fmt.Sprintf("%s:/usr/bin", GetStatePath("shims"))) defer os.Setenv("PATH", oldPath) sysVersion, sysPath := DetermineSystemPython() - if sysVersion != "1.2.3" { - t.Errorf("Expected system Python to be 1.2.3, found %s instead.", sysVersion) + if sysVersion == "4.5.6" { + t.Errorf("Expected system Python to not match the shim, found %s instead.", sysVersion) } - if sysPath != mockSystemPythonExecPath { + if sysPath != "/bin/python" { t.Errorf("Expected system Python path to be %s, found %s instead.", mockSystemPythonExecPath, sysPath) } } @@ -62,8 +61,8 @@ func TestDetermineSelectedPythonVersionUsesPythonVersionFileIfFound(t *testing.T version, err := DetermineSelectedPythonVersion(ReadState()) - if err != nil || version != "1.2.3" { - t.Errorf("Expected version to be %s, got %s instead.", "1.2.3", version) + if err != nil || version.Version != "1.2.3" { + t.Errorf("Expected version to be %s, got %s instead.", "1.2.3", version.Version) } } @@ -78,7 +77,7 @@ func TestDetermineSelectedPythonVersionGetsUserDefinedVersion(t *testing.T) { version, err := DetermineSelectedPythonVersion(ReadState()) - if err != nil || version != mockState.GlobalVersion { + if err != nil || version.Version != mockState.GlobalVersion { t.Errorf("Expected version to be %s, got %s instead.", mockState.GlobalVersion, version) } } @@ -88,7 +87,7 @@ func TestDetermineSelectedPythonVersionDefaultsToSystem(t *testing.T) { version, err := DetermineSelectedPythonVersion(ReadState()) - if err != nil || version != "SYSTEM" { + if err != nil || version.Source != "system" { t.Errorf("Expected version to be 'SYSTEM', got %s instead.", version) } } @@ -102,7 +101,7 @@ func TestSearchForPythonVersionFileFindsFileInCwd(t *testing.T) { versionFound, found := SearchForPythonVersionFile() - if versionFound != "1.2.3" || !found { + if versionFound.Version != "1.2.3" || !found { t.Errorf("Expected \"1.2.3\", found %s", versionFound) } } @@ -118,7 +117,7 @@ func TestSearchForPythonVersionFileFindsFileInParents(t *testing.T) { versionFound, found := SearchForPythonVersionFile() - if versionFound != "1.2.3" || !found { + if versionFound.Version != "1.2.3" || !found { t.Errorf("Expected \"1.2.3\", found %s", versionFound) } @@ -129,8 +128,8 @@ func TestSearchForPythonVersionFileReturnsOnRootIfNoneFound(t *testing.T) { versionFound, found := SearchForPythonVersionFile() - if versionFound != "" || found { - t.Errorf("Did not expect any result, found %s.", versionFound) + if versionFound.Version != "" || found { + t.Errorf("Did not expect any result, found %s.", versionFound.Version) } } diff --git a/v.go b/v.go index f60a55d..e10a03d 100644 --- a/v.go +++ b/v.go @@ -5,7 +5,7 @@ import ( ) const ( - Version = "0.0.6" + Version = "0.0.7" Author = "Marc Cataford " Homepage = "https://github.com/mcataford/v" ) @@ -30,9 +30,9 @@ func main() { ).AddCommand( "ls", ListVersions, "v ls", "Lists the installed Python versions.", ).AddCommand( - "where", Where, "v where", "Prints the path to the current Python version.", + "version", CurrentVersion, "v version", "Prints the current version and its source.", ).AddCommand( - "which", Which, "v which", "Prints the current Python version.", + "which", Which, "v which", "Prints the path to the current Python version.", ).AddCommand( "init", Initialize, "v init", "Initializes the v state.", ).Run(args, currentState)