From c0d8d31ace0f9cd7535114488746cfa8ec2eed6a Mon Sep 17 00:00:00 2001 From: Marc Cataford Date: Thu, 25 Jan 2024 23:09:52 -0500 Subject: [PATCH] refactor(python): move python-specific logic to own module --- commands.go | 27 ++++++------- commands_test.go | 15 +++---- util.go => exec/util.go | 17 +------- style.go => logger/style.go | 2 +- install_python.go => python/install.go | 13 ++++--- .../pythonversion_test.go | 39 ++++++------------- pythonversion.go => python/version.go | 20 +++++++++- testutils/setup.go | 22 +++++++++++ 8 files changed, 83 insertions(+), 72 deletions(-) rename util.go => exec/util.go (60%) rename style.go => logger/style.go (93%) rename install_python.go => python/install.go (84%) rename pythonversion_test.go => python/pythonversion_test.go (84%) rename pythonversion.go => python/version.go (81%) create mode 100644 testutils/setup.go diff --git a/commands.go b/commands.go index 0fe7f89..81c6acf 100644 --- a/commands.go +++ b/commands.go @@ -5,6 +5,7 @@ import ( "slices" cli "v/cli" logger "v/logger" + python "v/python" state "v/state" ) @@ -58,12 +59,12 @@ func UninstallPython(args []string, flags cli.Flags, currentState state.State) e func InstallPython(args []string, flags cli.Flags, currentState state.State) error { version := args[1] - return InstallPythonDistribution(version, flags.NoCache, flags.Verbose) + return python.InstallPythonDistribution(version, flags.NoCache, flags.Verbose) } func Use(args []string, flags cli.Flags, currentState state.State) error { version := args[1] - if err := ValidateVersion(version); err != nil { + if err := python.ValidateVersion(version); err != nil { return err } @@ -79,7 +80,7 @@ func Use(args []string, flags cli.Flags, currentState state.State) error { if !found { logger.InfoLogger.Println("Version not installed. Installing it first.") - InstallPythonDistribution(version, flags.NoCache, flags.Verbose) + python.InstallPythonDistribution(version, flags.NoCache, flags.Verbose) } state.WriteState(version) @@ -88,7 +89,7 @@ func Use(args []string, flags cli.Flags, currentState state.State) error { return nil } func ListVersions(args []string, flags cli.Flags, currentState state.State) error { - installedVersions, err := ListInstalledVersions() + installedVersions, err := python.ListInstalledVersions() if err != nil { return err @@ -108,17 +109,17 @@ func ListVersions(args []string, flags cli.Flags, currentState state.State) erro // Which prints out the system path to the executable being used by `python`. func Which(args []string, flags cli.Flags, currentState state.State) error { - selectedVersion, _ := DetermineSelectedPythonVersion(currentState) - installedVersions, _ := ListInstalledVersions() + selectedVersion, _ := python.DetermineSelectedPythonVersion(currentState) + installedVersions, _ := python.ListInstalledVersions() isInstalled := slices.Contains(installedVersions, selectedVersion.Version) var printedPath string if selectedVersion.Source == "system" { - _, sysPath := DetermineSystemPython() + _, sysPath := python.DetermineSystemPython() printedPath = sysPath + " (system)" } else if isInstalled { - tag := VersionStringToStruct(selectedVersion.Version) + tag := python.VersionStringToStruct(selectedVersion.Version) printedPath = state.GetStatePath("runtimes", "py-"+selectedVersion.Version, "bin", "python"+tag.MajorMinor()) } else { logger.InfoLogger.Printf("The desired version (%s) is not installed.\n", selectedVersion.Version) @@ -130,7 +131,7 @@ func Which(args []string, flags cli.Flags, currentState state.State) error { if flags.RawOutput { prefix = "" } else { - printedPath = Bold(printedPath) + printedPath = logger.Bold(printedPath) } logger.InfoLogger.Printf("%s%s\n", prefix, printedPath) @@ -141,12 +142,12 @@ func Which(args []string, flags cli.Flags, currentState state.State) error { // 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 cli.Flags, currentState state.State) error { - selectedVersion, _ := DetermineSelectedPythonVersion(currentState) - installedVersions, _ := ListInstalledVersions() + selectedVersion, _ := python.DetermineSelectedPythonVersion(currentState) + installedVersions, _ := python.ListInstalledVersions() isInstalled := slices.Contains(installedVersions, selectedVersion.Version) if !isInstalled { - logger.InfoLogger.Println(Bold(Yellow("WARNING: This version is not installed."))) + logger.InfoLogger.Println(logger.Bold(logger.Yellow("WARNING: This version is not installed."))) } if flags.RawOutput { @@ -154,6 +155,6 @@ func CurrentVersion(args []string, flags cli.Flags, currentState state.State) er return nil } - logger.InfoLogger.Printf("Python version: %s\nSource: %s\n", Bold(selectedVersion.Version), Bold(selectedVersion.Source)) + logger.InfoLogger.Printf("Python version: %s\nSource: %s\n", logger.Bold(selectedVersion.Version), logger.Bold(selectedVersion.Source)) return nil } diff --git a/commands_test.go b/commands_test.go index 4c2e460..ad3f469 100644 --- a/commands_test.go +++ b/commands_test.go @@ -8,10 +8,11 @@ import ( cli "v/cli" logger "v/logger" state "v/state" + testutils "v/testutils" ) func TestListVersionOutputsNoticeIfNoVersionsInstalled(t *testing.T) { - defer SetupAndCleanupEnvironment(t)() + defer testutils.SetupAndCleanupEnvironment(t)() os.Mkdir(state.GetStatePath("runtimes"), 0750) var out bytes.Buffer @@ -28,7 +29,7 @@ func TestListVersionOutputsNoticeIfNoVersionsInstalled(t *testing.T) { } func TestListVersionOutputsVersionsInstalled(t *testing.T) { - defer SetupAndCleanupEnvironment(t)() + defer testutils.SetupAndCleanupEnvironment(t)() os.MkdirAll(state.GetStatePath("runtimes", "py-1.2.3"), 0750) var out bytes.Buffer @@ -45,7 +46,7 @@ func TestListVersionOutputsVersionsInstalled(t *testing.T) { } func TestListVersionReturnsErrorOnFailure(t *testing.T) { - defer SetupAndCleanupEnvironment(t)() + defer testutils.SetupAndCleanupEnvironment(t)() var out bytes.Buffer @@ -65,7 +66,7 @@ func TestListVersionReturnsErrorOnFailure(t *testing.T) { } func TestListVersionOutputsVersionSelectedAndWarnsNotInstalled(t *testing.T) { - defer SetupAndCleanupEnvironment(t)() + defer testutils.SetupAndCleanupEnvironment(t)() var out bytes.Buffer @@ -81,7 +82,7 @@ func TestListVersionOutputsVersionSelectedAndWarnsNotInstalled(t *testing.T) { } func TestWhichOutputsVersionSelectedIfInstalled(t *testing.T) { - defer SetupAndCleanupEnvironment(t)() + defer testutils.SetupAndCleanupEnvironment(t)() var out bytes.Buffer @@ -99,7 +100,7 @@ func TestWhichOutputsVersionSelectedIfInstalled(t *testing.T) { } func TestWhichOutputsSystemVersionIfNoneSelected(t *testing.T) { - defer SetupAndCleanupEnvironment(t)() + defer testutils.SetupAndCleanupEnvironment(t)() var out bytes.Buffer @@ -116,7 +117,7 @@ func TestWhichOutputsSystemVersionIfNoneSelected(t *testing.T) { } func TestWhichOutputsVersionWithoutPrefixesIfRawOutput(t *testing.T) { - defer SetupAndCleanupEnvironment(t)() + defer testutils.SetupAndCleanupEnvironment(t)() var out bytes.Buffer diff --git a/util.go b/exec/util.go similarity index 60% rename from util.go rename to exec/util.go index ead3fb0..8ea239e 100644 --- a/util.go +++ b/exec/util.go @@ -1,26 +1,11 @@ -package main +package exec import ( - "errors" "os" "os/exec" "strings" ) -func VersionStringToStruct(version string) VersionTag { - splitVersion := strings.Split(version, ".") - - return VersionTag{Major: splitVersion[0], Minor: splitVersion[1], Patch: splitVersion[2]} -} - -func ValidateVersion(version string) error { - if splitVersion := strings.Split(version, "."); len(splitVersion) != 3 { - return errors.New("Invalid version string. Expected format 'a.b.c'.") - } - - return nil -} - // RunCommand is a thin wrapper around running command-line calls // programmatically. It abstracts common configuration like routing // output and handling the directory the calls are made from. diff --git a/style.go b/logger/style.go similarity index 93% rename from style.go rename to logger/style.go index c6fb774..18e9b35 100644 --- a/style.go +++ b/logger/style.go @@ -1,4 +1,4 @@ -package main +package logger const ( RESET = "\033[0m" diff --git a/install_python.go b/python/install.go similarity index 84% rename from install_python.go rename to python/install.go index affc3dd..4ff1cbc 100644 --- a/install_python.go +++ b/python/install.go @@ -1,4 +1,4 @@ -package main +package python import ( "errors" @@ -9,6 +9,7 @@ import ( "path" "strings" "time" + exec "v/exec" logger "v/logger" state "v/state" ) @@ -58,7 +59,7 @@ func downloadSource(version string, skipCache bool) (PackageMetadata, error) { client := http.Client{} - logger.InfoLogger.Println(Bold("Downloading source for Python " + version)) + logger.InfoLogger.Println(logger.Bold("Downloading source for Python " + version)) logger.InfoLogger.SetPrefix(" ") defer logger.InfoLogger.SetPrefix("") @@ -88,7 +89,7 @@ func downloadSource(version string, skipCache bool) (PackageMetadata, error) { } func buildFromSource(pkgMeta PackageMetadata, verbose bool) (PackageMetadata, error) { - logger.InfoLogger.Println(Bold("Building from source")) + logger.InfoLogger.Println(logger.Bold("Building from source")) logger.InfoLogger.SetPrefix(" ") defer logger.InfoLogger.SetPrefix("") @@ -96,7 +97,7 @@ func buildFromSource(pkgMeta PackageMetadata, verbose bool) (PackageMetadata, er logger.InfoLogger.Println("Unpacking source for " + pkgMeta.ArchivePath) - _, untarErr := RunCommand([]string{"tar", "zxvf", pkgMeta.ArchivePath}, state.GetStatePath("cache"), !verbose) + _, untarErr := exec.RunCommand([]string{"tar", "zxvf", pkgMeta.ArchivePath}, state.GetStatePath("cache"), !verbose) if untarErr != nil { return pkgMeta, untarErr @@ -108,14 +109,14 @@ func buildFromSource(pkgMeta PackageMetadata, verbose bool) (PackageMetadata, er targetDirectory := state.GetStatePath("runtimes", "py-"+pkgMeta.Version) - _, configureErr := RunCommand([]string{"./configure", "--prefix=" + targetDirectory, "--enable-optimizations"}, unzippedRoot, !verbose) + _, configureErr := exec.RunCommand([]string{"./configure", "--prefix=" + targetDirectory, "--enable-optimizations"}, unzippedRoot, !verbose) if configureErr != nil { return pkgMeta, configureErr } logger.InfoLogger.Println("Building") - _, buildErr := RunCommand([]string{"make", "altinstall", "-j4"}, unzippedRoot, !verbose) + _, buildErr := exec.RunCommand([]string{"make", "altinstall", "-j4"}, unzippedRoot, !verbose) if buildErr != nil { return pkgMeta, buildErr diff --git a/pythonversion_test.go b/python/pythonversion_test.go similarity index 84% rename from pythonversion_test.go rename to python/pythonversion_test.go index 486974a..7aa4fab 100644 --- a/pythonversion_test.go +++ b/python/pythonversion_test.go @@ -1,4 +1,4 @@ -package main +package python import ( "encoding/json" @@ -9,26 +9,11 @@ import ( "slices" "testing" state "v/state" + testutils "v/testutils" ) -// SetupAndCleanupEnvironment sets up a test directory and -// environment variables before the test and returns a cleanup -// function that can be deferred to cleanup any changes to the -// system. -func SetupAndCleanupEnvironment(t *testing.T) func() { - os.Setenv("V_ROOT", t.TempDir()) - - temporaryWd := t.TempDir() - - os.Chdir(temporaryWd) - - return func() { - os.Unsetenv("V_ROOT") - } -} - func TestDetermineSystemPythonGetsUnshimmedPythonRuntime(t *testing.T) { - defer SetupAndCleanupEnvironment(t)() + defer testutils.SetupAndCleanupEnvironment(t)() ioutil.WriteFile(state.GetStatePath("shims", "python"), []byte("#!/bin/bash\necho \"Python 4.5.6\""), 0777) mockSystemPythonPath := t.TempDir() @@ -49,7 +34,7 @@ func TestDetermineSystemPythonGetsUnshimmedPythonRuntime(t *testing.T) { } func TestDetermineSelectedPythonVersionUsesPythonVersionFileIfFound(t *testing.T) { - defer SetupAndCleanupEnvironment(t)() + defer testutils.SetupAndCleanupEnvironment(t)() // Writing a mock user-defined state. mockState := state.State{GlobalVersion: "1.0.0"} @@ -69,7 +54,7 @@ func TestDetermineSelectedPythonVersionUsesPythonVersionFileIfFound(t *testing.T } func TestDetermineSelectedPythonVersionGetsUserDefinedVersion(t *testing.T) { - defer SetupAndCleanupEnvironment(t)() + defer testutils.SetupAndCleanupEnvironment(t)() // Writing a mock user-defined state. mockState := state.State{GlobalVersion: "1.0.0"} @@ -85,7 +70,7 @@ func TestDetermineSelectedPythonVersionGetsUserDefinedVersion(t *testing.T) { } func TestDetermineSelectedPythonVersionDefaultsToSystem(t *testing.T) { - defer SetupAndCleanupEnvironment(t)() + defer testutils.SetupAndCleanupEnvironment(t)() version, err := DetermineSelectedPythonVersion(state.ReadState()) @@ -95,7 +80,7 @@ func TestDetermineSelectedPythonVersionDefaultsToSystem(t *testing.T) { } func TestSearchForPythonVersionFileFindsFileInCwd(t *testing.T) { - defer SetupAndCleanupEnvironment(t)() + defer testutils.SetupAndCleanupEnvironment(t)() temporaryWd := t.TempDir() os.Chdir(temporaryWd) @@ -109,7 +94,7 @@ func TestSearchForPythonVersionFileFindsFileInCwd(t *testing.T) { } func TestSearchForPythonVersionFileFindsFileInParents(t *testing.T) { - defer SetupAndCleanupEnvironment(t)() + defer testutils.SetupAndCleanupEnvironment(t)() temporaryWd := t.TempDir() @@ -126,7 +111,7 @@ func TestSearchForPythonVersionFileFindsFileInParents(t *testing.T) { } func TestSearchForPythonVersionFileReturnsOnRootIfNoneFound(t *testing.T) { - defer SetupAndCleanupEnvironment(t)() + defer testutils.SetupAndCleanupEnvironment(t)() versionFound, found := SearchForPythonVersionFile() @@ -136,7 +121,7 @@ func TestSearchForPythonVersionFileReturnsOnRootIfNoneFound(t *testing.T) { } func TestListInstalledVersion(t *testing.T) { - defer SetupAndCleanupEnvironment(t)() + defer testutils.SetupAndCleanupEnvironment(t)() versions := []string{"1.2.3", "4.5.6", "7.8.9"} @@ -153,7 +138,7 @@ func TestListInstalledVersion(t *testing.T) { } func TestListInstalledVersionNoVersionsInstalled(t *testing.T) { - defer SetupAndCleanupEnvironment(t)() + defer testutils.SetupAndCleanupEnvironment(t)() os.Mkdir(state.GetStatePath("runtimes"), 0750) @@ -165,7 +150,7 @@ func TestListInstalledVersionNoVersionsInstalled(t *testing.T) { } func TestListInstalledVersionNoRuntimesDir(t *testing.T) { - defer SetupAndCleanupEnvironment(t)() + defer testutils.SetupAndCleanupEnvironment(t)() installedVersions, err := ListInstalledVersions() diff --git a/pythonversion.go b/python/version.go similarity index 81% rename from pythonversion.go rename to python/version.go index 865db36..210cbd0 100644 --- a/pythonversion.go +++ b/python/version.go @@ -1,13 +1,29 @@ -package main +package python import ( + "errors" "io/ioutil" "os" "path" "strings" + exec "v/exec" state "v/state" ) +func VersionStringToStruct(version string) VersionTag { + splitVersion := strings.Split(version, ".") + + return VersionTag{Major: splitVersion[0], Minor: splitVersion[1], Patch: splitVersion[2]} +} + +func ValidateVersion(version string) error { + if splitVersion := strings.Split(version, "."); len(splitVersion) != 3 { + return errors.New("Invalid version string. Expected format 'a.b.c'.") + } + + return nil +} + type SelectedVersion struct { Version string Source string @@ -84,7 +100,7 @@ func DetermineSelectedPythonVersion(currentState state.State) (SelectedVersion, // 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"}, state.GetStatePath(), true) + versionOut, _ := exec.RunCommand([]string{"/bin/python", "--version"}, state.GetStatePath(), true) detectedVersion, _ := strings.CutPrefix(versionOut, "Python") return strings.TrimSpace(detectedVersion), "/bin/python" } diff --git a/testutils/setup.go b/testutils/setup.go new file mode 100644 index 0000000..4228235 --- /dev/null +++ b/testutils/setup.go @@ -0,0 +1,22 @@ +package testutils + +import ( + "os" + "testing" +) + +// SetupAndCleanupEnvironment sets up a test directory and +// environment variables before the test and returns a cleanup +// function that can be deferred to cleanup any changes to the +// system. +func SetupAndCleanupEnvironment(t *testing.T) func() { + os.Setenv("V_ROOT", t.TempDir()) + + temporaryWd := t.TempDir() + + os.Chdir(temporaryWd) + + return func() { + os.Unsetenv("V_ROOT") + } +}