do

package module
v0.0.0-...-d5834dd Latest Latest
Warning

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

Go to latest
Published: Dec 31, 2025 License: EUPL-1.2 Imports: 19 Imported by: 0

README

do: A simple make alternative for Go projects

do is a zero-dependency utility package that replaces make and shell scripts with pure Go task files.

//go:build ignore

package main

import "codeberg.org/zerodeps/do"

func main() {
	do.GoToTaskDir("..")                      // resolve paths from project root
	do.Run("go", "build", "-o", "bin/app")    // stream output, exit on error
	do.Success("Build complete")
}
go run do/build.go

Why use do?

do provides a simple, batteries-included alternative to make for Go projects:

  • Single import - Most task files need only codeberg.org/zerodeps/do, no juggling stdlib packages for common operations
  • Consistent API - Unified interface for common automation needs instead of learning multiple stdlib packages
  • Zero dependencies - Pure standard library implementation, no go.mod pollution
  • Better ergonimics - Use a real programming language with types, error handling, solid IDE / tooling support and a vast ecosystem instead of shell scripts
  • Minimal setup - Only requires the Go toolchainm, works immediately without installation and configuration of tools

As part of Zero Deps, do has no other dependency than the Go standard library.

Note: This package is for task scripts run via go run only. It will panic if imported into a compiled binary—path resolution relies on source file locations that only exist during go run.

Installation

go get codeberg.org/zerodeps/do

Or vendor the single source file directly.

Quick reference

Command execution
Function Behaviour
Run(cmd, args...) Stream output, exit on error
Exec(cmd, args...) Capture output, exit on error, return SubcommandResult
RunTask(file, args...) Run another task file, stream output, exit on error
ExecTask(file, args...) Run another task file, capture output, exit on error, return SubcommandResult
Filesystem
Path checking
Function Description
Exists(path) Check if path exists, return bool
IsFile(path) Check if path is a regular file, return bool
IsDir(path) Check if path is a directory, return bool
IsSymlink(path) Check if path is a symlink, return bool
IsExecutable(path) Check if file is executable (platform-aware), return bool
Command availability
Function Description
IsCommand(name) Check if command exists in PATH, return bool
Which(name) Find command path in PATH, exit on error, return string
WhichAny(names...) Find first available command, return string (empty if none found)
File and directory operations
Function Description
CreateDir(path) Create directory (with parents), exit on error, return string
Delete(path) Remove file or directory, exit on error, return string
Copy(src, dst) Copy file or directory (preserves symlinks), exit on error, return string
Move(src, dst) Move/rename, exit on error, return string
ReadFile(path) Read file contents, exit on error, return string
WriteFile(path, data) Write file, exit on error, return string
AppendFile(path, data) Append to file, exit on error, return string
Touch(path) Create empty file or update mtime, exit on error, return string
Function Description
CreateSymlink(target, linkPath) Create symbolic link, exit on error, return string
ResolveSymlink(path) Follow symlink to final target, exit on error, return string
Function Description
ListDir(path) List directory contents (non-recursive), exit on error, return []string
Find(root, pattern) Find files/dirs recursively matching pattern, exit on error, return []string
Archive operations
Function Description
Archive(source, target) Create zip archive (preserves symlinks), exit on error, return string
Unarchive(source, destination) Extract zip archive (preserves symlinks), exit on error, return string
HTTP
Function Description
Download(url, dest) Download file, exit on error, return string
Get(url) GET request, exit on error, return body string
Post(url, contentType, body) POST request, exit on error, return body string
Request(method, url, body, headers) Custom request, exit on error, return Response
Output and control flow
Function Description
Log(msg) / Println(msg) Print message with newline, no prefix
Logf(format, ...) / Printf(...) Print formatted message, no automatic newline
Info(msg) / Infof(fmt, ...) Print message
Success(msg) / Successf(...) Print ✓ message
Fail(msg) / Failf(...) Print ✗ message and exit(1)
Assert(cond, msg) / Assertf(...) Exit if condition false
Mute() / Unmute() Suppress or enable output
Must(val, err) Return val or exit on error
Input
Function Description
ReadLine() Read line from stdin, exit on error, return string
Read() Read whitespace-delimited value from stdin, exit on error, return string
Readf(format, pointers...) Read formatted input from stdin, exit on error
Navigation
Function Description
GoToDir(elem...) Change current directory, exit on error
GoToTaskDir(elem...) Change to task file's directory (optionally joined with path elements), exit on error
ResolvePath(path) Resolve path against CWD and return absolute path
TaskDir() Get task file's directory
TaskName() Get task file's name
CurrentDir() Get current working directory
StartingDir() Get original invocation directory
BaseName(path) Get last element of path
Extension(path) Get file extension without dot
Dir(path) Get directory (dir→as-is, file→parent)
FileName(path) Get filename without extension
Setup and utilities
Function Description
Env() Access process environment variables
NewMapEnvManager() Create isolated in-memory environment
NewMapEnvManagerFrom(envMgr) Clone environment from another envManager
NewMapEnvManagerFromMap(map) Create environment from map
NewOutput(out, err) Create custom output with specific writers
NewMutedOutput() Create output that discards all messages
NewInput(in) Create custom input with specific reader
NewEmptyInput() Create input with no data
NewInputFromFile(path) Create input from file contents, exit on error
NewContext() Create new context with Background
NewContextFrom(ctx) Wrap existing context
NewContextWithIO(output, input) Create context with custom I/O
Subcommand(defaultCmd, valid...) Parse subcommand and flags, exit on invalid command
ProvideHelp(msg) Print help message and exit if "help" argument passed
DoAsync(funcs...) Run functions concurrently, return results and errors

All filesystem and command functions have *WithContext variants for timeouts, cancellation, directory override, environment variables, and explicit error handling via if err != nil checks.

Error handling patterns

Plain functions like Run() and Delete() exit on error, ideal for simple scripts. For control flow based on results, use the *WithContext variants which return errors:

// Plain variant: exits on error
do.Run("go", "build", "./...")

