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
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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 <version>`) 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"
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
6
v.go
6
v.go
|
@ -5,7 +5,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
Version = "0.0.6"
|
||||
Version = "0.0.7"
|
||||
Author = "Marc Cataford <hello@karnov.club>"
|
||||
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)
|
||||
|
|
Loading…
Reference in a new issue