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
This commit is contained in:
parent
3ccf410f17
commit
38450e0f72
4 changed files with 46 additions and 62 deletions
37
commands.go
37
commands.go
|
@ -21,7 +21,7 @@ var SHIMS = []string{
|
||||||
const DEFAULT_PERMISSION = 0775
|
const DEFAULT_PERMISSION = 0775
|
||||||
|
|
||||||
func writeShim(shimPath string) error {
|
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 {
|
if err := os.WriteFile(shimPath, shimContent, DEFAULT_PERMISSION); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -99,18 +99,18 @@ func ListVersions(args []string, flags Flags, currentState State) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Where prints out the system path to the executable being used by `python`.
|
// Which prints out the system path to the executable being used by `python`.
|
||||||
func Where(args []string, flags Flags, currentState State) error {
|
func Which(args []string, flags Flags, currentState State) error {
|
||||||
selectedVersion, _ := DetermineSelectedPythonVersion(currentState)
|
selectedVersion, _ := DetermineSelectedPythonVersion(currentState)
|
||||||
|
|
||||||
var printedPath string
|
var printedPath string
|
||||||
|
|
||||||
if selectedVersion == "SYSTEM" {
|
if selectedVersion.Source == "system" {
|
||||||
_, sysPath := DetermineSystemPython()
|
_, sysPath := DetermineSystemPython()
|
||||||
printedPath = fmt.Sprintf("%s (system)", sysPath)
|
printedPath = fmt.Sprintf("%s (system)", sysPath)
|
||||||
} else {
|
} else {
|
||||||
tag := VersionStringToStruct(selectedVersion)
|
tag := VersionStringToStruct(selectedVersion.Version)
|
||||||
printedPath = GetStatePath("runtimes", fmt.Sprintf("py-%s", selectedVersion), "bin", fmt.Sprintf("python%s", tag.MajorMinor()))
|
printedPath = GetStatePath("runtimes", fmt.Sprintf("py-%s", selectedVersion.Version), "bin", fmt.Sprintf("python%s", tag.MajorMinor()))
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix := "Python path: "
|
prefix := "Python path: "
|
||||||
|
@ -125,28 +125,17 @@ func Where(args []string, flags Flags, currentState State) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Which prints out the Python version that will be used by shims. It can be invoked
|
// CurrentVersion (called via `v version`) outputs the currently selected version
|
||||||
// directly by the `v which` command.
|
// 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.
|
||||||
// If no version is set (i.e. none is installed, the specified version is not installed),
|
func CurrentVersion(args []string, flags Flags, currentState State) error {
|
||||||
// the system version is used and 'SYSTEM' is printed by Which.
|
|
||||||
func Which(args []string, flags Flags, currentState State) error {
|
|
||||||
selectedVersion, _ := DetermineSelectedPythonVersion(currentState)
|
selectedVersion, _ := DetermineSelectedPythonVersion(currentState)
|
||||||
printedVersion := selectedVersion
|
|
||||||
|
|
||||||
if selectedVersion == "SYSTEM" {
|
|
||||||
sysVersion, _ := DetermineSystemPython()
|
|
||||||
printedVersion = fmt.Sprintf("%s (system)", sysVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix := "Python version: "
|
|
||||||
|
|
||||||
if flags.RawOutput {
|
if flags.RawOutput {
|
||||||
prefix = ""
|
fmt.Println(selectedVersion.Version)
|
||||||
} else {
|
return nil
|
||||||
printedVersion = Bold(printedVersion)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s%s\n", prefix, printedVersion)
|
fmt.Printf("Python version: %s\nSource: %s\n", Bold(selectedVersion.Version), Bold(selectedVersion.Source))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,17 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SelectedVersion struct {
|
||||||
|
Version string
|
||||||
|
Source string
|
||||||
|
}
|
||||||
|
|
||||||
// SearchForPythonVersionFile crawls up to the system root to find any
|
// SearchForPythonVersionFile crawls up to the system root to find any
|
||||||
// .python-version file that could set the current version.
|
// .python-version file that could set the current version.
|
||||||
func SearchForPythonVersionFile() (string, bool) {
|
func SearchForPythonVersionFile() (SelectedVersion, bool) {
|
||||||
currentPath, _ := os.Getwd()
|
currentPath, _ := os.Getwd()
|
||||||
var versionFound string
|
var versionFound string
|
||||||
for {
|
for {
|
||||||
|
@ -30,7 +34,11 @@ func SearchForPythonVersionFile() (string, bool) {
|
||||||
currentPath = nextPath
|
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
|
// 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
|
// file that would indicate which version is preferred. If none are found, the global
|
||||||
// user-defined version (via `v use <version>`) is used. If there is none, the system
|
// user-defined version (via `v use <version>`) is used. If there is none, the system
|
||||||
// Python version is used.
|
// Python version is used.
|
||||||
func DetermineSelectedPythonVersion(currentState State) (string, error) {
|
func DetermineSelectedPythonVersion(currentState State) (SelectedVersion, error) {
|
||||||
pythonFileVersion, pythonFileVersionFound := SearchForPythonVersionFile()
|
pythonFileVersion, pythonFileVersionFound := SearchForPythonVersionFile()
|
||||||
|
|
||||||
if pythonFileVersionFound {
|
if pythonFileVersionFound {
|
||||||
|
@ -48,29 +56,17 @@ func DetermineSelectedPythonVersion(currentState State) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(currentState.GlobalVersion) != 0 {
|
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.
|
// DetermineSystemPython returns the unshimmed Python version and path.
|
||||||
// This is done by inspected the output of `which` and `python --version` if v's shims
|
// It assumes that /bin/python is where system Python lives.
|
||||||
// are not in $PATH.
|
|
||||||
func DetermineSystemPython() (string, string) {
|
func DetermineSystemPython() (string, string) {
|
||||||
currentPathEnv := os.Getenv("PATH")
|
versionOut, _ := RunCommand([]string{"/bin/python", "--version"}, GetStatePath(), true)
|
||||||
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)
|
|
||||||
|
|
||||||
detectedVersion, _ := strings.CutPrefix(versionOut, "Python")
|
detectedVersion, _ := strings.CutPrefix(versionOut, "Python")
|
||||||
|
return strings.TrimSpace(detectedVersion), "/bin/python"
|
||||||
return strings.TrimSpace(detectedVersion), strings.TrimSpace(whichOut)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,18 +31,17 @@ func TestDetermineSystemPythonGetsUnshimmedPythonRuntime(t *testing.T) {
|
||||||
ioutil.WriteFile(GetStatePath("shims", "python"), []byte("#!/bin/bash\necho \"Python 4.5.6\""), 0777)
|
ioutil.WriteFile(GetStatePath("shims", "python"), []byte("#!/bin/bash\necho \"Python 4.5.6\""), 0777)
|
||||||
mockSystemPythonPath := t.TempDir()
|
mockSystemPythonPath := t.TempDir()
|
||||||
mockSystemPythonExecPath := path.Join(mockSystemPythonPath, "python")
|
mockSystemPythonExecPath := path.Join(mockSystemPythonPath, "python")
|
||||||
ioutil.WriteFile(mockSystemPythonExecPath, []byte("#!/bin/bash\necho \"Python 1.2.3\""), 0777)
|
|
||||||
|
|
||||||
oldPath := os.Getenv("PATH")
|
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)
|
defer os.Setenv("PATH", oldPath)
|
||||||
sysVersion, sysPath := DetermineSystemPython()
|
sysVersion, sysPath := DetermineSystemPython()
|
||||||
|
|
||||||
if sysVersion != "1.2.3" {
|
if sysVersion == "4.5.6" {
|
||||||
t.Errorf("Expected system Python to be 1.2.3, found %s instead.", sysVersion)
|
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)
|
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())
|
version, err := DetermineSelectedPythonVersion(ReadState())
|
||||||
|
|
||||||
if err != nil || version != "1.2.3" {
|
if err != nil || version.Version != "1.2.3" {
|
||||||
t.Errorf("Expected version to be %s, got %s instead.", "1.2.3", version)
|
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())
|
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)
|
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())
|
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)
|
t.Errorf("Expected version to be 'SYSTEM', got %s instead.", version)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,7 +101,7 @@ func TestSearchForPythonVersionFileFindsFileInCwd(t *testing.T) {
|
||||||
|
|
||||||
versionFound, found := SearchForPythonVersionFile()
|
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)
|
t.Errorf("Expected \"1.2.3\", found %s", versionFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,7 +117,7 @@ func TestSearchForPythonVersionFileFindsFileInParents(t *testing.T) {
|
||||||
|
|
||||||
versionFound, found := SearchForPythonVersionFile()
|
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)
|
t.Errorf("Expected \"1.2.3\", found %s", versionFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,8 +128,8 @@ func TestSearchForPythonVersionFileReturnsOnRootIfNoneFound(t *testing.T) {
|
||||||
|
|
||||||
versionFound, found := SearchForPythonVersionFile()
|
versionFound, found := SearchForPythonVersionFile()
|
||||||
|
|
||||||
if versionFound != "" || found {
|
if versionFound.Version != "" || found {
|
||||||
t.Errorf("Did not expect any result, found %s.", versionFound)
|
t.Errorf("Did not expect any result, found %s.", versionFound.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
6
v.go
6
v.go
|
@ -5,7 +5,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
Version = "0.0.6"
|
Version = "0.0.7"
|
||||||
Author = "Marc Cataford <hello@karnov.club>"
|
Author = "Marc Cataford <hello@karnov.club>"
|
||||||
Homepage = "https://github.com/mcataford/v"
|
Homepage = "https://github.com/mcataford/v"
|
||||||
)
|
)
|
||||||
|
@ -30,9 +30,9 @@ func main() {
|
||||||
).AddCommand(
|
).AddCommand(
|
||||||
"ls", ListVersions, "v ls", "Lists the installed Python versions.",
|
"ls", ListVersions, "v ls", "Lists the installed Python versions.",
|
||||||
).AddCommand(
|
).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(
|
).AddCommand(
|
||||||
"which", Which, "v which", "Prints the current Python version.",
|
"which", Which, "v which", "Prints the path to the current Python version.",
|
||||||
).AddCommand(
|
).AddCommand(
|
||||||
"init", Initialize, "v init", "Initializes the v state.",
|
"init", Initialize, "v init", "Initializes the v state.",
|
||||||
).Run(args, currentState)
|
).Run(args, currentState)
|
||||||
|
|
Loading…
Reference in a new issue