// WithContext variant: handle errors explicitly
result, err := do.ExecWithContext(ctx, "go", "build", "./...")

if err != nil {
    do.Info("Build failed, trying fallback...")
    do.Run("go", "build", "-tags", "fallback", "./...")
}

// Must: convert WithContext to plain behaviour
path := do.Must(do.CopyWithContext(ctx, "src", "dst"))

Use Must(val, err) to wrap *WithContext calls when you want exit-on-error behaviour but need context features like timeouts.

Custom output and input

For testing or custom I/O destinations, create Output and Input objects:

// Capture output for testing
var outBuf, errBuf bytes.Buffer
out := do.NewOutput(&outBuf, &errBuf)
out.Info("This goes to outBuf")

// Custom input source
in := do.NewInput(strings.NewReader("test input\n"))
line := in.ReadLine()

// Use with context
ctx := do.NewContextWithIO(out, in)
do.RunWithContext(ctx, "go", "version")
Muted mode

Suppress all output (Success, Info, Log, etc.) while still capturing command results:

// Package-level muted mode
do.Mute()                      // suppress all output
do.Run("go", "build")          // runs muted
do.Unmute()                    // restore output

// Context-based muted mode (isolated)
output := do.NewMutedOutput()
ctx := do.NewContext().WithOutput(output)
result, err := do.ExecWithContext(ctx, "go", "test")  // muted execution
// Parent task output unaffected

// Muted mode propagates to child tasks via DO_MUTED_MODE=1

Muted mode is useful for:

  • Reducing noise in CI/CD pipelines
  • Running tests without cluttering output
  • Wrapper scripts that only want to show their own messages

Core concepts

Task file structure

Place task files in a do/ subfolder. Each file needs //go:build ignore to exclude it from normal builds:

//go:build ignore

package main

import "codeberg.org/zerodeps/do"

func main() {
	do.ProvideHelp(`Build the application

Usage: go run do/build.go [release]`)

	do.GoToTaskDir("..")  // paths now relative to project root
	// ... task logic
}
Path resolution

GoToTaskDir() ensures paths resolve consistently regardless of where go run is invoked from. Call it at the start of every task, optionally with path elements to navigate relative to the task file's directory.

// All equivalent results:
//   go run do/build.go          (from project root)
//   go run ../do/build.go       (from src/)
//   go run /abs/path/do/build.go
do.GoToTaskDir("..")
do.Run("go", "build", "./...")  // always runs from project root
Run vs Exec

Use Run when you want output streamed and automatic exit on failure:

do.Run("go", "test", "./...")  // prints as it runs, exits if tests fail

Use Exec when you need to capture or process output:

result := do.Exec("git", "describe", "--tags")
version := strings.TrimSpace(result.Output)
Command parsing

Parse subcommands and flags with the fluent Subcommand API:

cmd := do.Subcommand("", "build", "test")  // default "", valid: build, test

// Register flags before parsing
verbose := cmd.RegisterBoolFlag("v", false, "Verbose output")
output := cmd.RegisterStringFlag("o", "bin/app", "Output path")

// Parse flags, get remaining positional arguments
remaining := cmd.ParseFlags()

switch cmd.Name {
case "build":
    if *verbose {
        do.Info("Building with verbose output...")
    }
    do.Run("go", "build", "-o", *output, "./...")
case "test":
    do.Run("go", "test", "./...")
default:
    do.Infof("Remaining args: %v", remaining)
}

Available flag types: RegisterBoolFlag, RegisterStringFlag, RegisterIntFlag, RegisterInt64Flag, RegisterUintFlag, RegisterUint64Flag, RegisterFloat64Flag, RegisterDurationFlag.

Environment variables

Access and manipulate environment variables using the Env() interface or create isolated environments for testing:

// Access process environment
env := do.Env()
val, ok := env.GetVar("HOME")
oldVal, _ := env.SetVar("DEBUG", "true")
env.DeleteVar("DEBUG")

// Create isolated in-memory environment for testing
testEnv := do.NewMapEnvManager()
testEnv.SetVar("GOOS", "linux")
testEnv.SetVar("GOARCH", "amd64")

// Clone existing environment
envClone := do.NewMapEnvManagerFrom(do.Env())

// Create from map
customEnv := do.NewMapEnvManagerFromMap(map[string]string{
    "GOOS": "windows",
    "GOARCH": "amd64",
})
Context for advanced control

Use *WithContext variants for timeouts, cancellation, or directory/environment override:

// Timeout
ctx, cancel := do.NewContext().WithTimeout(30 * time.Second)
defer cancel()
do.RunWithContext(ctx, "slow-command")

// Directory override
ctx := do.NewContext().WithDir("/other/project")
do.RunWithContext(ctx, "go", "build")

// Environment variables with isolated environment
env := do.NewMapEnvManager()
env.SetVar("GOOS", "linux")
ctx := do.NewContext().WithEnv(env)
do.RunWithContext(ctx, "go", "build")

// Redirect stdin
ctx := do.NewContext().WithInput(do.NewInput(strings.NewReader("input data")))
do.RunWithContext(ctx, "my-interactive-program")

// Or use the helper for complete I/O setup
output := do.NewOutput(customOut, customErr)
input := do.NewInput(customIn)
ctx := do.NewContextWithIO(output, input)
Concurrent execution

Use DoAsync for parallel operations that return values. Each function returns (any, error):

results, err := do.DoAsync(
    func() (any, error) {
        ctx := do.NewContext().WithDir("./service-a")
        result, err := do.ExecWithContext(ctx, "go", "test", "./...")
        return result, err
    },
    func() (any, error) {
        ctx := do.NewContext().WithDir("./service-b")
        result, err := do.ExecWithContext(ctx, "go", "test", "./...")
        return result, err
    },
)

// Check if any failed
if err != nil {
    // Error message includes function indices:
    // "function 0: test failed\nfunction 1: connection timeout"
    do.Fail("tests failed")
}

// Access individual results by index
if results[0].Error() != nil {
    do.Info("Service A tests failed")
}

// Type-safe value extraction (calls Fail on type mismatch or error)
value := results[0].Value()      // Get raw value (any type)
str := results[0].String()       // Extract as string
num := results[1].Int()          // Extract as int
b := results[2].Bool()           // Extract as bool
f := results[3].Float64()        // Extract as float64

// Available type extractors:
// .String(), .Bool()
// .Int(), .Int8(), .Int16(), .Int32(), .Int64()
// .Uint(), .Uint8(), .Uint16(), .Uint32(), .Uint64()
// .Float32(), .Float64()
// .Byte(), .Rune()

// Check specific error types
var netErr *NetworkError
if errors.As(results[3].Error(), &netErr) {
    // Function 3 had a network error
}

// Works with Must
results := do.Must(do.DoAsync(...))

Example: complete build task

//go:build ignore

package main

import (
	"strings"
	"codeberg.org/zerodeps/do"
)

func main() {
	do.ProvideHelp(`Build the application

Usage: go run do/build.go [command] [flags]

Commands:
  release  Build optimised binary with version info
  clean    Remove build artifacts

Flags:
  -o       Output path (default: bin/app)`)

	do.GoToTaskDir("..")

	cmd := do.Subcommand("", "release", "clean")
	output := cmd.RegisterStringFlag("o", "bin/app", "Output path")
	cmd.ParseFlags()

	switch cmd.Name {
	case "clean":
		do.Delete("bin")
		do.Success("Clean complete")

	case "release":
		do.CreateDir("bin")
		result := do.Exec("git", "describe", "--tags", "--always")
		version := strings.TrimSpace(result.Output)
		do.Run("go", "build", "-ldflags", "-s -w -X main.version="+version, "-o", *output, ".")
		do.Successf("Built %s (%s)", *output, version)

	default:
		do.CreateDir("bin")
		do.Run("go", "build", "-o", *output, ".")
		do.Successf("Built %s", *output)
	}
}

Contributing

See AGENTS.md for design principles, coding conventions, and development workflow.

Documentation

Overview

Package "do" provides helper functions for automating common build, file system, and command-line operations in task scripts.

This package is designed exclusively for task scripts run via 'go run'. It can't work properly when imported in compiled binaries, and therefore panics to avoid unintended behaviour.

Index

Constants

This section is empty.

Variables

View Source
var ErrBrokenSymlink = errors.New("symlink target does not exist")

ErrBrokenSymlink indicates that a symbolic link exists but its target does not. This error is returned by ExistsWithContext when checking a path that is a symlink pointing to a non-existent or inaccessible target.

View Source
var ErrNonZeroExit = errors.New("command exited with non-zero exit code")

ErrNonZeroExit is returned when a command exits with a non-zero exit code. The exit code can be retrieved from the first return value of Exec/ExecWithContext.

Functions

func AppendFile

func AppendFile(path, content string) string

AppendFile appends content to a file, creating it if necessary, exiting on error. Relative paths are resolved from the current working directory. Returns the path of the appended file.

func AppendFileWithContext

func AppendFileWithContext(ctx context.Context, path, content string) (string, error)

AppendFileWithContext appends to a file with context support. The context can override the working directory via context.WithDir().

Returns an error if:

  • The context is cancelled (context.Err())
  • Permission is denied (wrapped os.ErrPermission)
  • The path is a directory
  • Insufficient disk space
  • I/O errors occur

func Archive

func Archive(source, target string) string

Archive creates a zip archive from a source file or directory, exiting on error. Relative paths are resolved from the current working directory. The archive will contain the top-level folder or file from the source path. Symlinks are preserved as symlinks in the archive. Returns the path of the created archive.

func ArchiveWithContext

func ArchiveWithContext(ctx context.Context, source, target string) (string, error)

ArchiveWithContext creates a zip archive with context support for cancellation. The context can override the working directory via context.WithDir(). Cancellation is checked between files during the walk. Symlinks are preserved in the archive by storing the link target as the entry data.

Returns an error if:

  • The context is cancelled (context.Err())
  • The source does not exist (wrapped os.ErrNotExist)
  • Permission is denied for source or target (wrapped os.ErrPermission)
  • Insufficient disk space
  • Cannot create or write to target file
  • I/O errors occur

func Assert

func Assert(condition bool, msg string)

Assert exits with an error message if the condition is false.

func Assertf

func Assertf(condition bool, format string, args ...any)

Assertf exits with a formatted error message if the condition is false.

func BaseName

func BaseName(path string) string

BaseName returns the last element of a path. Trailing slashes are removed before extracting the last element. If the path is empty, BaseName returns ".". If the path consists entirely of slashes, BaseName returns "/".

func Copy

func Copy(src, dst string) string

Copy recursively copies a file, directory, or symlink, exiting on error. Relative paths are resolved from the current working directory. Symlinks are preserved as symlinks (not followed), maintaining the original link target. Returns the destination path.

func CopyWithContext

func CopyWithContext(ctx context.Context, src, dst string) (string, error)

CopyWithContext recursively copies with context support for cancellation. The context can override the working directory via context.WithDir(). For directory copies, cancellation is checked between files.

Returns an error if:

  • The context is cancelled (context.Err())
  • The source does not exist (wrapped os.ErrNotExist)
  • Permission is denied (wrapped os.ErrPermission)
  • Insufficient disk space
  • I/O errors occur

func CreateDir

func CreateDir(path string) string

CreateDir creates a directory at the given path, including any necessary parents, exiting on error. Relative paths are resolved from the current working directory. Returns the path of the created directory.

func CreateDirWithContext

func CreateDirWithContext(ctx context.Context, path string) (string, error)

CreateDirWithContext creates a directory with context support. The context can override the working directory via context.WithDir().

Returns an error if:

  • The context is cancelled (context.Err())
  • Permission is denied (wrapped os.ErrPermission)
  • The path exists as a file
  • Insufficient disk space
func CreateSymlink(target, linkPath string) string

