refactor: simplify project structure (#2)
* refactor: remove file structure cruft * refactor: flatten file structure entirely * refactor: combine utils
This commit is contained in:
parent
7a1912c02a
commit
ab711a28b8
16 changed files with 207 additions and 263 deletions
|
@ -1,15 +1,18 @@
|
|||
package argparse
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
stateManager "v/internal/state"
|
||||
)
|
||||
|
||||
type Flags struct {
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
// Command definition for CLI subcommands.
|
||||
type Command struct {
|
||||
Label string
|
||||
Handler func([]string, Flags, stateManager.State) error
|
||||
Handler func([]string, Flags, State) error
|
||||
Usage string
|
||||
Description string
|
||||
}
|
||||
|
@ -29,7 +32,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, stateManager.State) error, usage string, description string) CLI {
|
||||
func (c CLI) AddCommand(label string, handler func([]string, Flags, State) error, usage string, description string) CLI {
|
||||
if c.Commands == nil {
|
||||
c.Commands = map[string]Command{}
|
||||
c.OrderedCommands = []string{}
|
||||
|
@ -43,7 +46,7 @@ func (c CLI) AddCommand(label string, handler func([]string, Flags, stateManager
|
|||
|
||||
// Executes one of the registered commands if any match the provided
|
||||
// user arguments.
|
||||
func (c CLI) Run(args []string, currentState stateManager.State) error {
|
||||
func (c CLI) Run(args []string, currentState State) error {
|
||||
if len(args) == 0 {
|
||||
c.Help()
|
||||
return nil
|
||||
|
@ -56,7 +59,7 @@ func (c CLI) Run(args []string, currentState stateManager.State) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
flags := CollectFlags(args)
|
||||
flags := collectFlags(args)
|
||||
return c.Commands[command].Handler(args, flags, currentState)
|
||||
}
|
||||
|
||||
|
@ -76,3 +79,21 @@ func (c CLI) Help() {
|
|||
|
||||
fmt.Println(helpString)
|
||||
}
|
||||
|
||||
// Traverses input arguments and extracts flags of
|
||||
// the form --<flag-label>.
|
||||
func collectFlags(args []string) Flags {
|
||||
collected := Flags{}
|
||||
|
||||
for _, arg := range args {
|
||||
if !strings.HasPrefix(arg, "--") {
|
||||
continue
|
||||
}
|
||||
|
||||
if arg == "--verbose" {
|
||||
collected.Verbose = true
|
||||
}
|
||||
}
|
||||
|
||||
return collected
|
||||
}
|
45
cmd/v.go
45
cmd/v.go
|
@ -1,45 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
argparse "v/internal/argparse"
|
||||
stateManager "v/internal/state"
|
||||
subcommands "v/internal/subcommands"
|
||||
)
|
||||
|
||||
const (
|
||||
Version = "0.0.1"
|
||||
Author = "mcataford <hello@karnov.club>"
|
||||
)
|
||||
|
||||
// Main entrypoint.
|
||||
func main() {
|
||||
args := os.Args[1:]
|
||||
currentState := stateManager.ReadState()
|
||||
|
||||
cli := argparse.CLI{
|
||||
Metadata: map[string]string{
|
||||
"Version": Version,
|
||||
},
|
||||
}
|
||||
|
||||
err := cli.AddCommand(
|
||||
"install", subcommands.InstallPython, "v install <version>", "Downloads, builds and installs a new version of Python.",
|
||||
).AddCommand(
|
||||
"uninstall", subcommands.UninstallPython, "v uninstall <version>", "Uninstalls the given Python version.",
|
||||
).AddCommand(
|
||||
"use", subcommands.Use, "v use <version>", "Selects which Python version to use.",
|
||||
).AddCommand(
|
||||
"ls", subcommands.ListVersions, "v ls", "Lists the installed Python versions.",
|
||||
).AddCommand(
|
||||
"where", subcommands.Where, "v where", "Prints the path to the current Python version.",
|
||||
).AddCommand(
|
||||
"which", subcommands.Which, "v which", "Prints the current Python version.",
|
||||
).AddCommand(
|
||||
"init", subcommands.Initialize, "v init", "Initializes the v state.",
|
||||
).Run(args, currentState)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
111
commands.go
Normal file
111
commands.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var DIRECTORIES = []string{
|
||||
"cache",
|
||||
"runtimes",
|
||||
"shims",
|
||||
}
|
||||
|
||||
var SHIMS = []string{
|
||||
"python",
|
||||
"python3",
|
||||
}
|
||||
|
||||
const DEFAULT_PERMISSION = 0775
|
||||
|
||||
func writeShim(shimPath string) error {
|
||||
shimContent := []byte("#!/bin/bash\n$(vm where) $@")
|
||||
if err := os.WriteFile(shimPath, shimContent, DEFAULT_PERMISSION); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sets up directories and files used to store downloaded archives,
|
||||
// installed runtimes and metadata.
|
||||
func Initialize(args []string, flags Flags, currentState State) error {
|
||||
stateDirectory := GetStateDirectory()
|
||||
|
||||
os.Mkdir(stateDirectory, DEFAULT_PERMISSION)
|
||||
for _, dir := range DIRECTORIES {
|
||||
os.Mkdir(GetPathFromStateDirectory(dir), DEFAULT_PERMISSION)
|
||||
}
|
||||
|
||||
for _, shim := range SHIMS {
|
||||
writeShim(GetPathFromStateDirectory(path.Join("shims", shim)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func UninstallPython(args []string, flags Flags, currentState State) error {
|
||||
runtimePath := GetPathFromStateDirectory(path.Join("runtimes", fmt.Sprintf("py-%s", args[1])))
|
||||
err := os.RemoveAll(runtimePath)
|
||||
return err
|
||||
}
|
||||
|
||||
func Use(args []string, flags Flags, currentState State) error {
|
||||
version := args[1]
|
||||
if err := ValidateVersion(version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
availableVersions := GetAvailableVersions()
|
||||
|
||||
found := false
|
||||
for _, v := range availableVersions {
|
||||
if v == version {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return errors.New("Version not installed.")
|
||||
}
|
||||
|
||||
WriteState(version)
|
||||
fmt.Printf("Now using Python %s\n", version)
|
||||
|
||||
return nil
|
||||
}
|
||||
func ListVersions(args []string, flags Flags, currentState State) error {
|
||||
runtimesDir := GetPathFromStateDirectory("runtimes")
|
||||
entries, err := os.ReadDir(runtimesDir)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
fmt.Println("No versions installed!")
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, d := range entries {
|
||||
fmt.Println(strings.TrimPrefix(d.Name(), "py-"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Where(args []string, flags Flags, currentState State) error {
|
||||
version := currentState.GlobalVersion
|
||||
tag := VersionStringToStruct(version)
|
||||
withoutPatch := fmt.Sprintf("%s.%s", tag.Major, tag.Minor)
|
||||
fmt.Printf("%s/runtimes/py-%s/bin/python%s\n", GetStateDirectory(), currentState.GlobalVersion, withoutPatch)
|
||||
return nil
|
||||
}
|
||||
|
||||
func Which(args []string, flags Flags, currentState State) error {
|
||||
fmt.Println(currentState.GlobalVersion)
|
||||
return nil
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package subcommands
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -8,9 +8,6 @@ import (
|
|||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
argparse "v/internal/argparse"
|
||||
stateManager "v/internal/state"
|
||||
util "v/internal/util"
|
||||
)
|
||||
|
||||
var pythonReleasesBaseURL = "https://www.python.org/ftp/python"
|
||||
|
@ -27,11 +24,11 @@ type VersionTag struct {
|
|||
Patch string
|
||||
}
|
||||
|
||||
func InstallPython(args []string, flags argparse.Flags, currentState stateManager.State) error {
|
||||
func InstallPython(args []string, flags Flags, currentState State) error {
|
||||
verbose := flags.Verbose
|
||||
version := args[1]
|
||||
|
||||
if err := validateVersion(version); err != nil {
|
||||
if err := ValidateVersion(version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -53,13 +50,13 @@ func InstallPython(args []string, flags argparse.Flags, currentState stateManage
|
|||
// and stores it at <destination>.
|
||||
func downloadSource(version string, destination string) (PackageMetadata, error) {
|
||||
archiveName := fmt.Sprintf("Python-%s.tgz", version)
|
||||
archivePath := stateManager.GetPathFromStateDirectory(path.Join("cache", archiveName))
|
||||
archivePath := GetPathFromStateDirectory(path.Join("cache", archiveName))
|
||||
sourceUrl := fmt.Sprintf("%s/%s/%s", pythonReleasesBaseURL, version, archiveName)
|
||||
file, _ := os.Create(archivePath)
|
||||
|
||||
client := http.Client{}
|
||||
|
||||
dlPrint := util.StartFmtGroup(fmt.Sprintf("Downloading source for Python %s", version))
|
||||
dlPrint := StartFmtGroup(fmt.Sprintf("Downloading source for Python %s", version))
|
||||
|
||||
dlPrint(fmt.Sprintf("Fetching from %s", sourceUrl))
|
||||
start := time.Now()
|
||||
|
@ -80,12 +77,12 @@ func downloadSource(version string, destination string) (PackageMetadata, error)
|
|||
}
|
||||
|
||||
func buildFromSource(pkgMeta PackageMetadata, verbose bool) (PackageMetadata, error) {
|
||||
buildPrint := util.StartFmtGroup(fmt.Sprintf("Building from source"))
|
||||
buildPrint := StartFmtGroup(fmt.Sprintf("Building from source"))
|
||||
start := time.Now()
|
||||
|
||||
buildPrint(fmt.Sprintf("Unpacking source for %s", pkgMeta.ArchivePath))
|
||||
|
||||
_, untarErr := RunCommand([]string{"tar", "zxvf", pkgMeta.ArchivePath}, stateManager.GetPathFromStateDirectory("cache"), !verbose)
|
||||
_, untarErr := RunCommand([]string{"tar", "zxvf", pkgMeta.ArchivePath}, GetPathFromStateDirectory("cache"), !verbose)
|
||||
|
||||
if untarErr != nil {
|
||||
return pkgMeta, untarErr
|
||||
|
@ -95,7 +92,7 @@ func buildFromSource(pkgMeta PackageMetadata, verbose bool) (PackageMetadata, er
|
|||
|
||||
buildPrint("Configuring installer")
|
||||
|
||||
targetDirectory := stateManager.GetPathFromStateDirectory(path.Join("runtimes", fmt.Sprintf("py-%s", pkgMeta.Version)))
|
||||
targetDirectory := GetPathFromStateDirectory(path.Join("runtimes", fmt.Sprintf("py-%s", pkgMeta.Version)))
|
||||
|
||||
_, configureErr := RunCommand([]string{"./configure", fmt.Sprintf("--prefix=%s", targetDirectory), "--enable-optimizations"}, unzippedRoot, !verbose)
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
package argparse
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Flags struct {
|
||||
Verbose bool
|
||||
}
|
||||
|
||||
func CollectFlags(args []string) Flags {
|
||||
collected := Flags{}
|
||||
|
||||
for _, arg := range args {
|
||||
if !strings.HasPrefix(arg, "--") {
|
||||
continue
|
||||
}
|
||||
|
||||
if arg == "--verbose" {
|
||||
collected.Verbose = true
|
||||
}
|
||||
}
|
||||
|
||||
return collected
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package subcommands
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
argparse "v/internal/argparse"
|
||||
stateManager "v/internal/state"
|
||||
)
|
||||
|
||||
var DIRECTORIES = []string{
|
||||
"cache",
|
||||
"runtimes",
|
||||
"shims",
|
||||
}
|
||||
|
||||
var SHIMS = []string{
|
||||
"python",
|
||||
"python3",
|
||||
}
|
||||
|
||||
const DEFAULT_PERMISSION = 0775
|
||||
|
||||
func writeShim(shimPath string) error {
|
||||
shimContent := []byte("#!/bin/bash\n$(vm where) $@")
|
||||
if err := os.WriteFile(shimPath, shimContent, DEFAULT_PERMISSION); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sets up directories and files used to store downloaded archives,
|
||||
// installed runtimes and metadata.
|
||||
func Initialize(args []string, flags argparse.Flags, currentState stateManager.State) error {
|
||||
stateDirectory := stateManager.GetStateDirectory()
|
||||
|
||||
os.Mkdir(stateDirectory, DEFAULT_PERMISSION)
|
||||
for _, dir := range DIRECTORIES {
|
||||
os.Mkdir(stateManager.GetPathFromStateDirectory(dir), DEFAULT_PERMISSION)
|
||||
}
|
||||
|
||||
for _, shim := range SHIMS {
|
||||
writeShim(stateManager.GetPathFromStateDirectory(path.Join("shims", shim)))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package subcommands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
argparse "v/internal/argparse"
|
||||
stateManager "v/internal/state"
|
||||
)
|
||||
|
||||
func ListVersions(args []string, flags argparse.Flags, currentState stateManager.State) error {
|
||||
runtimesDir := stateManager.GetPathFromStateDirectory("runtimes")
|
||||
entries, err := os.ReadDir(runtimesDir)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(entries) == 0 {
|
||||
fmt.Println("No versions installed!")
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, d := range entries {
|
||||
fmt.Println(strings.TrimPrefix(d.Name(), "py-"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package subcommands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
argparse "v/internal/argparse"
|
||||
stateManager "v/internal/state"
|
||||
)
|
||||
|
||||
func UninstallPython(args []string, flags argparse.Flags, currentState stateManager.State) error {
|
||||
runtimePath := stateManager.GetPathFromStateDirectory(path.Join("runtimes", fmt.Sprintf("py-%s", args[1])))
|
||||
err := os.RemoveAll(runtimePath)
|
||||
return err
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package subcommands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
argparse "v/internal/argparse"
|
||||
stateManager "v/internal/state"
|
||||
)
|
||||
|
||||
func Use(args []string, flags argparse.Flags, currentState stateManager.State) error {
|
||||
version := args[1]
|
||||
if err := validateVersion(version); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
availableVersions := stateManager.GetAvailableVersions()
|
||||
|
||||
found := false
|
||||
for _, v := range availableVersions {
|
||||
if v == version {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return errors.New("Version not installed.")
|
||||
}
|
||||
|
||||
stateManager.WriteState(version)
|
||||
fmt.Printf("Now using Python %s\n", version)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package subcommands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
argparse "v/internal/argparse"
|
||||
stateManager "v/internal/state"
|
||||
)
|
||||
|
||||
func versionStringToStruct(version string) VersionTag {
|
||||
splitVersion := strings.Split(version, ".")
|
||||
|
||||
return VersionTag{Major: splitVersion[0], Minor: splitVersion[1], Patch: splitVersion[2]}
|
||||
}
|
||||
|
||||
func Where(args []string, flags argparse.Flags, currentState stateManager.State) error {
|
||||
version := currentState.GlobalVersion
|
||||
tag := versionStringToStruct(version)
|
||||
withoutPatch := fmt.Sprintf("%s.%s", tag.Major, tag.Minor)
|
||||
fmt.Printf("%s/runtimes/py-%s/bin/python%s\n", stateManager.GetStateDirectory(), currentState.GlobalVersion, withoutPatch)
|
||||
return nil
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package subcommands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
argparse "v/internal/argparse"
|
||||
stateManager "v/internal/state"
|
||||
)
|
||||
|
||||
func Which(args []string, flags argparse.Flags, currentState stateManager.State) error {
|
||||
fmt.Println(currentState.GlobalVersion)
|
||||
return nil
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func StartFmtGroup(label string) func(string) {
|
||||
fmt.Printf("\033[1m%s\033[0m\n", label)
|
||||
|
||||
return func(message string) {
|
||||
fmt.Printf(" %s\n", message)
|
||||
}
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
#!/usr/bin/bash
|
||||
|
||||
go build cmd/v.go
|
||||
go build .
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package state
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
|
@ -1,13 +1,28 @@
|
|||
package subcommands
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func validateVersion(version string) error {
|
||||
func StartFmtGroup(label string) func(string) {
|
||||
fmt.Printf("\033[1m%s\033[0m\n", label)
|
||||
|
||||
return func(message string) {
|
||||
fmt.Printf(" %s\n", message)
|
||||
}
|
||||
}
|
||||
|
||||
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'.")
|
||||
}
|
42
v.go
Normal file
42
v.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
const (
|
||||
Version = "0.0.1"
|
||||
Author = "mcataford <hello@karnov.club>"
|
||||
)
|
||||
|
||||
// Main entrypoint.
|
||||
func main() {
|
||||
args := os.Args[1:]
|
||||
currentState := ReadState()
|
||||
|
||||
cli := CLI{
|
||||
Metadata: map[string]string{
|
||||
"Version": Version,
|
||||
},
|
||||
}
|
||||
|
||||
err := cli.AddCommand(
|
||||
"install", InstallPython, "v install <version>", "Downloads, builds and installs a new version of Python.",
|
||||
).AddCommand(
|
||||
"uninstall", UninstallPython, "v uninstall <version>", "Uninstalls the given Python version.",
|
||||
).AddCommand(
|
||||
"use", Use, "v use <version>", "Selects which Python version to use.",
|
||||
).AddCommand(
|
||||
"ls", ListVersions, "v ls", "Lists the installed Python versions.",
|
||||
).AddCommand(
|
||||
"where", Where, "v where", "Prints the path to the current Python version.",
|
||||
).AddCommand(
|
||||
"which", Which, "v which", "Prints the current Python version.",
|
||||
).AddCommand(
|
||||
"init", Initialize, "v init", "Initializes the v state.",
|
||||
).Run(args, currentState)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue