Skip to content

Commit

Permalink
Refactor runner; support Go snippets
Browse files Browse the repository at this point in the history
  • Loading branch information
adambabik committed Jul 20, 2022
1 parent d25d36e commit ceabf69
Show file tree
Hide file tree
Showing 11 changed files with 212 additions and 37 deletions.
9 changes: 9 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Connect to server",
"type": "go",
"request": "attach",
"mode": "remote",
"remotePath": "${workspaceFolder}",
"port": 56379,
"host": "127.0.0.1"
},
{
"name": "Launch Package",
"type": "go",
Expand Down
39 changes: 39 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Examples

## Shell

This is a basic snippet with shell command:

```sh
$ echo "Hello, rdme!"
```

It can contain multiple lines too:

```sh
$ echo "1"
$ echo "2"
$ echo "3"
```

Also, the dollar sign is not needed:

```sh
echo "Hello, rdme! Again!"
```

## Go

It can also execute a snippet of Go code:

```go
package main

import (
"fmt"
)

func main() {
fmt.Println("Hello from Go, rdme!")
}
```
4 changes: 2 additions & 2 deletions internal/cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ func listCmd() *cobra.Command {

for _, snippet := range snippets {
table.AddField(snippet.Name(), nil, nil)
table.AddField(snippet.FirstCmd(), nil, nil)
table.AddField(fmt.Sprintf("%d", len(snippet.Cmds())), nil, nil)
table.AddField(snippet.FirstLine(), nil, nil)
table.AddField(fmt.Sprintf("%d", len(snippet.Lines())), nil, nil)
table.AddField(snippet.Description(), nil, nil)
table.EndRow()
}
Expand Down
54 changes: 31 additions & 23 deletions internal/cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ package cmd

import (
"context"
"io"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"

"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/stateful/rdme/internal/parser"
"github.com/stateful/rdme/internal/runner"
)

func runCmd() *cobra.Command {
Expand Down Expand Up @@ -60,33 +60,41 @@ func runCmd() *cobra.Command {
cancel()
}()

sh, ok := os.LookupEnv("SHELL")
if !ok {
sh = "/bin/sh"
}

stdin := cmd.InOrStdin()
stdout, stderr := cmd.OutOrStdout(), cmd.ErrOrStderr()

for _, cmd := range snippet.Cmds() {
if err := execSingle(ctx, sh, cmd, stdin, stdout, stderr); err != nil {
return err
}
executable, err := newExecutable(cmd, snippet)
if err != nil {
return errors.WithStack(err)
}

return nil
return errors.WithStack(executable.Run(ctx))
},
}

return &cmd
}

func execSingle(ctx context.Context, sh, cmd string, stdin io.Reader, stdout, stderr io.Writer) error {
c := exec.CommandContext(ctx, sh, []string{"-c", cmd}...)
c.Dir = chdir
c.Stderr = stderr
c.Stdout = stdout
c.Stdin = stdin

return errors.Wrapf(c.Run(), "failed to run command %q", cmd)
func newExecutable(cmd *cobra.Command, s parser.Snippet) (runner.Executable, error) {
switch s.Executable() {
case "sh":
return &runner.Shell{
Cmds: s.Lines(),
Base: runner.Base{
Dir: chdir,
Stdin: cmd.InOrStdin(),
Stdout: cmd.OutOrStdout(),
Stderr: cmd.ErrOrStderr(),
},
}, nil
case "go":
return &runner.Go{
Source: s.Content(),
Base: runner.Base{
Dir: chdir,
Stdin: cmd.InOrStdin(),
Stdout: cmd.OutOrStdout(),
Stderr: cmd.ErrOrStderr(),
},
}, nil
default:
return nil, errors.Errorf("unknown executable: %q", s.Executable())
}
}
9 changes: 5 additions & 4 deletions internal/cmd/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import (

func tasksCmd() *cobra.Command {
cmd := cobra.Command{
Use: "tasks",
Short: "Generates task.json for VS Code editor. Caution, this is experimental.",
Args: cobra.ExactArgs(1),
Use: "tasks",
Short: "Generates task.json for VS Code editor. Caution, this is experimental.",
Hidden: true,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// TODO: like can omit because Args should do the validation.
if len(args) != 1 {
Expand All @@ -33,7 +34,7 @@ func tasksCmd() *cobra.Command {

tasksDef, err := tasks.GenerateFromShellCommand(
snippet.Name(),
snippet.FirstCmd(),
snippet.FirstLine(),
&tasks.ShellCommandOpts{
Cwd: chdir,
},
Expand Down
6 changes: 3 additions & 3 deletions internal/parser/parse_snippets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ go run main.go
snippets0 := p0.Snippets()
assert.Len(t, snippets0, 1)
assert.EqualValues(t, []string{"go-run"}, snippets0.Names())
assert.EqualValues(t, []string{"go run main.go"}, snippets0[0].Cmds())
assert.EqualValues(t, "go run main.go", snippets0[0].FirstCmd())
assert.EqualValues(t, []string{"go run main.go"}, snippets0[0].Lines())
assert.EqualValues(t, "go run main.go", snippets0[0].FirstLine())

p1 := New([]byte(`
Snippet without proper annotations:
Expand Down Expand Up @@ -64,5 +64,5 @@ $ curl -X PATCH -H "Content-Type: application/json" localhost:8080/feedback/a02b
assert.Len(t, snippets5, 1)
assert.EqualValues(t, []string{"patch-feedback"}, snippets5.Names())
p5Snippet, _ := snippets5.Lookup("patch-feedback")
assert.Equal(t, []string{`curl -X PATCH -H "Content-Type: application/json" localhost:8080/feedback/a02b6b5f-46c4-40ff-8160-ff7d55b8ca6f/ -d '{"message": "Modified!"}'`}, p5Snippet.Cmds())
assert.Equal(t, []string{`curl -X PATCH -H "Content-Type: application/json" localhost:8080/feedback/a02b6b5f-46c4-40ff-8160-ff7d55b8ca6f/ -d '{"message": "Modified!"}'`}, p5Snippet.Lines())
}
11 changes: 9 additions & 2 deletions internal/parser/parser_snippets.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,19 @@ func (p *Parser) Snippets() (result Snippets) {
}

if s.Name() == "" {
fragments := strings.Split(s.FirstCmd(), " ")
fragments := strings.Split(s.FirstLine(), " ")
executable := fragments[0]

// Usually the command looks like this: EXECUTABLE subcommand arg1, arg2, ...
if len(fragments) > 1 {
executable += "-" + fragments[1]
suffixBuf := bytes.NewBuffer(nil)
for _, r := range strings.ToLower(fragments[1]) {
if r >= '0' && r <= '9' || r >= 'a' && r <= 'z' {
_, _ = suffixBuf.WriteRune(r)
}
}

executable += "-" + suffixBuf.String()
}
nameIndexes[executable]++

Expand Down
17 changes: 14 additions & 3 deletions internal/parser/snippet.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,18 @@ type Snippet struct {
language string
}

func (s Snippet) Cmds() []string {
func (s Snippet) Executable() string {
if s.language != "" {
return s.language
}
return "sh"
}

func (s Snippet) Content() string {
return strings.TrimSpace(s.content)
}

func (s Snippet) Lines() []string {
var cmds []string

firstHasDollar := false
Expand Down Expand Up @@ -41,8 +52,8 @@ func (s Snippet) Cmds() []string {
return cmds
}

func (s Snippet) FirstCmd() string {
cmds := s.Cmds()
func (s Snippet) FirstLine() string {
cmds := s.Lines()
if len(cmds) > 0 {
return cmds[0]
}
Expand Down
17 changes: 17 additions & 0 deletions internal/runner/executable.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package runner

import (
"context"
"io"
)

type Executable interface {
Run(context.Context) error
}

type Base struct {
Dir string
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
}
43 changes: 43 additions & 0 deletions internal/runner/go.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package runner

import (
"context"
"os"
"os/exec"
"path/filepath"

"github.com/pkg/errors"
)

type Go struct {
Base
Source string
}

func (g Go) Run(ctx context.Context) error {
executable, err := exec.LookPath("go")
if err != nil {
return errors.Wrapf(err, "failed to find %q executable", "go")
}

tmpDir, err := os.MkdirTemp("", "rdme-*")
if err != nil {
return errors.Wrapf(err, "failed to create a temp dir")
}
defer os.RemoveAll(tmpDir)

mainFile := filepath.Join(tmpDir, "main.go")

err = os.WriteFile(mainFile, []byte(g.Source), 0o600)
if err != nil {
return errors.Wrapf(err, "failed to write source to file")
}

c := exec.CommandContext(ctx, executable, "run", mainFile)
c.Dir = g.Dir
c.Stderr = g.Stderr
c.Stdout = g.Stdout
c.Stdin = g.Stdin

return errors.Wrapf(c.Run(), "failed to run command %q", "go run main.go")
}
40 changes: 40 additions & 0 deletions internal/runner/shell.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package runner

import (
"context"
"io"
"os"
"os/exec"

"github.com/pkg/errors"
)

type Shell struct {
Base
Cmds []string
}

func (s Shell) Run(ctx context.Context) error {
sh, ok := os.LookupEnv("SHELL")
if !ok {
sh = "/bin/sh"
}

for _, cmd := range s.Cmds {
if err := execSingle(ctx, sh, s.Dir, cmd, s.Stdin, s.Stdout, s.Stderr); err != nil {
return err
}
}

return nil
}

func execSingle(ctx context.Context, sh, dir, cmd string, stdin io.Reader, stdout, stderr io.Writer) error {
c := exec.CommandContext(ctx, sh, []string{"-c", cmd}...)
c.Dir = dir
c.Stderr = stderr
c.Stdout = stdout
c.Stdin = stdin

return errors.Wrapf(c.Run(), "failed to run command %q", cmd)
}

0 comments on commit ceabf69

Please sign in to comment.