CreateSymlink creates a symbolic link at linkPath pointing to target, exiting on error. Both relative paths are resolved from the current working directory. The target path is stored as-is in the symlink (relative or absolute). The target does not need to exist (dangling symlinks are allowed). Fails if linkPath already exists. Returns the path of the created symlink.

func CreateSymlinkWithContext

func CreateSymlinkWithContext(ctx context.Context, target, linkPath string) (string, error)

CreateSymlinkWithContext creates a symbolic link with context support. The context can override the working directory via context.WithDir().

Returns an error if:

  • The context is cancelled (context.Err())
  • The link path already exists (wrapped os.ErrExist)
  • Permission is denied (wrapped os.ErrPermission)
  • The filesystem does not support symlinks (platform-specific)
  • On Windows: administrator privileges or developer mode may be required

func CurrentDir

func CurrentDir() string

CurrentDir returns the current working directory. Panics if the current directory cannot be determined.

func Delete

func Delete(path string) string

Delete recursively deletes a file or directory, exiting on error. Relative paths are resolved from the current working directory. Returns the path of the deleted item.

func DeleteWithContext

func DeleteWithContext(ctx context.Context, path string) (string, error)

DeleteWithContext deletes a file or directory with context support. The context can override the working directory via context.WithDir().

Returns an error if:

  • The context is cancelled (context.Err())
  • Permission is denied (wrapped os.ErrPermission)

Note: If the path does not exist, DeleteWithContext returns successfully (no-op).

func Dir

func Dir(path string) string

Dir returns the directory path, exiting on error. If path is a directory, returns the path as-is. If path is a file, returns the parent directory. Relative paths are resolved from the current working directory.

func DirWithContext

func DirWithContext(ctx context.Context, path string) (string, error)

DirWithContext returns the directory path with context support. The context can override the working directory via context.WithDir().

Returns an error if:

  • The context is cancelled (context.Err())
  • The path does not exist (wrapped os.ErrNotExist)
  • Permission is denied (wrapped os.ErrPermission)

func DoAsync

func DoAsync(funcs ...func() (any, error)) ([]asyncResult, error)

DoAsync runs multiple functions concurrently and collects all results. Each function returns a value and an error. Results are collected in order, so results[i] corresponds to funcs[i].

Returns:

  • []asyncResult: One result per function, in order. Check .Error() to see if failed.
  • error: Non-nil if any function failed (for Must compatibility). Contains all errors joined.

The error return allows DoAsync to work with Must:

results := Must(DoAsync(...))

Individual results can be accessed by index:

if results[3].Error() != nil {
    // Function 3 failed
}
value := results[5].String()  // Get string from function 5, or Fail

func Download

func Download(url, dest string) string

Download downloads a file from a URL to a destination path, exiting on error. Relative paths are resolved from the current working directory. Returns the destination path.

func DownloadWithContext

func DownloadWithContext(ctx context.Context, url, dest string) (string, error)

DownloadWithContext downloads a file from a URL with context support for cancellation. Relative paths are resolved from the current working directory. Verifies the downloaded size matches Content-Length when available. Returns the destination path.

Returns an error if:

  • The context is cancelled or times out (context.Err())
  • The HTTP request fails (network errors, DNS resolution, invalid URL)
  • The server returns a non-2xx status code
  • Content-Length mismatch detected
  • Permission is denied to write destination file (wrapped os.ErrPermission)
  • Insufficient disk space
  • I/O errors occur

func Env

func Env() envManager

Env provides access to the environment variables for the current process through the default environment manager.

func Exists

func Exists(path string) bool

Exists checks if a file or directory exists at the given path. Relative paths are resolved from the current working directory. Returns false if the path does not exist, if it's a broken symlink, or if it cannot be accessed due to permissions. Note: This differs from os.Stat by detecting broken symlinks explicitly.

func ExistsWithContext

func ExistsWithContext(ctx context.Context, path string) (bool, error)

ExistsWithContext checks if a file or directory exists with context support. The context can override the working directory via context.WithDir(). Returns (true, nil) if the path exists and is accessible. Returns (false, error) if: - The path does not exist (error will be os.ErrNotExist) - The path is a broken symlink (error will wrap ErrBrokenSymlink) - The path cannot be accessed (error will indicate permission or other issue) - The context is cancelled (error will be the context error)

func Extension

func Extension(path string) string

Extension returns the file extension without the leading dot. Returns "" if the path has no extension. Hidden files starting with a dot (like ".bashrc") are treated as having no extension. Examples: "/foo/bar.go" → "go", "/foo/bar" → "", ".bashrc" → ""

func Fail

func Fail(msg string)

Fail prints an error message with an X prefix and exits with code 1.

func Failf

func Failf(format string, args ...any)

Failf prints a formatted error message with an X prefix and exits with code 1.

func FileName

func FileName(path string) string

FileName returns the filename without extension, exiting on error. Returns an error if the path is a directory. Relative paths are resolved from the current working directory. Example: "/foo/bar/fizz.go" → "fizz"

func FileNameWithContext

func FileNameWithContext(ctx context.Context, path string) (string, error)

FileNameWithContext returns the filename without extension with context support. The context can override the working directory via context.WithDir().

Returns an error if:

  • The context is cancelled (context.Err())
  • The path does not exist (wrapped os.ErrNotExist)
  • The path is a directory (not a file)
  • Permission is denied (wrapped os.ErrPermission)

func Find

func Find(root, pattern string) []string

Find finds all files and directories matching a pattern within a root directory, exiting on error. Relative paths are resolved from the current working directory. Recurses into subdirectories. Symlinks are followed during traversal.

func FindWithContext

func FindWithContext(ctx context.Context, root, pattern string) ([]string, error)

FindWithContext finds matching files with context support for cancellation. The context can override the working directory via context.WithDir(). Cancellation is checked between directory entries. Symlinks are followed during traversal.

Returns an error if:

  • The context is cancelled (context.Err())
  • The pattern is invalid (malformed glob pattern)
  • The root directory does not exist (wrapped os.ErrNotExist)
  • Permission is denied for any directory (wrapped os.ErrPermission)
  • A directory cannot be traversed (other walk errors)

