run

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 13, 2022 License: Apache-2.0 Imports: 12 Imported by: 20

README

🏃‍♂️ run

Go Reference Tests

A new way to execute commands in Go

Example usage

package main

import (
  "bytes"
  "context"
  "fmt"
  "log"
  "os"

  "github.com/sourcegraph/run"
)

func main() {
  ctx := context.Background()

  // Easily stream all output back to standard out
  err := run.Cmd(ctx, "echo", "hello world").Run().Stream(os.Stdout)
  if err != nil {
    log.Fatal(err.Error())
  }

  // Or collect filter and modify standard out, then collect string lines from it
  lines, err := run.Cmd(ctx, "ls").Run().
    Filter(func(s []byte) ([]byte, bool) {
      if !bytes.HasSuffix(s, []byte(".go")) {
        return nil, true
      }
      return bytes.TrimSuffix(s, []byte(".go")), false
    }).
    Lines()
  if err != nil {
    log.Fatal(err.Error())
  }
  for i, l := range lines {
    fmt.Printf("line %d: %q\n", i, l)
  }

  // Errors include standard error by default, so we can just stream stdout.
  err = run.Cmd(ctx, "ls", "foobar").Run().StdOut().Stream(os.Stdout)
  if err != nil {
    println(err.Error()) // exit status 1: ls: foobar: No such file or directory
  }

  // Generate data from a file, replacing tabs with spaces for Markdown purposes
  var exampleData bytes.Buffer
  exampleData.Write([]byte(exampleStart + "\n\n```go\n"))
  if err = run.Cmd(ctx, "cat", "cmd/example/main.go").Run().
    Filter(func(line []byte) ([]byte, bool) {
      return bytes.ReplaceAll(line, []byte("\t"), []byte("  ")), false
    }).
    Stream(&exampleData); err != nil {
    log.Fatal(err)
  }
  exampleData.Write([]byte("```\n\n" + exampleEnd))

  // Render new README file
  var readmeData bytes.Buffer
  if err = run.Cmd(ctx, "cat", "README.md").Run().Stream(&readmeData); err != nil {
    log.Fatal(err)
  }
  replaced := exampleBlockRegexp.ReplaceAll(readmeData.Bytes(), exampleData.Bytes())

  // Pipe data to command
  err = run.Cmd(ctx, "cp /dev/stdin README.md").Input(bytes.NewReader(replaced)).Run().Wait()
  if err != nil {
    log.Fatal(err)
  }
}

Documentation

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ExitCode added in v0.2.0

func ExitCode(err error) int

ExitCode returns the exit code associated with err if there is one, otherwise 1. If err is nil, returns 0.

In practice, this replicates the behaviour observed when running commands in the shell, running a command with an incorrect syntax for example will set $? to 1, which is what is expected in script. Not implementing this creates a confusing case where an error such as not finding the binary would either force the code to account for the absence of exit code, which defeats the purpose of this library which is to provide a convenient replacement for shell scripting.

Example
package main

import (
	"context"
	"fmt"

	"bitbucket.org/creachadair/shell"
	"github.com/sourcegraph/run"
)

func main() {
	ctx := context.Background()

	err := run.Cmd(ctx, "bash -c", shell.Quote("exit 123")).Run().Wait()
	fmt.Println(run.ExitCode(err))

	err = run.Cmd(ctx, "echo 'hello world!'").Run().Wait()
	fmt.Println(run.ExitCode(err))

	err = run.Cmd(ctx, "non-existing-binary").Run().Wait()
	fmt.Println(run.ExitCode(err))
}
Output:

123
0
1

Types

type Command

type Command struct {
	// contains filtered or unexported fields
}

Command builds a command for execution. Functions modify the underlying command.

func Cmd

func Cmd(ctx context.Context, parts ...string) *Command

Cmd joins all the parts and builds a command from it.

func (*Command) Dir

func (c *Command) Dir(dir string) *Command

Dir sets the directory this command should be executed in.

func (*Command) Env

func (c *Command) Env(env map[string]string) *Command

Env adds the given environment variables to the command.

func (*Command) Environ

func (c *Command) Environ(environ []string) *Command

InheritEnv adds the given strings representing the environment (key=value) to the command, for example os.Environ().

func (*Command) Input

func (c *Command) Input(input io.Reader) *Command

Input pipes the given io.Reader to the command. If an input is already set, the given input is appended.

func (*Command) ResetInput added in v0.2.0

func (c *Command) ResetInput() *Command

ResetInput sets the command's input to nil.

func (*Command) Run

func (c *Command) Run() Output

Run starts command execution and returns Output, which defaults to combined output.

type ExitCoder added in v0.2.0

type ExitCoder interface {
	error
	ExitCode() int
}

ExitCoder is an error that also denotes an exit code to exit with. Users of Output can check if an error implements this interface to get the underlying exit code of a command execution.

For convenience, the ExitCode function can be used to get the code.

type LineFilter

type LineFilter func(line []byte) (newLine []byte, skip bool)

LineFilter allows modifications of individual lines from Output.

An explicit "skip" return parameter is required because many bytes library functions return nil to denote empty lines, which should be preserved: https://github.com/golang/go/issues/46415

func JQFilter added in v0.3.0

func JQFilter(query string) (LineFilter, error)

JQFilter creates a LineFilter that executes a JQ query against each line. Errors at runtime get written to output.

Refer to https://github.com/itchyny/gojq for the specifics of supported syntax.

type Output

type Output interface {
	// StdOut configures this Output to only provide StdErr. By default, Output works with
	// combined output.
	StdOut() Output
	// StdErr configures this Output to only provide StdErr. By default, Output works with
	// combined output.
	StdErr() Output
	// Filter adds a filter to this Output. It is only applied at aggregation time using
	// e.g. Stream, Lines, and so on.
	Filter(filter LineFilter) Output

	// Stream writes filtered output from the command to the destination writer until
	// command completion.
	Stream(dst io.Writer) error
	// StreamLines writes filtered output from the command and sends it line by line to the
	// destination callback until command completion.
	StreamLines(dst func(line []byte)) error
	// Lines waits for command completion and aggregates filtered output from the command.
	Lines() ([]string, error)
	// JQ waits for command completion executes a JQ query against the entire output.
	//
	// Refer to https://github.com/itchyny/gojq for the specifics of supported syntax.
	JQ(query string) ([]byte, error)
	// Reader is implemented so that Output can be provided directly to another Command
	// using Input().
	io.Reader

	// Wait waits for command completion and returns.
	Wait() error
}

Output configures output and aggregation from a command.

It is behind an interface to more easily enable mock outputs and build different types of outputs, such as multi-outputs and error-only outputs, without complicating the core commandOutput implementation.

func NewErrorOutput added in v0.4.0

func NewErrorOutput(err error) Output

NewErrorOutput creates an Output that just returns error. Useful for allowing function that help run Commands and want to just return an Output even if errors can happen before command execution.

Directories

Path Synopsis
cmd
example command
pipeexample command
pollexample command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL