Skip to content

Commit

Permalink
Merge pull request #11 from davidovich/add-execution-functionality
Browse files Browse the repository at this point in the history
Add execution functionality
  • Loading branch information
davidovich authored Mar 10, 2019
2 parents 6be3e1a + 535e9db commit da64f8f
Show file tree
Hide file tree
Showing 15 changed files with 496 additions and 18 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ version: 2
jobs:
build:
docker:
- image: circleci/golang:1.11.5
- image: circleci/golang:1.12
environment:
GO111MODULE=on
steps:
Expand Down
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ not every feature is implemented yet.
Why not use git directly?

While you could use git directly to bring an asset directory with a simple git clone, the result does not have executable properties.
while in summon you leverage go execution to bootstrap in one phase. So your data can do:
In summon you leverage go execution to bootstrap in one phase. So your data can do:

```
go run github.com/davidovich/summon-example-assets/summon --help
Expand Down Expand Up @@ -62,13 +62,12 @@ The `assets/summon.config.yaml` contains a configuration file to customize summo

* aliases
* default output-dir
* gobin flags
* executables


```yaml
version: 1

outputdir: .summoned
# exec section declares invokables with their handle
# a same handle name cannot be in two invokers at the same time
exec:
Expand All @@ -78,11 +77,21 @@ exec:
hello: echo hello
# ^ handle to script (must be unique)

go: # go gettable executables
gobin: github.com/myitcv/gobin
gobin: # go gettable executables
gobin: github.com/myitcv/gobin@v0.0.8
gohack: github.com/rogppepe/gohack