func Get

func Get(url string) string

Get performs an HTTP GET request and returns the response body as a string, exiting on error. Returns an error for non-2xx status codes.

func GetWithContext

func GetWithContext(ctx context.Context, url string) (string, error)

GetWithContext performs an HTTP GET request with context support.

Returns an error if:

  • The context is cancelled or times out (context.Err())
  • The HTTP request fails (network errors, DNS resolution, invalid URL)
  • The server returns a non-2xx status code
  • The response body cannot be read

func GoToDir

func GoToDir(elem ...string)

GoToDir changes the current working directory to the path formed by joining the provided path elements. This affects all subsequent operations that use relative paths until another GoToDir call is made.

Note: This function is not safe for concurrent use. For concurrent operations, use *WithContext functions with context.WithDir() instead.

func GoToTaskDir

func GoToTaskDir(elem ...string)

GoToTaskDir changes the current working directory to the task file's directory, optionally joined with additional path elements. Call this at the start of your task file to ensure relative paths resolve from the task file's location rather than where 'go run' was invoked.

Note: This function is not safe for concurrent use. For concurrent operations, use *WithContext functions with context.WithDir() instead.

Example:

func main() {
 do.GoToTaskDir() // go to task file's directory
 do.GoToTaskDir("..") // go to parent of task file's directory
 do.GoToTaskDir("foo/bar") // go to foo/bar relative to task file's directory
}

func Info

func Info(msg string)

Info prints an informational message with an arrow prefix.

func Infof

func Infof(format string, args ...any)

Infof prints a formatted informational message with an arrow prefix.

func IsCommand

func IsCommand(name string) bool

IsCommand checks if a command is available in PATH. Returns false (suppressing any errors) if the command is not found.

func IsCommandWithContext

func IsCommandWithContext(ctx context.Context, name string) (bool, error)

IsCommandWithContext checks if a command is available with context support. Returns (false, nil) if the command is not found (expected negative case). Returns (false, error) if the context is cancelled or PATH lookup fails.

func IsDir

func IsDir(path string) bool

IsDir checks if a path is a directory. If the path is a symlink, it checks the symlink's target. Returns false (suppressing any errors) if the path does not exist or cannot be accessed. Relative paths are resolved from the current working directory.

func IsDirWithContext

func IsDirWithContext(ctx context.Context, path string) (bool, error)

IsDirWithContext checks if a path is a directory with context support. If the path is a symlink, it checks the symlink's target. The context can override the working directory via context.WithDir().

Returns an error if:

  • The context is cancelled (context.Err())
  • The path does not exist (wrapped os.ErrNotExist)
  • Permission is denied (wrapped os.ErrPermission)

func IsExecutable

func IsExecutable(path string) bool

IsExecutable checks if a file can be executed on the current platform. On Unix systems (Linux, macOS, BSD), checks if any execute permission bit is set. On Windows, checks if the file extension matches PATHEXT environment variable (defaults to .COM, .EXE, .BAT, .CMD, .VBS, .JS, .WS, .MSC, .PS1 if not set). Returns false for directories, symlinks, and non-existent files. Relative paths are resolved from the current working directory.

func IsExecutableWithContext

func IsExecutableWithContext(ctx context.Context, path string) (bool, error)

IsExecutableWithContext checks if a file is executable with context support. The context can override the working directory via context.WithDir().

Returns an error if:

  • The context is cancelled (context.Err())
  • The path does not exist (wrapped os.ErrNotExist)
  • Permission is denied (wrapped os.ErrPermission)

func IsFile

func IsFile(path string) bool

IsFile checks if a path is a regular file. If the path is a symlink, it checks the symlink's target. Returns false (suppressing any errors) if the path does not exist or cannot be accessed. Relative paths are resolved from the current working directory.

func IsFileWithContext

func IsFileWithContext(ctx context.Context, path string) (bool, error)

IsFileWithContext checks if a path is a regular file with context support. If the path is a symlink, it checks the symlink's target. The context can override the working directory via context.WithDir().

Returns an error if:

  • The context is cancelled (context.Err())
  • The path does not exist (wrapped os.ErrNotExist)
  • Permission is denied (wrapped os.ErrPermission)
func IsSymlink(path string) bool

IsSymlink checks if a path is a symbolic link. Returns false (suppressing any errors) if the path does not exist or cannot be accessed. Relative paths are resolved from the current working directory.

func IsSymlinkWithContext

func IsSymlinkWithContext(ctx context.Context, path string) (bool, error)

IsSymlinkWithContext checks if a path is a symbolic link with context support. The context can override the working directory via context.WithDir().

Returns an error if:

  • The context is cancelled (context.Err())
  • The path does not exist (wrapped os.ErrNotExist)
  • Permission is denied (wrapped os.ErrPermission)

func ListDir

func ListDir(path string) []string

ListDir returns all entries (files and directories) in a directory, exiting on error. Relative paths are resolved from the current working directory. Unlike Find, it does not recurse into subdirectories.

func ListDirWithContext

func ListDirWithContext(ctx context.Context, path string) ([]string, error)

ListDirWithContext lists directory contents with context support. The context can override the working directory via context.WithDir().

Returns an error if:

  • The context is cancelled (context.Err())
  • The directory does not exist (wrapped os.ErrNotExist)
  • Permission is denied (wrapped os.ErrPermission)
  • The path is not a directory

func Log

func Log(msg string)

Log outputs a message without a prefix, followed by a newline.

func Logf

func Logf(format string, args ...any)

Logf outputs a formatted message without a prefix or trailing newline.

func Move

func Move(src, dst string) string

Move renames (moves) a file or directory, exiting on error. Relative paths are resolved from the current working directory. Returns the destination path.

func MoveWithContext

func MoveWithContext(ctx context.Context, src, dst string) (string, error)

MoveWithContext moves a file or directory with context support. The context can override the working directory via context.WithDir().

