Skip to content

Commit

Permalink
Go toolchain "caster"
Browse files Browse the repository at this point in the history
To use the Go toolchain, also known as the `go` command [1], a new
caster [2], introduced in GH-14 [3], has been implemented.
To unify the handling of errors in the cast [5] package, a new
`ErrCast` [4] struct type has also been implemented.

The `Validate` function [6] of the new caster returns an error of type
`*cast.ErrCast` when the `go` binary executable does not exists at the
configured path or when it is also not available in the executable
search paths of the current environment.

[1]: https://golang.org/cmd/go
[2]: https://pkg.go.dev/github.com/svengreb/wand/pkg/cast#Caster
[3]: #14
[4]: https://pkg.go.dev/github.com/svengreb/wand/pkg/cast#ErrCast
[5]: https://pkg.go.dev/github.com/svengreb/wand/pkg/cast
[6]: https://pkg.go.dev/github.com/svengreb/wand/pkg/cast#Validate

GH-20
  • Loading branch information
svengreb committed Nov 21, 2020
1 parent cc9f7c4 commit 882c8fe
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 17 deletions.
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ go 1.15

require (
github.com/Masterminds/semver/v3 v3.1.0
github.com/go-git/go-git/v5 v5.1.0
github.com/go-git/go-git/v5 v5.2.0
github.com/magefile/mage v1.10.0
github.com/stretchr/testify v1.6.1
github.com/svengreb/golib v0.1.0
)
12 changes: 8 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git-fixtures/v4 v4.0.1 h1:q+IFMfLx200Q3scvt2hN79JsEzy4AmBTp/pqnefH+Bc=
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-git/go-git/v5 v5.1.0 h1:HxJn9g/E7eYvKW3Fm7Jt4ee8LXfPOm/H1cdDu8vEssk=
github.com/go-git/go-git/v5 v5.1.0/go.mod h1:ZKfuPUoY1ZqIG4QG9BDBh3G4gLM5zvPuSJAozQrZuyM=
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI=
github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
Expand All @@ -38,6 +38,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g=
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
Expand All @@ -52,6 +54,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/svengreb/golib v0.1.0 h1:SLuz/hkaOzX5GNLyknhF4jKnC709f36zCNaYQJKhxyQ=
github.com/svengreb/golib v0.1.0/go.mod h1:0ROtwFxTxg2kYrY8MexNqnVA5NGUwPhu4P5qWhMMF+U=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
Expand Down
31 changes: 31 additions & 0 deletions internal/support/os/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

// Package os provides utilities for operating system related operations and interactions.
package os

import (
"fmt"
"strings"
)

// EnvMapToSlice transforms a map of environment variable key/value pairs into a slice separated by an equal sign.
func EnvMapToSlice(src map[string]string) []string {
dst := make([]string, len(src))
for k, v := range src {
dst = append(dst, fmt.Sprintf("%s=%s", k, v))
}
return dst
}

// EnvSliceToMap transforms a slice of environment variables separated by an equal sign into a map.
func EnvSliceToMap(src []string, dst map[string]string) {
for _, envVar := range src {
kv := strings.Split(envVar, "=")
if len(kv) == 1 {
dst[kv[0]] = ""
} else {
dst[kv[0]] = kv[1]
}
}
}
1 change: 1 addition & 0 deletions pkg/cast/cast.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

// Package cast provides caster for spell incantations.
package cast

import (
Expand Down
47 changes: 47 additions & 0 deletions pkg/cast/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

package cast

import (
"errors"
"fmt"

wErr "github.com/svengreb/wand/pkg/error"
)

const (
// ErrCasterValidation indicates that a caster validation failed.
ErrCasterValidation = wErr.ErrString("caster validation failed")

// ErrCasterSpellIncantationKindUnsupported indicates that a spell incantation kind is not supported by a caster.
ErrCasterSpellIncantationKindUnsupported = wErr.ErrString("unsupported spell incantation kind")
)

// ErrCast represents a cast error.
type ErrCast struct {
// Err is a wrapped error.
Err error
// Kind is the error kind.
Kind error
}

func (e *ErrCast) Error() string {
msg := "cast error"
if e.Kind != nil {
msg = fmt.Sprintf("%s: %v", msg, e.Kind)
}
if e.Err != nil {
msg = fmt.Sprintf("%s: %v", msg, e.Err)
}

return msg
}

// Is enables usage of errors.Is() to determine the kind of error that occurred.
func (e *ErrCast) Is(err error) bool {
return errors.Is(err, e.Kind)
}

// Unwrap returns the underlying error for usage with errors.Unwrap().
func (e *ErrCast) Unwrap() error { return e.Err }
64 changes: 64 additions & 0 deletions pkg/cast/golang/toolchain/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

package toolchain

import (
"github.com/magefile/mage/mg"
)

const (
// CasterName is the name of the Go toolchain command caster.
CasterName = "golang"

// DefaultEnvVarGO111MODULE is the default environment variable name to toggle the Go 1.11 module mode.
DefaultEnvVarGO111MODULE = "GO111MODULE"

// DefaultEnvVarGOBIN is the default environment variable name for the Go binary executable path.
DefaultEnvVarGOBIN = "GOBIN"

// DefaultEnvVarGOFLAGS is the default environment variable name for Go tool flags.
DefaultEnvVarGOFLAGS = "GOFLAGS"

// DefaultEnvVarGOPATH is the default environment variable name for the Go path.
DefaultEnvVarGOPATH = "GOPATH"
)

// DefaultExec is the default path to the Go executable.
var DefaultExec = mg.GoCmd()

// Options stores Go toolchain command caster options.
type Options struct {
Env map[string]string
Exec string
}

// Option is a Go toolchain command caster option.
type Option func(*Options)

// WithEnv sets the caster environment.
func WithEnv(env map[string]string) Option {
return func(o *Options) {
o.Env = env
}
}

// WithExec sets the path to the Go executable.
func WithExec(nameOrPath string) Option {
return func(o *Options) {
o.Exec = nameOrPath
}
}

// newOptions creates new Go toolchain command caster options.
func newOptions(opts ...Option) *Options {
opt := &Options{
Env: make(map[string]string),
Exec: DefaultExec,
}
for _, o := range opts {
o(opt)
}

return opt
}
89 changes: 89 additions & 0 deletions pkg/cast/golang/toolchain/toolchain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

package toolchain

import (
"fmt"
"os/exec"

"github.com/magefile/mage/sh"
glFS "github.com/svengreb/golib/pkg/io/fs"

"github.com/svengreb/wand/pkg/cast"
"github.com/svengreb/wand/pkg/spell"
)

// Caster is a Go toolchain command caster.
type Caster struct {
opts *Options
}

// GetExec returns the path to the binary executable.
func (c *Caster) GetExec() string {
return c.opts.Exec
}

// Cast casts a spell incantation.
// It returns an error of type *cast.ErrCast when the spell is not a spell.KindBinary and any other error that occurs
// during the command execution.
func (c *Caster) Cast(si spell.Incantation) error {
if si.Kind() != spell.KindBinary {
return &cast.ErrCast{
Err: fmt.Errorf("%q", si.Kind()),
Kind: cast.ErrCasterSpellIncantationKindUnsupported,
}
}

s, ok := si.(spell.Binary)
if !ok {
return &cast.ErrCast{
Err: fmt.Errorf("expected %q but got %q", s.Kind(), si.Kind()),
Kind: cast.ErrCasterSpellIncantationKindUnsupported,
}
}

args := si.Formula()
for k, v := range s.Env() {
c.opts.Env[k] = v
}

return sh.RunWithV(c.opts.Env, c.opts.Exec, args...)
}

// Handles returns the supported spell.Kind.
func (c *Caster) Handles() spell.Kind {
return spell.KindBinary
}

// Validate validates the Go toolchain command caster.
// It returns an error of type *cast.ErrCast when the binary executable does not exists at the configured path and when
// it is also not available in the executable search paths of the current environment.
func (c *Caster) Validate() error {
// Check if the Go executable exists,...
execExits, fsErr := glFS.FileExists(c.opts.Exec)
if fsErr != nil {
return &cast.ErrCast{
Err: fmt.Errorf("caster %q: %w", CasterName, fsErr),
Kind: cast.ErrCasterValidation,
}
}
// ...otherwise try to look up the system-wide executable paths.
if !execExits {
path, pathErr := exec.LookPath(c.opts.Exec)
if pathErr != nil {
return &cast.ErrCast{
Err: fmt.Errorf("caster %q: %q not found or does not exist: %w", CasterName, c.opts.Exec, pathErr),
Kind: cast.ErrCasterValidation,
}
}
c.opts.Exec = path
}

return nil
}

// NewCaster creates a new Go toolchain command caster.
func NewCaster(opts ...Option) *Caster {
return &Caster{opts: newOptions(opts...)}
}
12 changes: 12 additions & 0 deletions pkg/error/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

// Package error provides types and utilities to handle errors.
package error

// ErrString is a string type for implementing constant errors.
type ErrString string

func (e ErrString) Error() string {
return string(e)
}
19 changes: 7 additions & 12 deletions pkg/project/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,24 @@ package project
import (
"errors"
"fmt"

wErr "github.com/svengreb/wand/pkg/error"
)

const (
// ErrDeriveVCSInformation indicates that the derivation of VCS version information failed.
ErrDeriveVCSInformation = ErrString("failed to derive VCS version information")
ErrDeriveVCSInformation = wErr.ErrString("failed to derive VCS version information")

// ErrDetectProjectRootDirPath indicates that the detection of a project root directory path failed.
ErrDetectProjectRootDirPath = ErrString("failed to detect project root directory path")
ErrDetectProjectRootDirPath = wErr.ErrString("failed to detect project root directory path")

// ErrDetermineGoModuleInformation indicates that a determination of Go module information failed.
ErrDetermineGoModuleInformation = ErrString("failed to determine Go module information")
ErrDetermineGoModuleInformation = wErr.ErrString("failed to determine Go module information")

// ErrPathNotRelative indicates that a path is not releative.
ErrPathNotRelative = ErrString("path is not relative")
// ErrPathNotRelative indicates that a path is not relative.
ErrPathNotRelative = wErr.ErrString("path is not relative")
)

// ErrString is a string type for implementing constant errors.
type ErrString string

func (e ErrString) Error() string {
return string(e)
}

// ErrProject represents a project error.
type ErrProject struct {
// Err is a wrapped error.
Expand Down
6 changes: 6 additions & 0 deletions pkg/project/vcs/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

// Package vcs provides packages utilities to interact with version control systems.
// See https://en.wikipedia.org/wiki/Version_control for more details about VCS.
package vcs
2 changes: 2 additions & 0 deletions pkg/project/vcs/git/git.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

// Package git provides VCS utility functions to interact with Git repositories.
// See https://git-scm.com for more details about Git.
package git

import (
Expand Down
1 change: 1 addition & 0 deletions pkg/spell/spell.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

// Package spell provides incantations for different kinds.
package spell

import "github.com/svengreb/wand/pkg/project"
Expand Down

0 comments on commit 882c8fe

Please sign in to comment.