refactor: extract core logic to a plain Go package

This commit is contained in:
2022-02-26 18:38:37 +00:00
parent b67da4accb
commit 5fcb2b52ab
21 changed files with 1126 additions and 763 deletions

39
commands/commands.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
}
}