Returns an error if:

  • The context is cancelled (context.Err())
  • The source does not exist (wrapped os.ErrNotExist)
  • The destination already exists (wrapped os.ErrExist)
  • Permission is denied (wrapped os.ErrPermission)
  • Source and destination are on different filesystems (requires copy+delete)

func Must

func Must[T any](val T, err error) T

Must extracts the value from a (T, error) return, exiting with Fail if err is non-nil.

func Mute

func Mute()

Mute suppresses all output from package-level functions (Log, Info, Success, etc.). Does not modify environment variables. Child processes inherit muted mode when Run*, Exec*, RunTask, or ExecTask detect the output is muted and automatically set DO_MUTED_MODE=1 for the subprocess.

Call Unmute() to restore normal output.

Note: Mute() is not safe for concurrent use. Only call from the main goroutine of your task script.

func NewContext

func NewContext() taskContext

NewContext creates a new context with context.Background(). The context has output and input set to package-level output and input by default.

func NewContextFrom

func NewContextFrom(ctx context.Context) taskContext

NewContextFrom wraps an existing context.Context into a context. The context has output and input set to package-level output and input by default. Any additional configuration (output, input, dir, env, etc.) should be added via the With* methods after wrapping.

func NewContextWithIO

func NewContextWithIO(output *Output, input *Input) taskContext

NewContextWithIO creates a new context with Output and Input configured. Pass nil for output or input to use package-level defaults. This is a shortcut for:

ctx := NewContext()
if output != nil { ctx = ctx.WithOutput(output) }
if input != nil { ctx = ctx.WithInput(input) }

func NewMapEnvManager

func NewMapEnvManager() envManager

NewMapEnvManager returns an envManager implementation for interacting with environment variables as a sandboxed in-memory map.

func NewMapEnvManagerFrom

func NewMapEnvManagerFrom(em envManager) envManager

NewMapEnvManagerFrom returns an envManager implementation for interacting with environment variables as a sandboxed in-memory map from another envManager instance.

func NewMapEnvManagerFromMap

func NewMapEnvManagerFromMap(data map[string]string) envManager

NewMapEnvManagerFromMap returns an envManager implementation for interacting with environment variables as a sandboxed in-memory map from another map.

func Post

func Post(url, contentType, body string) string

Post performs an HTTP POST request with the given content type and body, exiting on error. Returns the response body as a string, or an error for non-2xx status codes.

func PostWithContext

func PostWithContext(ctx context.Context, url, contentType, body string) (string, error)

PostWithContext performs an HTTP POST request with context support.

Returns an error if:

  • The context is cancelled or times out (context.Err())
  • The HTTP request fails (network errors, DNS resolution, invalid URL)
  • The server returns a non-2xx status code
  • The response body cannot be read

func Printf

func Printf(format string, args ...any)

Printf is an alias for Logf, provided for Go developers familiar with fmt.Printf.

func Println

func Println(msg string)

Println is an alias for Log, provided for Go developers familiar with fmt.Println.

func ProvideHelp

func ProvideHelp(msg string)

ProvideHelp checks if "help" was passed as the first argument. If so, it prints the provided message and exits with status 0. This is typically called at the start of a task file to provide usage info.

func Read

func Read() string

Read reads a single whitespace-delimited value from stdin. Returns the value as a string (reads up to whitespace, newline, or EOF). Exits with code 1 on error (but not on EOF).

func ReadFile

func ReadFile(path string) string

ReadFile reads and returns the contents of a file as a string, exiting on error. Relative paths are resolved from the current working directory.

func ReadFileWithContext

func ReadFileWithContext(ctx context.Context, path string) (string, error)

ReadFileWithContext reads a file with context support. The context can override the working directory via context.WithDir().

Returns an error if:

  • The context is cancelled (context.Err())
  • The file does not exist (wrapped os.ErrNotExist)
  • Permission is denied (wrapped os.ErrPermission)
  • The path is a directory
  • I/O errors occur

func ReadLine

func ReadLine() string

ReadLine reads a line from stdin (up to newline). Exits with code 1 on error.

func Readf

func Readf(format string, a ...any)

Readf reads formatted input from stdin according to a format string. Exits with code 1 on error.

func ResolvePath

func ResolvePath(path string) string

ResolvePath resolves a path against the current working directory and returns the absolute path. It is intended to be used after GoToTask() to resolve paths from a consistent location (e.g. project root). Absolute paths are returned as-is. Exits on error.

func ResolvePathWithContext

func ResolvePathWithContext(ctx context.Context, path string) (string, error)

ResolvePathWithContext resolves a path and returns it as an absolute path. It resolves relative paths against the directory set by context.WithDir(), or the current working directory if no directory is set in the context. Absolute paths are returned as-is.

func ResolveSymlink(path string) string

ResolveSymlink follows symbolic links recursively to find the ultimate target, exiting on error. Relative paths are resolved from the current working directory. Returns the absolute path to the final target. Works on symlinks, regular files, and directories (returns cleaned absolute path for non-symlinks). Fails if the path does not exist or if circular symlinks are detected.

func ResolveSymlinkWithContext

func ResolveSymlinkWithContext(ctx context.Context, path string) (string, error)

ResolveSymlinkWithContext resolves symlinks with context support. The context can override the working directory via context.WithDir().

Returns an error if:

  • The context is cancelled (context.Err())
  • The path does not exist (wrapped os.ErrNotExist)
  • A circular symlink is detected
  • Permission is denied (wrapped os.ErrPermission)
  • Too many levels of symlinks (platform-specific limit, typically 40)

func Run

func Run(command string, args ...string)

Run executes a command with output to stdout/stderr, exiting on error.

func RunTask

func RunTask(name string, args ...string)

RunTask runs another task file by name, exiting on error. The task file is resolved relative to the calling task's directory. Output is streamed to stdout/stderr.

func StartingDir

func StartingDir() string

StartingDir returns the original working directory captured at package initialisation. This value remains constant regardless of any subsequent GoTo calls.

func Subcommand

