โฉ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โฉ
__________ __ _____ ______ _ ______ __________
/ ____/ __ \/ |/ / |/ / | / | / / __ \/ ____/ __ \
/ / / / / / /|_/ / /|_/ / /| | / |/ / / / / __/ / /_/ /
/ /___/ /_/ / / / / / / / ___ |/ /| / /_/ / /___/ _, _/
\____/\____/_/ /_/_/ /_/_/ |_/_/ |_/_____/_____/_/ |_|
๐๐๐ฅ
โฉ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โฉ
Commander ๐
SOMEBODY STOP ME! ๐ Command execution from hell's kitchen ๐ฅ๐
Commander takes Go's os/exec and transforms it from a fucking disaster ๐ฅ into something that actually works - P-A-R-T-Y! ๐ This shit wraps all the garbage ๐๏ธ that makes you want to violate everything holy and spawn some digital violence ๐ช๐ป. No more hanging processes (they picked the wrong god to pray to! โฐ๏ธ), no more race conditions giving you digital hemorrhoids ๐ฉธ, no more timeout bullshit โฐ๐ฉ that makes you question why you didn't just become a tortured soul in the first place ๐น.
SMOKIN'! ๐ฌ: Stream output like the green-faced monster ๐น๐ you were born to be, terminate processes with malevolent glee ๐๐ฅ, mock everything without losing your goddamn sanity ๐ง ๐ฅ, and handle errors like a true hellspawn console warrior ๐บ๐ป. I am returned from the darkness to tear your shitty command execution apart! โ๏ธ๐ฅ๏ธ๐ฅ
Installation
go get github.com/psyb0t/commander
The Interfaces ๐ ๏ธ (Know Your Weapons ๐ก๏ธโ๏ธ)
Commander Interface ๐๐ญ - The main event (SOMEBODY STOP ME! ๐น)
type Commander interface {
// Fire and forget - P-A-R-T-Y! ๐ฅ๐ Just run the damn thing ๐จ
Run(ctx context.Context, name string, args []string, opts ...Option) error
// Get stdout/stderr separated - it's showtime! ๐ญ๐ฅ
Output(ctx context.Context, name string, args []string, opts ...Option) (stdout []byte, stderr []byte, err error)
// Mix that output together - VIOLATE EVERYTHING HOLY! ๐น๐
CombinedOutput(ctx context.Context, name string, args []string, opts ...Option) (output []byte, err error)
// Start a process you can control - somebody stop me! ๐๐น๐ฅ
Start(ctx context.Context, name string, args []string, opts ...Option) (Process, error)
}
Process Interface ๐ฅ๐น - Hellspawn fuckery from the abyss โฐ๏ธ๐
type Process interface {
Start() error // Spawn the beast - it's PARTY TIME! ๐๐น๐ฅ
Wait() error // Wait for the carnage to finish โฐ๐
StdinPipe() (io.WriteCloser, error) // Feed the machine - VIOLATE EVERYTHING! ๐ฐ๐ช
Stream(stdout, stderr chan<- string) // Stream the chaos live - witness the violence! ๐๐ป๐กโก
Stop(ctx context.Context, timeout time.Duration) error // They picked the wrong god to pray to! โฐ๏ธ๐น๐
Kill(ctx context.Context) error // Somebody stop me from this beautiful murder! ๐ซ๐ฅ๐
}
Options โ๏ธ๐น - Pimp your malevolent machinery ๐ฅ๐
func WithStdin(stdin io.Reader) Option // Feed the beast - it's party time! ๐ฝ๏ธ๐น๐
func WithEnv(env []string) Option // Corrupt the environment - spawn the chaos! ๐๐ป๐ฅ๐บ
func WithDir(dir string) Option // Choose your battlefield - violate everything holy! ๐๐๏ธโฐ๏ธ
func WithTimeout(timeout time.Duration) Option // Set your death timer - they picked the wrong god! โฐ๐๐ฅ
Basic Usage ๐โ๏ธ - The fundamentals of digital violence ๐ฅ๐
Simple Command Execution
package main
import (
"context"
"errors"
"fmt"
"log"
"github.com/psyb0t/commander"
commonerrors "github.com/psyb0t/common-go/errors"
)
func main() {
cmd := commander.New()
ctx := context.Background()
// Just run dat shit and forget about it - wicked!
err := cmd.Run(ctx, "echo", []string{"hello world"})
if err != nil {
log.Fatal("Failed to run command - what a fucking disaster:", err)
}
// Get da output like a civilized person, innit
stdout, stderr, err := cmd.Output(ctx, "ls", []string{"-la", "/tmp"})
if err != nil {
log.Fatal("Command failed - dis is well fucked:", err)
}
fmt.Printf("Files:\n%s\n", stdout)
if len(stderr) > 0 {
fmt.Printf("Errors (oh for fuck's sake):\n%s\n", stderr)
}
// When you don't give a toss about separating streams
output, err := cmd.CombinedOutput(ctx, "git", []string{"status"})
if err != nil {
log.Fatal("Git failed - typical fucking git:", err)
}
fmt.Printf("Git says (probably some bullshit):\n%s\n", output)
}
Advanced Shit - Real-time Streaming, bruv
Want to see what's happening while it's happening? Here's how you stream dat shit live - it's well good:
func streamingExample() {
cmd := commander.New()
ctx := context.Background()
// Start a long-running process
proc, err := cmd.Start(ctx, "ping", []string{"-c", "10", "google.com"})
if err != nil {
log.Fatal("Failed to start ping:", err)
}
// Create channels for live streaming
stdout := make(chan string, 100) // Buffer it so we don't block
stderr := make(chan string, 100)
// Start streaming (this is non-blocking)
proc.Stream(stdout, stderr)
// Read the streams as they come in
go func() {
for line := range stdout {
fmt.Printf("[PING] %s\n", line)
}
}()
go func() {
for line := range stderr {
fmt.Printf("[ERROR] %s\n", line)
}
}()
// Wait for the process to finish
err = proc.Wait()
if err != nil {
fmt.Printf("Ping finished with error: %v\n", err)
} else {
fmt.Println("Ping completed successfully!")
}
}
Process Control - Be the Boss
Graceful Termination
func gracefulTermination() {
cmd := commander.New()
ctx := context.Background()
// Start something that runs forever
proc, err := cmd.Start(ctx, "tail", []string{"-f", "/var/log/syslog"})
if err != nil {
log.Fatal("Failed to start tail:", err)
}
// Let it run for a bit
time.Sleep(2 * time.Second)
// Now shut it down gracefully (SIGTERM first, SIGKILL after 5 seconds if needed)
fmt.Println("Shutting down gracefully...")
err = proc.Stop(ctx, 5*time.Second)
if err == nil {
fmt.Println("Process stopped cleanly")
} else if errors.Is(err, commonerrors.ErrTerminated) {
fmt.Println("Process terminated gracefully (SIGTERM)")
} else if errors.Is(err, commonerrors.ErrKilled) {
fmt.Println("Process had to be killed (SIGKILL)")
} else {
fmt.Printf("Stop failed: %v\n", err)
}
}
func justKillIt() {
cmd := commander.New()
ctx := context.Background()
proc, _ := cmd.Start(ctx, "sleep", []string{"1000"})
// Like a glove! No mercy, just beautiful violence
err := proc.Kill(ctx)
if errors.Is(err, commonerrors.ErrKilled) {
fmt.Println("Process killed with SIGKILL - somebody stop me!")
} else if err != nil {
fmt.Printf("Kill failed: %v\n", err)
}
}
Timeout Handling - Patience is for suckers
Context Timeout
func contextTimeout() {
cmd := commander.New()
// This will timeout after 2 seconds
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
err := cmd.Run(ctx, "sleep", []string{"10"})
if errors.Is(err, commonerrors.ErrTimeout) {
fmt.Println("Bingo! Command timed out like a champ!")
}
}
Option Timeout
func optionTimeout() {
cmd := commander.New()
ctx := context.Background()
// Same shit, different toilet - LIKE A GLOVE!
err := cmd.Run(ctx, "sleep", []string{"10"},
commander.WithTimeout(2*time.Second))
if errors.Is(err, commonerrors.ErrTimeout) {
fmt.Println("Timeout option worked! Do NOT go in there!")
}
}
func stdinExample() {
cmd := commander.New()
ctx := context.Background()
// Feed some data to wc to count lines
input := strings.NewReader("line 1\nline 2\nline 3\nline 4\n")
stdout, _, err := cmd.Output(ctx, "wc", []string{"-l"},
commander.WithStdin(input))
if err != nil {
log.Fatal("wc failed:", err)
}
fmt.Printf("Line count: %s", stdout) // Should print "4" - Smokin'!
}
Environment Variables
func environmentExample() {
cmd := commander.New()
ctx := context.Background()
// Set custom environment
stdout, _, err := cmd.Output(ctx, "sh", []string{"-c", "echo $CUSTOM_VAR $ANOTHER_VAR"},
commander.WithEnv([]string{
"CUSTOM_VAR=hello",
"ANOTHER_VAR=world",
}))
if err != nil {
log.Fatal("Shell command failed:", err)
}
fmt.Printf("Environment output: %s", stdout) // Should print "hello world" - Alllllrighty then!
}
Working Directory
func workingDirectoryExample() {
cmd := commander.New()
ctx := context.Background()
// Run pwd in /tmp
stdout, _, err := cmd.Output(ctx, "pwd", nil,
commander.WithDir("/tmp"))
if err != nil {
log.Fatal("pwd failed:", err)
}
fmt.Printf("Current directory: %s", stdout) // Should print "/tmp"
}
All Options Combined
func kitchenSinkExample() {
cmd := commander.New()
ctx := context.Background()
input := strings.NewReader("some input data")
stdout, stderr, err := cmd.Output(ctx, "cat", nil,
commander.WithStdin(input),
commander.WithDir("/tmp"),
commander.WithEnv([]string{"LANG=en_US.UTF-8"}),
commander.WithTimeout(10*time.Second))
if err != nil {
log.Fatal("Kitchen sink failed:", err)
}
fmt.Printf("Output: %s\n", stdout)
if len(stderr) > 0 {
fmt.Printf("Errors: %s\n", stderr)
}
}
Advanced Streaming - Multiple Listeners
You can have multiple channels listening to the same process output:
func multipleListeners() {
cmd := commander.New()
ctx := context.Background()
proc, err := cmd.Start(ctx, "ping", []string{"-c", "5", "google.com"})
if err != nil {
log.Fatal("Failed to start ping:", err)
}
// Create multiple listeners
logger := make(chan string, 100)
display := make(chan string, 100)
storage := make(chan string, 100)
// All three will get the same data
proc.Stream(logger, nil)
proc.Stream(display, nil)
proc.Stream(storage, nil)
// Handle each stream differently
go func() {
for line := range logger {
log.Printf("[LOG] %s", line)
}
}()
go func() {
for line := range display {
fmt.Printf("[DISPLAY] %s\n", line)
}
}()
var stored []string
go func() {
for line := range storage {
stored = append(stored, line)
}
fmt.Printf("Stored %d lines total\n", len(stored))
}()
err = proc.Wait()
if err != nil {
fmt.Printf("Process failed: %v\n", err)
}
}
Concurrent Execution - Go Wild
Run multiple commands at the same time like a fucking machine:
func concurrentExecution() {
cmd := commander.New()
ctx := context.Background()
// Commands to run concurrently
commands := []struct {
name string
args []string
}{
{"echo", []string{"first"}},
{"echo", []string{"second"}},
{"echo", []string{"third"}},
{"sleep", []string{"1"}},
{"date", nil},
}
var wg sync.WaitGroup
results := make(chan string, len(commands))
// Launch all commands concurrently
for _, cmdInfo := range commands {
wg.Add(1)
go func(name string, args []string) {
defer wg.Done()
stdout, _, err := cmd.Output(ctx, name, args)
if err != nil {
results <- fmt.Sprintf("ERROR: %s %v failed: %v", name, args, err)
return
}
results <- fmt.Sprintf("SUCCESS: %s %v -> %s", name, args, strings.TrimSpace(string(stdout)))
}(cmdInfo.name, cmdInfo.args)
}
// Wait for all to complete
wg.Wait()
close(results)
// Show results
fmt.Println("Concurrent execution results:")
for result := range results {
fmt.Printf(" %s\n", result)
}
}
Error Handling - Know What Went Wrong
The package gives you specific error types so you know exactly what happened:
func errorHandling() {
cmd := commander.New()
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
proc, err := cmd.Start(ctx, "sleep", []string{"10"})
if err != nil {
log.Fatal("Failed to start process:", err)
}
err = proc.Wait()
if err == nil {
fmt.Println("โ
Process completed successfully")
} else if errors.Is(err, commonerrors.ErrTimeout) {
fmt.Println("โ Process timed out")
} else if errors.Is(err, commonerrors.ErrTerminated) {
fmt.Println("โ ๏ธ Process was terminated (SIGTERM)")
} else if errors.Is(err, commonerrors.ErrKilled) {
fmt.Println("๐ Process was killed (SIGKILL)")
} else {
fmt.Printf("๐ฅ Process failed: %v\n", err)
}
}
Testing - Mock That Shit
The package comes with a comprehensive mocking system so you can test without actually running commands:
Basic Mocking
func TestMyFunction(t *testing.T) {
mock := commander.NewMock()
defer func() {
if err := mock.VerifyExpectations(); err != nil {
t.Error("Mock expectations failed:", err)
}
}()
// Set up expectations
mock.Expect("git", "status").ReturnOutput([]byte("On branch main\nnothing to commit, working tree clean"))
mock.Expect("git", "push").ReturnError(errors.New("push failed"))
// Use the mock in your code (it implements Commander interface)
err := myDeployFunction(mock)
// Your function should handle the push failure gracefully
assert.Error(t, err)
}
Advanced Argument Matching
func TestWithMatchers(t *testing.T) {
mock := commander.NewMock()
defer mock.VerifyExpectations()
// Exact matching (default)
mock.Expect("echo", "hello").ReturnOutput([]byte("hello"))
// Regex matching
mock.ExpectWithMatchers("grep",
commander.Regex("^error.*"), // First arg must match regex
commander.Exact("logfile.txt")) // Second arg must be exact
// Wildcard matching
mock.ExpectWithMatchers("find", commander.Any(), commander.Any())
// Mixed matching
mock.ExpectWithMatchers("rsync",
commander.Exact("-av"),
commander.Regex(`.*\.tar\.gz$`),
commander.Any())
// Test your code here...
}
Process Mocking
func TestProcessMocking(t *testing.T) {
mock := commander.NewMock()
// Mock a streaming process
mock.Expect("tail", "-f", "/var/log/messages").
ReturnOutput([]byte("log line 1\nlog line 2\nlog line 3"))
proc, err := mock.Start(context.Background(), "tail", []string{"-f", "/var/log/messages"})
require.NoError(t, err)
// Test streaming
stdout := make(chan string, 10)
proc.Stream(stdout, nil)
var lines []string
for line := range stdout {
lines = append(lines, line)
}
expected := []string{"log line 1", "log line 2", "log line 3"}
assert.Equal(t, expected, lines)
require.NoError(t, mock.VerifyExpectations())
}
Mock Utilities
func TestMockUtilities(t *testing.T) {
mock := commander.NewMock()
// Set up multiple expectations
mock.Expect("first").ReturnOutput([]byte("1"))
mock.Expect("second").ReturnOutput([]byte("2"))
mock.Expect("third").ReturnError(errors.New("failed"))
// Execute them
mock.Output(context.Background(), "first", nil)
mock.Output(context.Background(), "second", nil)
mock.Output(context.Background(), "third", nil)
// Check call order
order := mock.CallOrder()
expected := []string{"first ", "second ", "third "}
assert.Equal(t, expected, order)
// Reset if needed
mock.Reset() // Clears all expectations and history
require.NoError(t, mock.VerifyExpectations())
}
Thread Safety
Everything is thread-safe. You can:
- Use the same Commander instance from multiple goroutines
- Run multiple commands concurrently
- Stream from multiple processes simultaneously
- Use mocks in parallel tests
func TestConcurrentMocking(t *testing.T) {
mock := commander.NewMock()
// Set up expectations for concurrent calls
for i := 0; i < 10; i++ {
mock.Expect("echo", string(rune('a'+i))).
ReturnOutput([]byte(string(rune('A'+i))))
}
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
stdout, _, err := mock.Output(context.Background(),
"echo", []string{string(rune('a'+index))})
require.NoError(t, err)
assert.Equal(t, string(rune('A'+index)), string(stdout))
}(i)
}
wg.Wait()
require.NoError(t, mock.VerifyExpectations())
}
Memory Management
- Channels are automatically closed when processes end
- Context cancellation is properly handled
- No memory leaks from goroutines or file descriptors
- Process cleanup uses
sync.Once for safety
Error Types Reference
Here's all the shit that can go wrong and how to handle it:
// Package-specific errors
var (
ErrUnexpectedCommand = errors.New("unexpected command")
ErrExpectedCommandNotCalled = errors.New("expected command not called")
ErrProcessStartFailed = errors.New("process start failed")
ErrProcessWaitFailed = errors.New("process wait failed")
ErrPipeCreationFailed = errors.New("pipe creation failed")
ErrCommandFailed = errors.New("command failed")
)
// Common errors (from github.com/psyb0t/common-go/errors)
commonerrors.ErrTimeout // Command timed out
commonerrors.ErrTerminated // Process terminated by SIGTERM
commonerrors.ErrKilled // Process killed by SIGKILL
Real-world Examples
Deploy Script
func deployApp(cmd commander.Commander) error {
ctx := context.Background()
fmt.Println("๐๏ธ Building application...")
err := cmd.Run(ctx, "go", []string{"build", "-o", "app", "./cmd/server"})
if err != nil {
return fmt.Errorf("build failed: %w", err)
}
fmt.Println("๐งช Running tests...")
err = cmd.Run(ctx, "go", []string{"test", "./..."},
commander.WithTimeout(5*time.Minute))
if err != nil {
return fmt.Errorf("tests failed: %w", err)
}
fmt.Println("๐ฆ Creating Docker image...")
err = cmd.Run(ctx, "docker", []string{"build", "-t", "myapp:latest", "."})
if err != nil {
return fmt.Errorf("docker build failed: %w", err)
}
fmt.Println("๐ Pushing to registry...")
err = cmd.Run(ctx, "docker", []string{"push", "myapp:latest"},
commander.WithTimeout(10*time.Minute))
if err != nil {
return fmt.Errorf("docker push failed: %w", err)
}
fmt.Println("โ
Deploy completed successfully!")
return nil
}
Log Monitor
func monitorLogs(cmd commander.Commander) error {
ctx := context.Background()
proc, err := cmd.Start(ctx, "tail", []string{"-f", "/var/log/app.log"})
if err != nil {
return fmt.Errorf("failed to start log monitoring: %w", err)
}
stdout := make(chan string, 100)
proc.Stream(stdout, nil)
// Monitor for specific patterns
errorPattern := regexp.MustCompile(`(?i)error|exception|panic`)
warningPattern := regexp.MustCompile(`(?i)warning|warn`)
go func() {
for line := range stdout {
switch {
case errorPattern.MatchString(line):
log.Printf("๐จ ERROR: %s", line)
// Maybe send alert, page someone, etc.
case warningPattern.MatchString(line):
log.Printf("โ ๏ธ WARNING: %s", line)
default:
log.Printf("โน๏ธ INFO: %s", line)
}
}
}()
// Stop monitoring after 1 hour
time.Sleep(1 * time.Hour)
return proc.Stop(ctx, 5*time.Second)
}
System Health Check
func healthCheck(cmd commander.Commander) (map[string]bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
checks := map[string][]string{
"disk_space": {"df", "-h", "/"},
"memory": {"free", "-m"},
"load_average": {"uptime"},
"docker": {"docker", "ps"},
"nginx": {"systemctl", "is-active", "nginx"},
"database": {"pg_isready"},
}
results := make(map[string]bool)
var wg sync.WaitGroup
for name, cmdArgs := range checks {
wg.Add(1)
go func(checkName string, args []string) {
defer wg.Done()
err := cmd.Run(ctx, args[0], args[1:])
results[checkName] = (err == nil)
if err != nil {
log.Printf("โ Health check '%s' failed: %v", checkName, err)
} else {
log.Printf("โ
Health check '%s' passed", checkName)
}
}(name, cmdArgs)
}
wg.Wait()
return results, nil
}
Dependencies
github.com/sirupsen/logrus - For debug logging
github.com/psyb0t/ctxerrors - Error wrapping with context
github.com/psyb0t/common-go - Common error types
- Standard library:
context, os/exec, sync, syscall, etc.
Why Use This?
Before (stdlib os/exec)
// Painful, error-prone, lots of boilerplate
cmd := exec.CommandContext(ctx, "some-command", "arg1", "arg2")
stdout, err := cmd.StdoutPipe()
if err != nil {
// handle error
}
stderr, err := cmd.StderrPipe()
if err != nil {
// handle error
}
err = cmd.Start()
if err != nil {
// handle error
}
// Now you need to read from pipes in goroutines...
// And handle timeouts manually...
// And figure out why your process is hanging...
// And write your own mocks...
// ๐คฎ
After (Commander)
// Clean, simple, powerful
cmd := commander.New()
stdout, stderr, err := cmd.Output(ctx, "some-command", []string{"arg1", "arg2"})
if err != nil {
// handle error (with proper context!)
}
// Done. That's it. ๐
License
MIT - Use it, abuse it, whatever. Just don't blame anyone if your servers catch fire. ๐ฅ
Contributing
Found a bug? Want a feature? Open an issue or send a PR. Contributing is welcome.
Commander: SOMEBODY STOP ME from this beautiful command-line carnage! P-A-R-T-Y time for your digital violence! ๐ฅ๐น๐โฐ๏ธ ๐