Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/
.idea/
76 changes: 76 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
version: "2"
run:
timeout: 5m
linters:
enable:
# region General

# Prevent improper directives in go.mod.
- gomoddirectives
# Prevent improper nolint directives.
- nolintlint

# endregion


# region Code Quality and Comments

# Inspect source code for potential security problems. This check has a fairly high false positive rate,
# comment with // nolint:gosec where not relevant.
- gosec
# Complain about deeply nested if cases.
- nestif
# Prevent naked returns in long functions.
- nakedret
# Make Go code more readable.
- gocritic
# Check if comments end in a period. This helps prevent incomplete comment lines, such as half-written sentences.
- godot
# Complain about comments as these indicate incomplete code.
- godox
# Keep the cyclomatic complexity of functions to a reasonable level.
- gocyclo
# Complain about cognitive complexity of functions.
- gocognit
# Find repeated strings that could be converted into constants.
- goconst
# Complain about unnecessary type conversions.
- unconvert
# Complain about unused parameters. These should be replaced with underscores.
- unparam
# Check for non-ASCII identifiers.
- asciicheck
# Check for HTTP response body being closed. Sometimes, you may need to disable this using // nolint:bodyclose.
- bodyclose
# Check for duplicate code. You may want to disable this with // nolint:dupl if the source code is the same, but
# legitimately exists for different reasons.
- dupl
# Prevent dogsledding (mass-ignoring return values). This typically indicates missing error handling.
- dogsled
# Enforce consistent import aliases across all files.
- importas
# Prevent faulty error checks.
- nilerr
# Prevent direct error checks that won't work with wrapped errors.
- errorlint
# Find slice usage that could potentially be preallocated.
- prealloc
# Check for improper duration handling.
- durationcheck
# Enforce tests being in the _test package.
- testpackage
# Enforce line length limits.
- lll

# endregion
settings:
govet:
enable-all: true
disable:
# We don't care about variable shadowing.
- shadow
- fieldalignment
formatters:
enable:
# Make code properly formatted.
- gofmt
33 changes: 25 additions & 8 deletions exex.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// exex provides a custom Cmd type that wraps exec.Cmd in a way that
// it will always capture standard error stream if execution fails
// with an exec.ExitError.
// Package exex provides a custom Cmd type that wraps exec.Cmd in a
// way that it will always capture standard error stream if execution
// fails with an exec.ExitError.
//
// The standard library exec package contains a very useful API to
// execute commands, however, the exec.Cmd.Run and exec.Cmd.Output
Expand All @@ -26,6 +26,7 @@ import (
"bytes"
"context"
"errors"
"fmt"
"io"
"os/exec"
)
Expand All @@ -45,15 +46,15 @@ type Cmd exec.Cmd
//
// Refer to the exec.Command documentation for additional information.
func Command(name string, args ...string) *Cmd {
return (*Cmd)(exec.Command(name, args...))
return (*Cmd)(exec.Command(name, args...)) //nolint:gosec // Variable args intentional.
}

// CommandContext is like Command but the Cmd is associated with a
// context.
//
// Refer to the exec.Command documentation for additional information.
func CommandContext(ctx context.Context, name string, args ...string) *Cmd {
return (*Cmd)(exec.CommandContext(ctx, name, args...))
return (*Cmd)(exec.CommandContext(ctx, name, args...)) //nolint:gosec // Variable args intentional.
}

// Run starts the command and waits for it to end.
Expand Down Expand Up @@ -132,7 +133,7 @@ func (c *Cmd) StdinPipe() (io.WriteCloser, error) { return (*exec.Cmd)(c).StdinP
// standard output when the command starts.
func (c *Cmd) StdoutPipe() (io.ReadCloser, error) { return (*exec.Cmd)(c).StdoutPipe() }

// String returns a human-readable description of c
// String returns a human-readable description of c.
func (c *Cmd) String() string { return (*exec.Cmd)(c).String() }

// RunCommand wraps an *exec.Cmd into a Cmd and returns the result of
Expand All @@ -152,10 +153,26 @@ func RunContext(ctx context.Context, cmd string, args ...string) error {
return CommandContext(ctx, cmd, args...).Run()
}

// Error is a type alias for exec.Error
// CommandError returns the error with the stderr log appended,
// if the error is a command exit error, and the stderr log exists.
func CommandError(err error, errMsg string) error {
var exErr *ExitError
if err != nil {
if !errors.As(err, &exErr) {
return fmt.Errorf("error converting error to exex.ExitError")
}
if exErr.Stderr == nil {
return fmt.Errorf("%s (%w)", errMsg, err)
}
return fmt.Errorf("%s (%w)\n%s", errMsg, err, exErr.Stderr)
}
return nil
}

// Error is a type alias for exec.Error.
type Error = exec.Error

// ExitError is a type alias for exec.ExitError
// ExitError is a type alias for exec.ExitError.
type ExitError = exec.ExitError

// ErrNotFound is an alias for exec.ErrNotFound, the error resulting
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/inkel/exex

go 1.17
go 1.25