func Subcommand(defaultCmd string, valid ...string) *subcommand

Subcommand is a convenience wrapper around SubcommandWithContext that exits on error. It creates a new parser for defining flags and a subcommand.

func SubcommandWithContext

func SubcommandWithContext(ctx context.Context, defaultCmd string, valid ...string) (*subcommand, error)

SubcommandWithContext creates a new parser for defining flags and a subcommand, returning an error if the parsed subcommand is invalid.

func Success

func Success(msg string)

Success prints a success message with a checkmark prefix.

func Successf

func Successf(format string, args ...any)

Successf prints a formatted success message with a checkmark prefix.

func TaskDir

func TaskDir() string

TaskDir returns the directory containing the task file. Panics if the task file location cannot be determined. This works reliably regardless of wrapper functions between the caller and main.

func TaskName

func TaskName() string

TaskName returns the file name of the task file, exiting on error. This works reliably regardless of wrapper functions between the caller and main.

func TaskNameWithContext

func TaskNameWithContext(ctx context.Context) (string, error)

TaskNameWithContext returns the file name of the task file. This works reliably regardless of wrapper functions between the caller and main.

func Touch

func Touch(path string) string

Touch creates an empty file or updates its modification time if it exists, exiting on error. Relative paths are resolved from the current working directory. Returns the path of the touched file.

func TouchWithContext

func TouchWithContext(ctx context.Context, path string) (string, error)

TouchWithContext touches a file with context support. The context can override the working directory via context.WithDir().

Returns an error if:

  • The context is cancelled (context.Err())
  • Permission is denied (wrapped os.ErrPermission)
  • The path is a directory
  • Insufficient disk space

func Unarchive

func Unarchive(source, destination string) string

Unarchive extracts a zip archive to a destination folder, exiting on error. Relative paths are resolved from the current working directory. Includes protection against the "zip slip" vulnerability. Symlinks are recreated as symlinks at the destination. Returns the destination path.

func UnarchiveWithContext

func UnarchiveWithContext(ctx context.Context, source, destination string) (string, error)

UnarchiveWithContext extracts a zip archive with context support for cancellation. The context can override the working directory via context.WithDir(). Cancellation is checked between zip entries. Symlinks stored in the archive are recreated as symlinks.

Returns an error if:

  • The context is cancelled (context.Err())
  • The archive file does not exist (wrapped os.ErrNotExist)
  • The archive file is corrupt or invalid
  • Zip slip attack detected (path traversal attempt)
  • Permission is denied (wrapped os.ErrPermission)
  • Insufficient disk space
  • Cannot create destination directories or files

func Unmute

func Unmute()

Unmute restores normal output from package-level functions after Mute() has been called. Does not modify environment variables.

Note: Unmute() is not safe for concurrent use. Only call from the main goroutine of your task script.

func Which

func Which(name string) string

Which finds the full path to a command in PATH, exiting on error. Returns the absolute path to the executable. Fails if the command is not found.

func WhichAny

func WhichAny(names ...string) string

WhichAny returns the path to the first available command from the list. Returns empty string if none are found, allowing graceful fallback handling. Useful for checking alternative tools: WhichAny("python3", "python"). When called with no arguments, returns empty string.

func WhichAnyWithContext

func WhichAnyWithContext(ctx context.Context, names ...string) (string, error)

WhichAnyWithContext finds the first available command with context support. Returns the path to the first found command and nil error. Returns ("", error) if none are found, where error is the last lookup error encountered. Returns ("", error) if the context is cancelled. Checks for cancellation between each command lookup.

func WhichWithContext

func WhichWithContext(ctx context.Context, name string) (string, error)

WhichWithContext finds a command path with context support.

Returns an error if:

  • The context is cancelled (context.Err())
  • The command is not found in PATH (exec.ErrNotFound)

func WriteFile

func WriteFile(path, content string) string

WriteFile writes content to a file, creating it if necessary, exiting on error. Relative paths are resolved from the current working directory. Returns the path of the written file.

func WriteFileWithContext

func WriteFileWithContext(ctx context.Context, path, content string) (string, error)

WriteFileWithContext writes to a file with context support. The context can override the working directory via context.WithDir().

Returns an error if:

  • The context is cancelled (context.Err())
  • Permission is denied (wrapped os.ErrPermission)
  • The path is a directory
  • Insufficient disk space
  • I/O errors occur

Types

type Input

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

Input handles input operations with a configured input source. Create inputs with NewInput(in) or use the package-level functions which delegate to the default input. Note: Input is not safe for concurrent use by multiple goroutines due to the internal scanner state.

func NewEmptyInput

func NewEmptyInput() *Input

NewEmptyInput creates an empty input source. This is a a convenience method equivalent to:

input := NewInput(bytes.NewReader(nil))

func NewInput

func NewInput(in io.Reader) *Input

NewInput creates a new Input with the specified input source. If in is nil, defaults to os.Stdin.

func NewInputFromFile

func NewInputFromFile(path string) (*Input, error)

NewFileInput creates an input source from a file. The file is read fully into a memory buffer.

func (*Input) Read

func (i *Input) Read() string

Read reads a single whitespace-delimited value from input. Returns the value as a string (reads up to whitespace, newline, or EOF). Uses fmt.Fscan internally. Exits with code 1 on error (but not on EOF).

func (*Input) ReadLine

func (i *Input) ReadLine() string

ReadLine reads a full line from input (up to newline). Uses bufio.Scanner internally to read the complete line including spaces. Exits with code 1 on error.

func (*Input) Reader

func (i *Input) Reader() io.Reader

Reader returns the input reader for this Input. Useful for integrating with context.WithInput().

func (*Input) Readf

func (i *Input) Readf(format string, a ...any)

Readf reads formatted input according to a format string. Uses fmt.Fscanf internally. Exits with code 1 on error.

type Output

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

Output handles output operations with immutable output destinations. Create outputs with NewOutput(out, err) or use the package-level functions which delegate to the default output. Output is thread-safe because its fields are immutable after construction.

func NewMutedOutput

