diff options
| author | Claude <noreply@anthropic.com> | 2026-08-05 23:14:29 -0500 |
|---|---|---|
| committer | Claude <noreply@anthropic.com> | 2026-08-05 23:14:29 -0500 |
| commit | 9f7c9f2f31acd7bc544dd988af1c8c2fe63ec504 (patch) | |
| tree | 6d7ab2a965c3e138a56c625197658ef2b324b045 /internal/sh | |
Initial commit of skiff scaffolding
Diffstat (limited to 'internal/sh')
| -rw-r--r-- | internal/sh/sh.go | 65 |
1 files changed, 65 insertions, 0 deletions
diff --git a/internal/sh/sh.go b/internal/sh/sh.go new file mode 100644 index 0000000..b5f6f49 --- /dev/null +++ b/internal/sh/sh.go @@ -0,0 +1,65 @@ +// Package sh runs external commands with output streamed live, since a +// post-receive hook's stdout/stderr goes straight back to the pushing client. +package sh + +import ( + "context" + "fmt" + "io" + "os" + "os/exec" +) + +func Run(ctx context.Context, out io.Writer, dir string, name string, args ...string) error { + cmd := exec.CommandContext(ctx, name, args...) + cmd.Dir = dir + cmd.Stdout = out + cmd.Stderr = out + if err := cmd.Run(); err != nil { + return fmt.Errorf("%s %v: %w", name, args, err) + } + return nil +} + +// Output runs a command and returns its trimmed stdout, for callers that need +// a value back (e.g. `zfs list -H -o name`) rather than a stream. +func Output(ctx context.Context, dir string, name string, args ...string) (string, error) { + cmd := exec.CommandContext(ctx, name, args...) + cmd.Dir = dir + b, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("%s %v: %w", name, args, err) + } + return string(b), nil +} + +// CombinedOutput runs a command and returns its combined stdout+stderr, +// even on failure - for callers that need to inspect the error text itself +// (e.g. distinguishing bmake's "no such target" from a real failure) +// rather than just pass/fail. +func CombinedOutput(ctx context.Context, dir string, name string, args ...string) (string, error) { + cmd := exec.CommandContext(ctx, name, args...) + cmd.Dir = dir + b, err := cmd.CombinedOutput() + return string(b), err +} + +// RunWithStdinFile runs a command with stdinPath fed to its stdin, e.g. +// piping a migration file into `sqlite3 db`. +func RunWithStdinFile(ctx context.Context, out io.Writer, dir, stdinPath, name string, args ...string) error { + f, err := os.Open(stdinPath) + if err != nil { + return fmt.Errorf("open %s: %w", stdinPath, err) + } + defer f.Close() + + cmd := exec.CommandContext(ctx, name, args...) + cmd.Dir = dir + cmd.Stdin = f + cmd.Stdout = out + cmd.Stderr = out + if err := cmd.Run(); err != nil { + return fmt.Errorf("%s %v < %s: %w", name, args, stdinPath, err) + } + return nil +} |
