shx

package
v0.1.73 Latest Latest
Warning

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

Go to latest
Published: Apr 24, 2024 License: Apache-2.0 Imports: 10 Imported by: 1

Documentation

Overview

Package shx provides shell-like operations for Go.

A Job represents one or more operations. A single-operation Job may represent running a command (Exec, System), or reading or writing a file (ReadFile, WriteFile), or a user defined operation (Func). A multiple-operation Job runs several single operation jobs in a sequence (Script) or pipeline (Pipe). Taken together, these primitive types allow the composition of more and more complex operations.

Users control how a Job runs using State. State controls the Job input and output, as well as its working directory and environment.

Users may produce a human-readable representation of a complex Job in a shell-like syntax using the Description. Because some operations have no shell equivalent, the result is only representative.

Examples are provided for all primitive Job types: Exec, System, Func, Pipe, Script. Additional convenience Jobs make creating more complex operations a little easier. Advanced users may create their own Job types for more flexibility.

Example
sc := Script(
	// Set environment in Script State.
	SetEnv("KEY", "ORIGINAL"),
	Script(
		// Overwrite environment in sub-Script.
		SetEnv("KEY", "SUBSCRIPT"),
		Exec("env"),
	),
	// Original Script State environment was not modified by sub-Script.
	Exec("env"),
	// Overwrite environment using command output.
	SetEnvFromJob("KEY", System("basename $( pwd )")),
	Exec("env"),
)
s := New()
s.Env = nil // Clear state environment for example.
err := sc.Run(context.Background(), s)
if err != nil {
	panic(err)
}
Output:

KEY=SUBSCRIPT
KEY=ORIGINAL
KEY=shx

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrScriptError = errors.New("script execution error")

ErrScriptError is a base Script error.

Functions

func NewReaderContext

func NewReaderContext(ctx context.Context, r io.Reader) io.Reader

NewReaderContext creates a context-aware io.Reader. This is helpful for creating custom Jobs that are context-aware when reading from otherwise unbounded IO operations, e.g. io.Copy().

Types

type Description

type Description struct {
	// Depth is used to control line prefix indentation in complex Jobs.
	Depth int
	// contains filtered or unexported fields
}

Description is used to produce a representation of a Job. Custom Job types should use the Description interface to represent their behavior in a helpful, human-readable form. After collecting a description, serialize using the String() method.

func (*Description) Append

func (d *Description) Append(cmd string)

Append adds a new command to the output buffer. Typically, the command is formatted as a new line at the end of the current buffer. If StartSequence was called before calling Append, then the command is formatted as a continuation of the current line.

func (*Description) StartSequence

func (d *Description) StartSequence(start, sep string) (endlist func(end string))

StartSequence begins formatting a multi-part expression on a single line, such as a list, pipeline, or similar expression. StartSequence begins with "start" and subsequent calls to Append add commands to the end of the current line, separating sequential commands with "sep". StartSequence returns a function that ends the line and restores the default behavior of Append.

func (*Description) String

func (d *Description) String() string

String serializes a description produced by running Job.Describe(). Calling String resets the Description buffer.

type ExecJob

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

ExecJob implements the Job interface for basic process execution.

func Exec

func Exec(cmd string, args ...string) *ExecJob

Exec creates a Job to execute the given command with the given arguments.

func System

func System(cmd string) *ExecJob

System is an Exec job that interprets the given command using "/bin/sh".

Example
sys := System("echo a b")
s := &State{
	Stdout: os.Stdout,
}
err := sys.Run(context.Background(), s)
if err != nil {
	panic(err)
}
Output:

a b

func (*ExecJob) Describe

func (f *ExecJob) Describe(d *Description)

Describe generates a description for this command.

func (*ExecJob) Run

func (f *ExecJob) Run(ctx context.Context, s *State) error

Run executes the command.

Example
ex := Exec("echo", "a", "b")
s := &State{
	Stdout: os.Stdout,
}
err := ex.Run(context.Background(), s)
if err != nil {
	panic(err)
}
Output:

a b

type FuncJob

type FuncJob struct {
	Job  func(ctx context.Context, s *State) error
	Desc func(d *Description)
}

FuncJob is a generic Job type that allows creating new operations without creating a totally new type. When created directly, both Job and Desc fields must be defined.

func Func

func Func(name string, job func(ctx context.Context, s *State) error) *FuncJob

Func creates a new FuncJob that runs the given function. Job functions should honor the context to support cancelation. The given name is used to describe this function.

func (*FuncJob) Describe

func (f *FuncJob) Describe(d *Description)

Describe generates a description for this custom function.

func (*FuncJob) Run

func (f *FuncJob) Run(ctx context.Context, s *State) error

Run executes the job function.

Example
f := Func("example", func(ctx context.Context, s *State) error {
	b, err := ioutil.ReadAll(s.Stdin)
	if err != nil {
		return err
	}
	_, err = s.Stdout.Write([]byte(base64.URLEncoding.EncodeToString(b)))
	return err
})
s := &State{
	Stdin:  bytes.NewBuffer([]byte(`{"key":"value"}\n`)),
	Stdout: os.Stdout,
}
err := f.Run(context.Background(), s)
if err != nil {
	panic(err)
}
Output:

eyJrZXkiOiJ2YWx1ZSJ9XG4=

type Job