func NewMutedOutput() *Output

NewMutedOutput creates a new Output that discards all output. The output is created in muted mode with both writers set to io.Discard. This is a convenience constructor equivalent to:

output := NewOutput(io.Discard, io.Discard)
output.Mute()

func NewOutput

func NewOutput(out, err io.Writer) *Output

NewOutput creates a new Output with the specified output destinations. Both writers must be non-nil.

func (*Output) ErrWriter

func (o *Output) ErrWriter() io.Writer

ErrWriter returns the error writer for this output. Returns io.Discard when in muted mode, otherwise returns the configured writer.

func (*Output) Fail

func (o *Output) Fail(msg string)

Fail prints an error message with an X prefix and exits with code 1.

func (*Output) Failf

func (o *Output) Failf(format string, args ...any)

Failf prints a formatted error message with an X prefix and exits with code 1.

func (*Output) Info

func (o *Output) Info(msg string)

Info prints an informational message with an arrow prefix.

func (*Output) Infof

func (o *Output) Infof(format string, args ...any)

Infof prints a formatted informational message with an arrow prefix.

func (*Output) IsMuted

func (o *Output) IsMuted() bool

IsMuted returns true if this Output instance is in muted mode.

func (*Output) Log

func (o *Output) Log(msg string)

Log outputs a message without a prefix, followed by a newline.

func (*Output) Logf

func (o *Output) Logf(format string, args ...any)

Logf outputs a formatted message without a prefix or trailing newline.

func (*Output) Mute

func (o *Output) Mute()

Mute enables muted mode for this Output instance. When muted, Writer() and ErrWriter() return io.Discard. Does not modify environment variables.

func (*Output) Printf

func (o *Output) Printf(format string, args ...any)

Printf is an alias for Logf.

func (*Output) Println

func (o *Output) Println(msg string)

Println is an alias for Log.

func (*Output) Success

func (o *Output) Success(msg string)

Success prints a success message with a checkmark prefix.

func (*Output) Successf

func (o *Output) Successf(format string, args ...any)

Successf prints a formatted success message with a checkmark prefix.

func (*Output) Unmute

func (o *Output) Unmute()

Unmute disables muted mode for this Output instance. When not muted, Writer() and ErrWriter() return their configured writers. Does not modify environment variables.

func (*Output) Writer

func (o *Output) Writer() io.Writer

Writer returns the output writer for this output. Returns io.Discard when in muted mode, otherwise returns the configured writer.

type Response

type Response struct {
	StatusCode int
	// contains filtered or unexported fields
}

Response holds the result of an HTTP request.

func Request

func Request(method, url string, body string, headers map[string]string) Response

Request performs an HTTP request with custom method, headers, and body, exiting on error. Unlike Get and Post, this function returns the Response for any status code, allowing the caller to handle non-2xx responses.

func RequestWithContext

func RequestWithContext(ctx context.Context, method, url string, body string, headers map[string]string) (Response, error)

RequestWithContext performs an HTTP request with context support for cancellation. Unlike GetWithContext and PostWithContext, this function returns the Response for any status code, allowing the caller to handle non-2xx responses.

Returns an error if:

  • The context is cancelled or times out (context.Err())
  • The HTTP request fails (network errors, DNS resolution, invalid URL)
  • The request cannot be created (malformed URL, invalid method)
  • The response body cannot be read (partial response may be returned)

func (Response) Body

func (r Response) Body() string

Body returns the response body as a string.

func (Response) Bytes

func (r Response) Bytes() []byte

Bytes returns the response body as a byte slice.

type SubcommandResult

type SubcommandResult struct {
	Code      int
	Output    string
	ErrOutput string
}

SubcommandResult holds the output from command execution.

func Exec

func Exec(command string, args ...string) SubcommandResult

Exec executes a command, captures stdout and stderr, and exits on error. Both outputs are trimmed of trailing whitespace.

func ExecTask

func ExecTask(name string, args ...string) SubcommandResult

ExecTask runs another task file by name with the given arguments, exiting on error. The task file is resolved relative to the calling task file's directory. Returns the captured output.

func ExecTaskWithContext

func ExecTaskWithContext(ctx context.Context, name string, args ...string) (SubcommandResult, error)

ExecTaskWithContext runs another task file with context support. The task file is resolved relative to the calling task file's directory. Returns the captured output and any error.

Returns an error if:

  • The context is cancelled (context.Err())
  • The task file path cannot be determined (errCallerUnknown)
  • The task file does not exist or cannot be compiled
  • The command exits with a non-zero exit code (ErrNonZeroExit)

func ExecWithContext

func ExecWithContext(ctx context.Context, command string, args ...string) (SubcommandResult, error)

ExecWithContext executes a command with context support and captures stdout and stderr. Both outputs are trimmed of trailing whitespace.

Returns an error if:

  • The context is cancelled (context.Err())
  • The command is not found in PATH (exec.ErrNotFound)
  • The command cannot be executed (permission or other system errors)
  • The command exits with a non-zero exit code (ErrNonZeroExit)

func RunTaskWithContext

func RunTaskWithContext(ctx context.Context, name string, args ...string) (SubcommandResult, error)

RunTaskWithContext runs another task file with context support. The task file is resolved relative to the calling task's directory. Output is streamed to stdout/stderr. Returns the command result and any error.

Returns an error if:

  • The context is cancelled (context.Err())
  • The task file path cannot be determined (errCallerUnknown)
  • The task file does not exist or cannot be compiled
  • The command exits with a non-zero exit code (ErrNonZeroExit)

func RunWithContext

func RunWithContext(ctx context.Context, command string, args ...string) (SubcommandResult, error)

RunWithContext executes a command with context support, returning an error on failure. Output is streamed to stdout/stderr.

Returns an error if:

  • The context is cancelled (context.Err())
  • The command is not found in PATH (exec.ErrNotFound)
  • The command cannot be executed (permission or other system errors)
  • The command exits with a non-zero exit code (ErrNonZeroExit)

Jump to

Keyboard shortcuts

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