python -c:
hello: print("hello from python!")
```
You can then invoke the executable like so:
```
summon run gohack ...
```

This will install gohack and forward the arguments that you provide.

Build
-----
Expand Down Expand Up @@ -126,7 +135,7 @@ include $(shell summon version.mk)

By default, summon will put summoned scripts at the `.summoned/` directory at root of the current directory.

### Running a go binary (soon)
### Running a go binary

`summon run [executable]` allows to run executables declared in the config file

Expand Down
21 changes: 20 additions & 1 deletion cmd/run.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package cmd

import (
"os"

"github.com/davidovich/summon/pkg/summon"
"github.com/spf13/cobra"
)

type runCmdOpts struct {
driver summon.Interface
ref string
args []string
}

func newRunCmd(driver summon.Interface) *cobra.Command {
Expand All @@ -16,7 +20,19 @@ func newRunCmd(driver summon.Interface) *cobra.Command {
rcmd := &cobra.Command{
Use: "run",
Short: "launch executable from summonables",
FParseErrWhitelist: cobra.FParseErrWhitelist{
UnknownFlags: true,
},
RunE: func(cmd *cobra.Command, args []string) error {
cmd.SilenceUsage = true

runCmd.ref = args[0]
// pass all Args down to the referenced executable
// this is due to a limitation in spf13/cobra which eats
// all unknown args or flags making it hard to wrap other commands
// we are lucky, we know the structure, just pass all args.
// see https://github.com/spf13/pflag/pull/160
runCmd.args = os.Args[3:] // 3 is [summon, run, handle]
return runCmd.run()
},
}
Expand All @@ -25,7 +41,10 @@ func newRunCmd(driver summon.Interface) *cobra.Command {
}

func (r *runCmdOpts) run() error {
r.driver.Configure()
r.driver.Configure(
summon.Ref(r.ref),
summon.Args(r.args...),
)

return r.driver.Run()
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ require (
github.com/gobuffalo/meta v0.0.0-20190207205153-50a99e08b8cf // indirect
github.com/gobuffalo/packr/v2 v2.0.1
github.com/lithammer/dedent v1.1.0
github.com/pkg/errors v0.8.1
github.com/rogpeppe/go-internal v1.2.1 // indirect
github.com/spf13/afero v1.2.1
github.com/spf13/cobra v0.0.3
Expand Down
98 changes: 98 additions & 0 deletions internal/testutil/testutils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
package testutil

import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"

"github.com/davidovich/summon/pkg/command"
"github.com/spf13/afero"
)

Expand All @@ -18,3 +26,93 @@ func ReplaceFs() func() {
SetFs(oldFs)
}
}

//Call is a recording of a fake call
type Call struct {
Args string
Env []string
}

// Calls is the array of calls
type Calls struct {
Calls []Call
}

// FakeExecCommand resturns a fake function which calls into testToCall
// this is used to mock an exec.Cmd
// Adapted from https://npf.io/2015/06/testing-exec-command/
func FakeExecCommand(testToCall string, stdout, stderr io.Writer) func(string, ...string) *command.Cmd {
calls := 0
return func(c string, args ...string) *command.Cmd {
cs := []string{"-test.run=" + testToCall, "--", c}
cs = append(cs, args...)
cmd := &command.Cmd{
Cmd: exec.Command(os.Args[0], cs...),
}
if calls == 0 {
startCall(stdout)
}
cmd.Run = func() error {
if calls > 0 {
willAppendCall(stdout)
}
cmd.Stdout = stdout
cmd.Stderr = stderr
cmd.Env = append(cmd.Env, "GO_WANT_HELPER_PROCESS=1")
err := cmd.Cmd.Run()
calls++
return err
}

return cmd
}
}

func startCall(out io.Writer) {
out.Write([]byte("{\"Calls\":["))
}

func willAppendCall(out io.Writer) {
out.Write([]byte(","))
}

// WriteCall marshals the executable call with env
func WriteCall(c Call, w io.Writer) error {
b, err := json.Marshal(c)
if err != nil {
return err
}
w.Write(b)
return nil
}

// GetCalls ends and returns a call sequence
func GetCalls(out io.Reader) (*Calls, error) {
c := &Calls{}
buf, _ := ioutil.ReadAll(out)
if len(buf) == 0 {
return c, nil
}
buf = append(buf, []byte("]}")...)
err := json.Unmarshal(buf, c)

return c, err
}

// CleanHelperArgs removes the helper process arguments
func CleanHelperArgs(helperArgs []string) []string {
args := os.Args
for len(args) > 0 {
if args[0] == "--" {
args = args[1:]
break
}
args = args[1:]
}
if len(args) == 0 {
fmt.Fprintf(os.Stderr, "No command\n")
os.Exit(2)
}

return args
}
49 changes: 49 additions & 0 deletions internal/testutil/testutils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package testutil

import (
"bytes"
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
)

func TestUnMarshallCall(t *testing.T) {
out := &bytes.Buffer{}

call1 := Call{
Args: "a b c",
Env: []string{"a=b"},
}
call2 := Call{
Args: "a b c",
Env: []string{"a=b"},
}

startCall(out)

WriteCall(call1, out)
willAppendCall(out)
WriteCall(call2, out)

c, err := GetCalls(out)

assert.Nil(t, err)
assert.Equal(t, 2, len(c.Calls))
assert.Contains(t, c.Calls, call1)
assert.Contains(t, c.Calls, call2)
}

func TestMarshallCalls(t *testing.T) {
c := Calls{Calls: []Call{
Call{
Args: "a b c",
Env: []string{"a=b"},
},
}}

b, err := json.Marshal(c)

assert.Nil(t, err)
assert.Equal(t, "{\"Calls\":[{\"Args\":\"a b c\",\"Env\":[\"a=b\"]}]}", string(b))
}
6 changes: 5 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package summon

import (
"os/exec"

"github.com/gobuffalo/packr/v2"

"github.com/davidovich/summon/cmd"
Expand All @@ -11,7 +13,9 @@ func Main(args []string, box *packr.Box) int {
err := cmd.Execute(box)

if err != nil {
return 1
if exitError, ok := err.(*exec.ExitError); ok {
return exitError.ExitCode()
}
}

return 0
Expand Down
22 changes: 22 additions & 0 deletions pkg/command/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package command

import (
"os/exec"
)

// Cmd is an exec.Cmd with a configurable Run function
type Cmd struct {
*exec.Cmd
Run func() error
}

// New creates a Cmd with a real exec.Cmd Run function
func New(c string, args ...string) *Cmd {
cmd := &Cmd{
Cmd: exec.Command(c, args...),
}
cmd.Run = func() error {
return cmd.Cmd.Run()
}
return cmd
}
18 changes: 18 additions & 0 deletions pkg/summon/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,30 @@ type options struct {
filename string
// show tree of files
tree bool
// reference to an exec config entry
ref string

args []string
}

// Option allows specifying configuration settings
// from the user
type Option func(*options)

// Args captures the arguments to be passed to run
func Args(args ...string) Option {
return func(opts *options) {
opts.args = args
}
}

// Ref references an exec config entry
func Ref(ref string) Option {
return func(opts *options) {
opts.ref = ref
}
}

// All specifies to download all config files
func All(all bool) Option {
return func(opts *options) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/summon/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ func TestBoxedConfig(t *testing.T) {

s := New(box)

assert.Equal(t, "overriden_dir", s.opts.destination)
assert.Equal(t, "overridden_dir", s.opts.destination)
}
Loading

0 comments on commit da64f8f

Please sign in to comment.