mirror of
https://github.com/jimeh/evm.git
synced 2026-02-19 07:26:40 +00:00
refactor: extract core logic to a plain Go package
This commit is contained in:
39
commands/commands.go
Normal file
39
commands/commands.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package commands
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
type runEFunc func(cmd *cobra.Command, _ []string) error
|
||||
|
||||
type validArgsFunc func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string,
|
||||
) ([]string, cobra.ShellCompDirective)
|
||||
|
||||
func noValidArgs(
|
||||
_ *cobra.Command,
|
||||
_ []string,
|
||||
_ string,
|
||||
) ([]string, cobra.ShellCompDirective) {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
func flagString(cmd *cobra.Command, name string) string {
|
||||
var r string
|
||||
|
||||
if f := cmd.Flag(name); f != nil {
|
||||
r = f.Value.String()
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func stringsContains(haystack []string, needle string) bool {
|
||||
for _, s := range haystack {
|
||||
if s == needle {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
28
commands/config.go
Normal file
28
commands/config.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/jimeh/evm/manager"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewConfig(mgr *manager.Manager) (*cobra.Command, error) {
|
||||
cmd := &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Show evm environment/setup details",
|
||||
Aliases: []string{"env", "info"},
|
||||
ValidArgs: []string{},
|
||||
RunE: configRunE(mgr),
|
||||
}
|
||||
|
||||
cmd.Flags().StringP("format", "f", "", "output format (yaml or json)")
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func configRunE(mgr *manager.Manager) runEFunc {
|
||||
return func(cmd *cobra.Command, _ []string) error {
|
||||
format := flagString(cmd, "format")
|
||||
|
||||
return render(cmd.OutOrStdout(), format, mgr.Config)
|
||||
}
|
||||
}
|
||||
48
commands/evm.go
Normal file
48
commands/evm.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/jimeh/evm/manager"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewEvm(mgr *manager.Manager) (*cobra.Command, error) {
|
||||
cmd := &cobra.Command{
|
||||
Use: "evm",
|
||||
Short: "A simple and opinionated Emacs Version Manager and build tool",
|
||||
}
|
||||
|
||||
configCmd, err := NewConfig(mgr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
listCmd, err := NewList(mgr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
useCmd, err := NewUse(mgr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rehashCmd, err := NewRehash(mgr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
execCmd, err := NewExec(mgr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd.AddCommand(
|
||||
configCmd,
|
||||
listCmd,
|
||||
useCmd,
|
||||
rehashCmd,
|
||||
execCmd,
|
||||
)
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
131
commands/exec.go
Normal file
131
commands/exec.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"html/template"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/jimeh/evm/manager"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewExec(mgr *manager.Manager) (*cobra.Command, error) {
|
||||
cmd := &cobra.Command{
|
||||
Use: "exec <binary> [args]...",
|
||||
Short: "Execute named binary from current " +
|
||||
"Emacs version",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
SilenceUsage: true,
|
||||
DisableFlagParsing: true,
|
||||
DisableFlagsInUseLine: true,
|
||||
Hidden: true,
|
||||
ValidArgsFunction: execValidArgs(mgr),
|
||||
RunE: execRunE(mgr),
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func execRunE(mgr *manager.Manager) runEFunc {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
program := args[0]
|
||||
args = args[1:]
|
||||
|
||||
err := mgr.Exec(ctx, program, args)
|
||||
if err != nil {
|
||||
if errors.Is(err, manager.ErrBinNotFound) {
|
||||
var versions []*manager.Version
|
||||
versions, err = mgr.FindBin(ctx, program)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return newExecOtherVersionsError(&execOtherVersionsData{
|
||||
Name: program,
|
||||
Current: mgr.CurrentVersion(),
|
||||
AvailableIn: versions,
|
||||
})
|
||||
}
|
||||
|
||||
if errors.Is(err, manager.ErrNoCurrentVersion) {
|
||||
return newExecNoCurrentVersionError()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func execValidArgs(mgr *manager.Manager) validArgsFunc {
|
||||
return func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string,
|
||||
) ([]string, cobra.ShellCompDirective) {
|
||||
var r []string
|
||||
|
||||
if len(args) > 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
version, err := mgr.Get(cmd.Context(), mgr.CurrentVersion())
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
for _, bin := range version.Binaries {
|
||||
base := filepath.Base(bin)
|
||||
if toComplete == "" || strings.HasPrefix(base, toComplete) {
|
||||
r = append(r, base)
|
||||
}
|
||||
}
|
||||
|
||||
return r, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
func newExecNoCurrentVersionError() error {
|
||||
return errors.New(`No current Emacs version is set.
|
||||
|
||||
List all installed versions with: evm list
|
||||
Change version with: evm use <version>`,
|
||||
)
|
||||
}
|
||||
|
||||
type execOtherVersionsData struct {
|
||||
Name string
|
||||
Current string
|
||||
AvailableIn []*manager.Version
|
||||
}
|
||||
|
||||
func newExecOtherVersionsError(data *execOtherVersionsData) error {
|
||||
var buf bytes.Buffer
|
||||
err := execOtherVersionsTemplate.Execute(&buf, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return errors.New(buf.String())
|
||||
}
|
||||
|
||||
var execOtherVersionsTemplate = template.Must(template.New("other").Parse(
|
||||
`{{ if gt (len .AvailableIn) 0 -}}
|
||||
Executable "{{.Name}}" is not available in the current Emacs version ({{.Current}}).
|
||||
|
||||
"{{.Name}}" is available in the following Emacs versions:
|
||||
{{- range .AvailableIn }}
|
||||
- {{ .Version }}
|
||||
{{- end -}}
|
||||
{{ else -}}
|
||||
Executable "{{.Name}}" is not available in any installed Emacs version.
|
||||
{{- end }}
|
||||
|
||||
Change version with: evm use <version>
|
||||
List all installed versions with: evm list`,
|
||||
))
|
||||
72
commands/list.go
Normal file
72
commands/list.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/jimeh/evm/manager"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewList(mgr *manager.Manager) (*cobra.Command, error) {
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all Emacs versions found in " +
|
||||
"`$EVM_ROOT/versions/*'",
|
||||
Aliases: []string{"ls", "versions"},
|
||||
Args: cobra.ExactArgs(0),
|
||||
ValidArgsFunction: noValidArgs,
|
||||
RunE: listRunE(mgr),
|
||||
}
|
||||
|
||||
cmd.Flags().StringP("format", "f", "", "output format (yaml or json)")
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func listRunE(mgr *manager.Manager) runEFunc {
|
||||
return func(cmd *cobra.Command, _ []string) error {
|
||||
format := flagString(cmd, "format")
|
||||
|
||||
versions, err := mgr.List(cmd.Context())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
output := &listOutput{
|
||||
Current: mgr.CurrentVersion(),
|
||||
SetBy: mgr.CurrentSetBy(),
|
||||
Versions: versions,
|
||||
}
|
||||
|
||||
return render(cmd.OutOrStdout(), format, output)
|
||||
}
|
||||
}
|
||||
|
||||
type listOutput struct {
|
||||
Current string `yaml:"current" json:"current"`
|
||||
SetBy string `yaml:"current_set_by,omitempty" json:"current_set_by,omitempty"`
|
||||
Versions []*manager.Version `yaml:"versions" json:"versions"`
|
||||
}
|
||||
|
||||
func (lr *listOutput) WriteTo(w io.Writer) (int64, error) {
|
||||
var b []byte
|
||||
|
||||
for _, ver := range lr.Versions {
|
||||
if lr.Current == ver.Version {
|
||||
b = append(b, []byte("* ")...)
|
||||
} else {
|
||||
b = append(b, []byte(" ")...)
|
||||
}
|
||||
|
||||
b = append(b, []byte(ver.Version)...)
|
||||
if lr.Current == ver.Version && lr.SetBy != "" {
|
||||
b = append(b, []byte(" (set by "+lr.SetBy+")")...)
|
||||
}
|
||||
|
||||
b = append(b, byte('\n'))
|
||||
}
|
||||
|
||||
n, err := w.Write(b)
|
||||
|
||||
return int64(n), err
|
||||
}
|
||||
58
commands/rehash.go
Normal file
58
commands/rehash.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jimeh/evm/manager"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewRehash(mgr *manager.Manager) (*cobra.Command, error) {
|
||||
cmd := &cobra.Command{
|
||||
Use: "rehash [<version>...]",
|
||||
Short: "Update shims for all or specific versions",
|
||||
Aliases: []string{"reshim"},
|
||||
ValidArgsFunction: rehashValidArgs(mgr),
|
||||
RunE: rehashRunE(mgr),
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func rehashRunE(mgr *manager.Manager) runEFunc {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
if len(args) > 0 {
|
||||
return mgr.RehashVersions(ctx, args)
|
||||
}
|
||||
|
||||
return mgr.RehashAll(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func rehashValidArgs(mgr *manager.Manager) validArgsFunc {
|
||||
return func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string,
|
||||
) ([]string, cobra.ShellCompDirective) {
|
||||
versions, err := mgr.List(cmd.Context())
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
var r []string
|
||||
for _, ver := range versions {
|
||||
if stringsContains(args, ver.Version) {
|
||||
continue
|
||||
}
|
||||
|
||||
if toComplete == "" || strings.HasPrefix(ver.Version, toComplete) {
|
||||
r = append(r, ver.Version)
|
||||
}
|
||||
}
|
||||
|
||||
return r, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
52
commands/render.go
Normal file
52
commands/render.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func render(w io.Writer, format string, v interface{}) error {
|
||||
if format == "yaml" || format == "yml" {
|
||||
return renderYAML(w, v)
|
||||
}
|
||||
|
||||
if format == "json" {
|
||||
return renderJSON(w, v)
|
||||
}
|
||||
|
||||
if wt, ok := v.(io.WriterTo); ok {
|
||||
_, err := wt.WriteTo(w)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
if tm, ok := v.(encoding.TextMarshaler); ok {
|
||||
b, err := tm.MarshalText()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write(b)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return renderYAML(w, v)
|
||||
}
|
||||
|
||||
func renderYAML(w io.Writer, v interface{}) error {
|
||||
enc := yaml.NewEncoder(w)
|
||||
enc.SetIndent(2)
|
||||
|
||||
return enc.Encode(v)
|
||||
}
|
||||
|
||||
func renderJSON(w io.Writer, v interface{}) error {
|
||||
enc := json.NewEncoder(w)
|
||||
enc.SetIndent("", " ")
|
||||
|
||||
return enc.Encode(v)
|
||||
}
|
||||
53
commands/use.go
Normal file
53
commands/use.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jimeh/evm/manager"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func NewUse(mgr *manager.Manager) (*cobra.Command, error) {
|
||||
cmd := &cobra.Command{
|
||||
Use: "use <version>",
|
||||
Short: "Switch to a specific version",
|
||||
Aliases: []string{"activate", "switch"},
|
||||
Args: cobra.ExactArgs(1),
|
||||
ValidArgsFunction: useValidArgs(mgr),
|
||||
RunE: useRunE(mgr),
|
||||
}
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func useRunE(mgr *manager.Manager) runEFunc {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
return mgr.Use(cmd.Context(), args[0])
|
||||
}
|
||||
}
|
||||
|
||||
func useValidArgs(mgr *manager.Manager) validArgsFunc {
|
||||
return func(
|
||||
cmd *cobra.Command,
|
||||
args []string,
|
||||
toComplete string,
|
||||
) ([]string, cobra.ShellCompDirective) {
|
||||
if len(args) > 0 {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
versions, err := mgr.List(cmd.Context())
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
var r []string
|
||||
for _, ver := range versions {
|
||||
if toComplete == "" || strings.HasPrefix(ver.Version, toComplete) {
|
||||
r = append(r, ver.Version)
|
||||
}
|
||||
}
|
||||
|
||||
return r, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user