refactor(state-mgmt): move state management code to own module
This commit is contained in:
parent
1bec59c814
commit
c8faae8637
9 changed files with 60 additions and 53 deletions
7
cli.go
7
cli.go
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"strings"
|
||||
state "v/state"
|
||||
)
|
||||
|
||||
type Flags struct {
|
||||
|
@ -14,7 +15,7 @@ type Flags struct {
|
|||
// Command definition for CLI subcommands.
|
||||
type Command struct {
|
||||
Label string
|
||||
Handler func([]string, Flags, State) error
|
||||
Handler func([]string, Flags, state.State) error
|
||||
Usage string
|
||||
Description string
|
||||
}
|
||||
|
@ -34,7 +35,7 @@ type CLI struct {
|
|||
// This specifies a label that is used to route the user input to
|
||||
// the right command, a handler that is called when the label is used,
|
||||
// and usage/description details that get included in autogenerated help messaging.
|
||||
func (c CLI) AddCommand(label string, handler func([]string, Flags, State) error, usage string, description string) CLI {
|
||||
func (c CLI) AddCommand(label string, handler func([]string, Flags, state.State) error, usage string, description string) CLI {
|
||||
if c.Commands == nil {
|
||||
c.Commands = map[string]Command{}
|
||||
c.OrderedCommands = []string{}
|
||||
|
@ -48,7 +49,7 @@ func (c CLI) AddCommand(label string, handler func([]string, Flags, State) error
|
|||
|
||||
// Executes one of the registered commands if any match the provided
|
||||
// user arguments.
|
||||
func (c CLI) Run(args []string, currentState State) error {
|
||||
func (c CLI) Run(args []string, currentState state.State) error {
|
||||
if len(args) == 0 {
|
||||
c.Help()
|
||||
return nil
|
||||
|
|
31
commands.go
31
commands.go
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"os"
|
||||
"slices"
|
||||
state "v/state"
|
||||
)
|
||||
|
||||
var DIRECTORIES = []string{
|
||||
|
@ -29,42 +30,42 @@ func writeShim(shimPath string) error {
|
|||
|
||||
// Sets up directories and files used to store downloaded archives,
|
||||
// installed runtimes and metadata.
|
||||
func Initialize(args []string, flags Flags, currentState State) error {
|
||||
func Initialize(args []string, flags Flags, currentState state.State) error {
|
||||
if flags.AddPath {
|
||||
InfoLogger.Printf("export PATH=%s:$PATH\n", GetStatePath("shims"))
|
||||
InfoLogger.Printf("export PATH=%s:$PATH\n", state.GetStatePath("shims"))
|
||||
return nil
|
||||
}
|
||||
|
||||
os.Mkdir(GetStatePath(), DEFAULT_PERMISSION)
|
||||
os.Mkdir(state.GetStatePath(), DEFAULT_PERMISSION)
|
||||
for _, dir := range DIRECTORIES {
|
||||
os.Mkdir(GetStatePath(dir), DEFAULT_PERMISSION)
|
||||
os.Mkdir(state.GetStatePath(dir), DEFAULT_PERMISSION)
|
||||
}
|
||||
|
||||
for _, shim := range SHIMS {
|
||||
writeShim(GetStatePath("shims", shim))
|
||||
writeShim(state.GetStatePath("shims", shim))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func UninstallPython(args []string, flags Flags, currentState State) error {
|
||||
runtimePath := GetStatePath("runtimes", "py-"+args[1])
|
||||
func UninstallPython(args []string, flags Flags, currentState state.State) error {
|
||||
runtimePath := state.GetStatePath("runtimes", "py-"+args[1])
|
||||
err := os.RemoveAll(runtimePath)
|
||||
return err
|
||||
}
|
||||
|
||||
func InstallPython(args []string, flags Flags, currentState State) error {
|
||||
func InstallPython(args []string, flags Flags, currentState state.State) error {
|
||||
version := args[1]
|
||||
|
||||
return InstallPythonDistribution(version, flags.NoCache, flags.Verbose)
|
||||
}
|
||||
|
||||
func Use(args []string, flags Flags, currentState State) error {
|
||||
func Use(args []string, flags Flags, currentState state.State) error {
|
||||
version := args[1]
|
||||
if err := ValidateVersion(version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
availableVersions := GetAvailableVersions()
|
||||
availableVersions := state.GetAvailableVersions()
|
||||
|
||||
found := false
|
||||
for _, v := range availableVersions {
|
||||
|
@ -79,12 +80,12 @@ func Use(args []string, flags Flags, currentState State) error {
|
|||
InstallPythonDistribution(version, flags.NoCache, flags.Verbose)
|
||||
}
|
||||
|
||||
WriteState(version)
|
||||
state.WriteState(version)
|
||||
InfoLogger.Printf("Now using Python %s\n", version)
|
||||
|
||||
return nil
|
||||
}
|
||||
func ListVersions(args []string, flags Flags, currentState State) error {
|
||||
func ListVersions(args []string, flags Flags, currentState state.State) error {
|
||||
installedVersions, err := ListInstalledVersions()
|
||||
|
||||
if err != nil {
|
||||
|
@ -104,7 +105,7 @@ func ListVersions(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 {
|
||||
func Which(args []string, flags Flags, currentState state.State) error {
|
||||
selectedVersion, _ := DetermineSelectedPythonVersion(currentState)
|
||||
installedVersions, _ := ListInstalledVersions()
|
||||
isInstalled := slices.Contains(installedVersions, selectedVersion.Version)
|
||||
|
@ -116,7 +117,7 @@ func Which(args []string, flags Flags, currentState State) error {
|
|||
printedPath = sysPath + " (system)"
|
||||
} else if isInstalled {
|
||||
tag := VersionStringToStruct(selectedVersion.Version)
|
||||
printedPath = GetStatePath("runtimes", "py-"+selectedVersion.Version, "bin", "python"+tag.MajorMinor())
|
||||
printedPath = state.GetStatePath("runtimes", "py-"+selectedVersion.Version, "bin", "python"+tag.MajorMinor())
|
||||
} else {
|
||||
InfoLogger.Printf("The desired version (%s) is not installed.\n", selectedVersion.Version)
|
||||
return nil
|
||||
|
@ -137,7 +138,7 @@ 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 {
|
||||
func CurrentVersion(args []string, flags Flags, currentState state.State) error {
|
||||
selectedVersion, _ := DetermineSelectedPythonVersion(currentState)
|
||||
installedVersions, _ := ListInstalledVersions()
|
||||
isInstalled := slices.Contains(installedVersions, selectedVersion.Version)
|
||||
|
|
|
@ -5,18 +5,19 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
state "v/state"
|
||||
)
|
||||
|
||||
func TestListVersionOutputsNoticeIfNoVersionsInstalled(t *testing.T) {
|
||||
defer SetupAndCleanupEnvironment(t)()
|
||||
|
||||
os.Mkdir(GetStatePath("runtimes"), 0750)
|
||||
os.Mkdir(state.GetStatePath("runtimes"), 0750)
|
||||
var out bytes.Buffer
|
||||
|
||||
InfoLogger.SetOutput(&out)
|
||||
defer InfoLogger.SetOutput(os.Stdout)
|
||||
|
||||
ListVersions([]string{}, Flags{}, State{})
|
||||
ListVersions([]string{}, Flags{}, state.State{})
|
||||
|
||||
captured := out.String()
|
||||
if captured != "No versions installed!\n" {
|
||||
|
@ -27,13 +28,13 @@ func TestListVersionOutputsNoticeIfNoVersionsInstalled(t *testing.T) {
|
|||
func TestListVersionOutputsVersionsInstalled(t *testing.T) {
|
||||
defer SetupAndCleanupEnvironment(t)()
|
||||
|
||||
os.MkdirAll(GetStatePath("runtimes", "py-1.2.3"), 0750)
|
||||
os.MkdirAll(state.GetStatePath("runtimes", "py-1.2.3"), 0750)
|
||||
var out bytes.Buffer
|
||||
|
||||
InfoLogger.SetOutput(&out)
|
||||
defer InfoLogger.SetOutput(os.Stdout)
|
||||
|
||||
ListVersions([]string{}, Flags{}, State{})
|
||||
ListVersions([]string{}, Flags{}, state.State{})
|
||||
|
||||
captured := out.String()
|
||||
if captured != "1.2.3\n" {
|
||||
|
@ -49,7 +50,7 @@ func TestListVersionReturnsErrorOnFailure(t *testing.T) {
|
|||
InfoLogger.SetOutput(&out)
|
||||
defer InfoLogger.SetOutput(os.Stdout)
|
||||
|
||||
err := ListVersions([]string{}, Flags{}, State{})
|
||||
err := ListVersions([]string{}, Flags{}, state.State{})
|
||||
|
||||
captured := out.String()
|
||||
if captured != "" {
|
||||
|
@ -69,7 +70,7 @@ func TestListVersionOutputsVersionSelectedAndWarnsNotInstalled(t *testing.T) {
|
|||
InfoLogger.SetOutput(&out)
|
||||
defer InfoLogger.SetOutput(os.Stdout)
|
||||
|
||||
Which([]string{}, Flags{}, State{GlobalVersion: "1.2.3"})
|
||||
Which([]string{}, Flags{}, state.State{GlobalVersion: "1.2.3"})
|
||||
|
||||
captured := out.String()
|
||||
if captured != "The desired version (1.2.3) is not installed.\n" {
|
||||
|
@ -85,11 +86,11 @@ func TestWhichOutputsVersionSelectedIfInstalled(t *testing.T) {
|
|||
InfoLogger.SetOutput(&out)
|
||||
defer InfoLogger.SetOutput(os.Stdout)
|
||||
|
||||
os.MkdirAll(GetStatePath("runtimes", "py-1.2.3"), 0750)
|
||||
Which([]string{}, Flags{}, State{GlobalVersion: "1.2.3"})
|
||||
os.MkdirAll(state.GetStatePath("runtimes", "py-1.2.3"), 0750)
|
||||
Which([]string{}, Flags{}, state.State{GlobalVersion: "1.2.3"})
|
||||
|
||||
captured := strings.TrimSpace(out.String())
|
||||
expected := GetStatePath("runtimes", "py-1.2.3", "bin", "python1.2")
|
||||
expected := state.GetStatePath("runtimes", "py-1.2.3", "bin", "python1.2")
|
||||
if !strings.Contains(captured, expected) {
|
||||
t.Errorf("Unexpected message: %s, not %s", captured, expected)
|
||||
}
|
||||
|
@ -103,7 +104,7 @@ func TestWhichOutputsSystemVersionIfNoneSelected(t *testing.T) {
|
|||
InfoLogger.SetOutput(&out)
|
||||
defer InfoLogger.SetOutput(os.Stdout)
|
||||
|
||||
Which([]string{}, Flags{RawOutput: true}, State{})
|
||||
Which([]string{}, Flags{RawOutput: true}, state.State{})
|
||||
|
||||
captured := strings.TrimSpace(out.String())
|
||||
|
||||
|
@ -120,11 +121,11 @@ func TestWhichOutputsVersionWithoutPrefixesIfRawOutput(t *testing.T) {
|
|||
InfoLogger.SetOutput(&out)
|
||||
defer InfoLogger.SetOutput(os.Stdout)
|
||||
|
||||
os.MkdirAll(GetStatePath("runtimes", "py-1.2.3"), 0750)
|
||||
Which([]string{}, Flags{RawOutput: true}, State{GlobalVersion: "1.2.3"})
|
||||
os.MkdirAll(state.GetStatePath("runtimes", "py-1.2.3"), 0750)
|
||||
Which([]string{}, Flags{RawOutput: true}, state.State{GlobalVersion: "1.2.3"})
|
||||
|
||||
captured := strings.TrimSpace(out.String())
|
||||
expected := GetStatePath("runtimes", "py-1.2.3", "bin", "python1.2")
|
||||
expected := state.GetStatePath("runtimes", "py-1.2.3", "bin", "python1.2")
|
||||
if captured != expected {
|
||||
t.Errorf("Unexpected message: %s, not %s", captured, expected)
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
state "v/state"
|
||||
)
|
||||
|
||||
var pythonReleasesBaseURL = "https://www.python.org/ftp/python"
|
||||
|
@ -51,7 +52,7 @@ func InstallPythonDistribution(version string, noCache bool, verbose bool) error
|
|||
// Fetches the Python tarball for version <version> from python.org.
|
||||
func downloadSource(version string, skipCache bool) (PackageMetadata, error) {
|
||||
archiveName := "Python-" + version + ".tgz"
|
||||
archivePath := GetStatePath("cache", archiveName)
|
||||
archivePath := state.GetStatePath("cache", archiveName)
|
||||
sourceUrl, _ := url.JoinPath(pythonReleasesBaseURL, version, archiveName)
|
||||
|
||||
client := http.Client{}
|
||||
|
@ -94,7 +95,7 @@ func buildFromSource(pkgMeta PackageMetadata, verbose bool) (PackageMetadata, er
|
|||
|
||||
InfoLogger.Println("Unpacking source for " + pkgMeta.ArchivePath)
|
||||
|
||||
_, untarErr := RunCommand([]string{"tar", "zxvf", pkgMeta.ArchivePath}, GetStatePath("cache"), !verbose)
|
||||
_, untarErr := RunCommand([]string{"tar", "zxvf", pkgMeta.ArchivePath}, state.GetStatePath("cache"), !verbose)
|
||||
|
||||
if untarErr != nil {
|
||||
return pkgMeta, untarErr
|
||||
|
@ -104,7 +105,7 @@ func buildFromSource(pkgMeta PackageMetadata, verbose bool) (PackageMetadata, er
|
|||
|
||||
InfoLogger.Println("Configuring installer")
|
||||
|
||||
targetDirectory := GetStatePath("runtimes", "py-"+pkgMeta.Version)
|
||||
targetDirectory := state.GetStatePath("runtimes", "py-"+pkgMeta.Version)
|
||||
|
||||
_, configureErr := RunCommand([]string{"./configure", "--prefix=" + targetDirectory, "--enable-optimizations"}, unzippedRoot, !verbose)
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
state "v/state"
|
||||
)
|
||||
|
||||
type SelectedVersion struct {
|
||||
|
@ -13,7 +14,7 @@ type SelectedVersion struct {
|
|||
}
|
||||
|
||||
func ListInstalledVersions() ([]string, error) {
|
||||
runtimesDir := GetStatePath("runtimes")
|
||||
runtimesDir := state.GetStatePath("runtimes")
|
||||
entries, err := os.ReadDir(runtimesDir)
|
||||
|
||||
if err != nil {
|
||||
|
@ -65,7 +66,7 @@ func SearchForPythonVersionFile() (SelectedVersion, 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) (SelectedVersion, error) {
|
||||
func DetermineSelectedPythonVersion(currentState state.State) (SelectedVersion, error) {
|
||||
pythonFileVersion, pythonFileVersionFound := SearchForPythonVersionFile()
|
||||
|
||||
if pythonFileVersionFound {
|
||||
|
@ -73,7 +74,7 @@ func DetermineSelectedPythonVersion(currentState State) (SelectedVersion, error)
|
|||
}
|
||||
|
||||
if len(currentState.GlobalVersion) != 0 {
|
||||
return SelectedVersion{Version: currentState.GlobalVersion, Source: GetStatePath("state.json")}, nil
|
||||
return SelectedVersion{Version: currentState.GlobalVersion, Source: state.GetStatePath("state.json")}, nil
|
||||
}
|
||||
|
||||
systemVersion, _ := DetermineSystemPython()
|
||||
|
@ -83,7 +84,7 @@ func DetermineSelectedPythonVersion(currentState State) (SelectedVersion, error)
|
|||
// DetermineSystemPython returns the unshimmed Python version and path.
|
||||
// It assumes that /bin/python is where system Python lives.
|
||||
func DetermineSystemPython() (string, string) {
|
||||
versionOut, _ := RunCommand([]string{"/bin/python", "--version"}, GetStatePath(), true)
|
||||
versionOut, _ := RunCommand([]string{"/bin/python", "--version"}, state.GetStatePath(), true)
|
||||
detectedVersion, _ := strings.CutPrefix(versionOut, "Python")
|
||||
return strings.TrimSpace(detectedVersion), "/bin/python"
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"path"
|
||||
"slices"
|
||||
"testing"
|
||||
state "v/state"
|
||||
)
|
||||
|
||||
// SetupAndCleanupEnvironment sets up a test directory and
|
||||
|
@ -29,12 +30,12 @@ func SetupAndCleanupEnvironment(t *testing.T) func() {
|
|||
func TestDetermineSystemPythonGetsUnshimmedPythonRuntime(t *testing.T) {
|
||||
defer SetupAndCleanupEnvironment(t)()
|
||||
|
||||
ioutil.WriteFile(GetStatePath("shims", "python"), []byte("#!/bin/bash\necho \"Python 4.5.6\""), 0777)
|
||||
ioutil.WriteFile(state.GetStatePath("shims", "python"), []byte("#!/bin/bash\necho \"Python 4.5.6\""), 0777)
|
||||
mockSystemPythonPath := t.TempDir()
|
||||
mockSystemPythonExecPath := path.Join(mockSystemPythonPath, "python")
|
||||
|
||||
oldPath := os.Getenv("PATH")
|
||||
os.Setenv("PATH", fmt.Sprintf("%s:/usr/bin", GetStatePath("shims")))
|
||||
os.Setenv("PATH", fmt.Sprintf("%s:/usr/bin", state.GetStatePath("shims")))
|
||||
defer os.Setenv("PATH", oldPath)
|
||||
sysVersion, sysPath := DetermineSystemPython()
|
||||
|
||||
|
@ -51,8 +52,8 @@ func TestDetermineSelectedPythonVersionUsesPythonVersionFileIfFound(t *testing.T
|
|||
defer SetupAndCleanupEnvironment(t)()
|
||||
|
||||
// Writing a mock user-defined state.
|
||||
mockState := State{GlobalVersion: "1.0.0"}
|
||||
statePath := GetStatePath("state.json")
|
||||
mockState := state.State{GlobalVersion: "1.0.0"}
|
||||
statePath := state.GetStatePath("state.json")
|
||||
stateData, _ := json.Marshal(mockState)
|
||||
ioutil.WriteFile(statePath, stateData, 0750)
|
||||
|
||||
|
@ -60,7 +61,7 @@ func TestDetermineSelectedPythonVersionUsesPythonVersionFileIfFound(t *testing.T
|
|||
os.Chdir(temporaryWd)
|
||||
ioutil.WriteFile(path.Join(temporaryWd, ".python-version"), []byte("1.2.3"), 0750)
|
||||
|
||||
version, err := DetermineSelectedPythonVersion(ReadState())
|
||||
version, err := DetermineSelectedPythonVersion(state.ReadState())
|
||||
|
||||
if err != nil || version.Version != "1.2.3" {
|
||||
t.Errorf("Expected version to be %s, got %s instead.", "1.2.3", version.Version)
|
||||
|
@ -71,12 +72,12 @@ func TestDetermineSelectedPythonVersionGetsUserDefinedVersion(t *testing.T) {
|
|||
defer SetupAndCleanupEnvironment(t)()
|
||||
|
||||
// Writing a mock user-defined state.
|
||||
mockState := State{GlobalVersion: "1.0.0"}
|
||||
statePath := GetStatePath("state.json")
|
||||
mockState := state.State{GlobalVersion: "1.0.0"}
|
||||
statePath := state.GetStatePath("state.json")
|
||||
stateData, _ := json.Marshal(mockState)
|
||||
ioutil.WriteFile(statePath, stateData, 0750)
|
||||
|
||||
version, err := DetermineSelectedPythonVersion(ReadState())
|
||||
version, err := DetermineSelectedPythonVersion(state.ReadState())
|
||||
|
||||
if err != nil || version.Version != mockState.GlobalVersion {
|
||||
t.Errorf("Expected version to be %s, got %s instead.", mockState.GlobalVersion, version)
|
||||
|
@ -86,7 +87,7 @@ func TestDetermineSelectedPythonVersionGetsUserDefinedVersion(t *testing.T) {
|
|||
func TestDetermineSelectedPythonVersionDefaultsToSystem(t *testing.T) {
|
||||
defer SetupAndCleanupEnvironment(t)()
|
||||
|
||||
version, err := DetermineSelectedPythonVersion(ReadState())
|
||||
version, err := DetermineSelectedPythonVersion(state.ReadState())
|
||||
|
||||
if err != nil || version.Source != "system" {
|
||||
t.Errorf("Expected version to be 'SYSTEM', got %s instead.", version)
|
||||
|
@ -139,9 +140,9 @@ func TestListInstalledVersion(t *testing.T) {
|
|||
|
||||
versions := []string{"1.2.3", "4.5.6", "7.8.9"}
|
||||
|
||||
os.Mkdir(GetStatePath("runtimes"), 0750)
|
||||
os.Mkdir(state.GetStatePath("runtimes"), 0750)
|
||||
for _, version := range versions {
|
||||
os.Mkdir(GetStatePath("runtimes", "py-"+version), 0750)
|
||||
os.Mkdir(state.GetStatePath("runtimes", "py-"+version), 0750)
|
||||
}
|
||||
|
||||
installedVersions, _ := ListInstalledVersions()
|
||||
|
@ -154,7 +155,7 @@ func TestListInstalledVersion(t *testing.T) {
|
|||
func TestListInstalledVersionNoVersionsInstalled(t *testing.T) {
|
||||
defer SetupAndCleanupEnvironment(t)()
|
||||
|
||||
os.Mkdir(GetStatePath("runtimes"), 0750)
|
||||
os.Mkdir(state.GetStatePath("runtimes"), 0750)
|
||||
|
||||
installedVersions, _ := ListInstalledVersions()
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package state
|
||||
|
||||
import (
|
||||
"encoding/json"
|
|
@ -1,4 +1,4 @@
|
|||
package main
|
||||
package state
|
||||
|
||||
import (
|
||||
"encoding/json"
|
3
v.go
3
v.go
|
@ -2,6 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"os"
|
||||
state "v/state"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -13,7 +14,7 @@ const (
|
|||
// Main entrypoint.
|
||||
func main() {
|
||||
args := os.Args[1:]
|
||||
currentState := ReadState()
|
||||
currentState := state.ReadState()
|
||||
|
||||
cli := CLI{
|
||||
Metadata: map[string]string{
|
||||
|
|
Loading…
Reference in a new issue