type Job interface {
	// Describe produces a readable representation of the Job operation. After
	// calling Describe, use Description.String() to report the result.
	Describe(d *Description)

	// Run executes the Job using the given State. A Job should terminate when
	// the given context is cancelled.
	Run(ctx context.Context, s *State) error
}

Job is the interface for an operation. A Job controls how an operation is run and represented.

func Chdir

func Chdir(dir string) Job

Chdir creates Job that changes the State Dir to the given directory at runtime. This does not alter the process working directory. Chdir is helpful in Script() Jobs.

func IfFileMissing

func IfFileMissing(file string, job Job) Job

IfFileMissing creates a Job that runs the given job if the named file does not exist.

func IfVarEmpty

func IfVarEmpty(key string, job Job) Job

IfVarEmpty creates a Job that runs the given job if the named variable is empty.

func Println

func Println(message string) Job

Println writes the given message to the State Stdout and expands variable references from the running State environment. Println supports the same variable syntax as os.Expand, e.g. $NAME or ${NAME}.

func Read

func Read(r io.Reader) Job

Read creates a Job that reads from the given reader and writes it to Job's stdout. Read creates a context-aware reader from the given io.Reader.

func ReadFile

func ReadFile(path string) Job

ReadFile creates a Job that reads from the named file and writes it to the Job's stdout.

func SetEnv

func SetEnv(name string, value string) Job

SetEnv creates a Job to assign the given name=value in the running State Env. SetEnv is helpful in Script() Jobs.

func SetEnvFromJob

func SetEnvFromJob(name string, job Job) Job

SetEnvFromJob creates a new Job that sets the given name in Env to the result written to stdout by running the given Job. Errors from the given Job are returned.

func Write

func Write(w io.Writer) Job

Write creates a Job that reads from the Job input and writes to the given writer. Write creates a context-aware reader from the Job input.

func WriteFile

func WriteFile(path string, perm os.FileMode) Job

WriteFile creates a Job that reads from the Job input and writes to the named file. The output path is created if it does not exist and is truncated if it does.

type PipeJob

type PipeJob struct {
	Jobs []Job
}

PipeJob implements the Job interface for running multiple Jobs in a pipeline.

func Pipe

func Pipe(t ...Job) *PipeJob

Pipe creates a Job that executes the given Jobs as a "shell pipeline", passing the output of the first to the input of the next, and so on. If any Job returns an error, the first error is returned.

func (*PipeJob) Describe

func (c *PipeJob) Describe(d *Description)

Describe generates a description for all jobs in the pipeline.

Example
p := Pipe(
	Exec("ls"),
	Exec("tail", "-1"),
	Exec("wc", "-l"),
)
d := &Description{}
p.Describe(d)
fmt.Println(d.String())
Output:

1: ls | tail -1 | wc -l

func (*PipeJob) Run

func (c *PipeJob) Run(ctx context.Context, z *State) error

Run executes every Job in the pipeline. The stdout from the first command is passed to the stdin to the next command. The stderr for all commands is inherited from the given State. If any Job returns an error, the first error is returned for the entire PipeJob.

Example
p := Pipe(
	Exec("ls"),
	Exec("tail", "-1"),
	Exec("wc", "-l"),
)
s := New()
err := p.Run(context.Background(), s)
if err != nil {
	panic(err)
}
Output:

1

type ScriptJob

type ScriptJob struct {
	Jobs []Job
}

ScriptJob implements the Job interface for running an ordered sequence of Jobs.

func Script

func Script(t ...Job) *ScriptJob

Script creates a Job that executes the given Job parameters in sequence. If any Job returns an error, execution stops.

func (*ScriptJob) Describe

func (c *ScriptJob) Describe(d *Description)

Describe generates a description for all jobs in the script.

Example
sc := Script(
	SetEnv("FOO", "BAR"),
	Exec("env"),
)
d := &Description{}
sc.Describe(d)
fmt.Println("\n" + d.String())
Output:

 1: (
 2:   export FOO="BAR"
 3:   env
 4: )

func (*ScriptJob) Run

func (c *ScriptJob) Run(ctx context.Context, s *State) error

Run sequentially executes every Job in the script. Any Job error stops execution and generates an error describing the command that failed.

Example
sc := Script(
	SetEnv("FOO", "BAR"),
	Exec("env"),
)
s := &State{
	Stdout: os.Stdout,
}
err := sc.Run(context.Background(), s)
if err != nil {
	panic(err)
}
Output:

FOO=BAR

type State

type State struct {
	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer
	Dir    string
	Env    []string
}

State is a Job configuration. Callers provide the first initial State, and as a Job executes it creates new State instances derived from the original, e.g. for Pipes and subcommands.

func New

func New() *State

New creates a State instance based on the current process state, using os.Stdin, os.Stdout, and os.Stderr, as well as the current working directory and environment.

func (*State) GetEnv

func (s *State) GetEnv(name string) string

GetEnv reads the named variable from the State environment. If name is not found, an empty value is returned. An undefined variable and a variable set to the empty value are indistinguishable.

func (*State) Path

func (s *State) Path(path ...string) string

Path produces a path relative to the State's current directory. If arguments represent an absolute path, then that is used. If multiple arguments are provided, they're joined using filepath.Join.

func (*State) SetDir

func (s *State) SetDir(dir string) string

SetDir assigns the Dir value and returns the previous value.

func (*State) SetEnv

func (s *State) SetEnv(name, value string)

SetEnv assigns the named variable to the given value in the State environment. If the named variable is already defined it is overwritten.

Jump to

Keyboard shortcuts

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