mrun

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Jan 19, 2025 License: MIT Imports: 23 Imported by: 0

README

mrun

Go Reference

Embedded TUI multi-command runner based on bubbletea.

Demo

Sometimes you want to run multiple commands in parallel and let the user monitor their progress in real time. tmux is an obvious option but it's highly unusual to spawn tmux session/windows as a third party program, and you can't control spawned commands as effectively when they're children of tmux. mrun lets you integrate a TUI grid right inside your program, neatly solving the problem.

See demo.go for the code used to generate the demo image.

Features

Supports:

  • Things you expect from a TUI built in the 2020s: Unicode support (CJK-aware), color.
  • Commands are run in ptys, don't need to mess with flags to reenable interactive features.
  • Each command has a scroll buffer.
  • Carriage returns are handled gracefully, so commands with basic progress bars work as expected.
  • Mouse support: click to focus, mouse wheel to scroll.
  • Terminal resizing is handled gracefully.

Does not support:

  • Input: commands can't receive user input.
  • Terminal emulation: if your command itself is a TUI program using escape sequences to write to specific coordinates on screen, then it won't work correctly and will likely mess up everything. The output buffer implementation of mrun is basic, far from a full blown terminal emulator like tmux. Try to use the batch mode of your program if it has one (e.g. by piping its output to cat).
  • Integration into larger bubbletea applications: currently not exposed.

Documentation

See https://pkg.go.dev/github.com/zmwangx/mrun.

Documentation

Overview

Example
package main

import (
	"log"
	"os/exec"

	"github.com/zmwangx/mrun"
)

func main() {
	commands, ok, err := mrun.Run(
		[]*mrun.Command{
			mrun.NewCommand(
				exec.Command("tail", "-f", "/var/log/service1.log"),
				mrun.WithLabel("service1.log"),
			),
			mrun.NewCommand(
				exec.Command("tail", "-f", "/var/log/service2.log"),
				mrun.WithLabel("service2.log"),
			),
			mrun.NewCommandWithShell(
				"tail -f /var/log/service3.log | grep --line-buffered ERROR",
				mrun.WithLabel("service3.log"),
			),
		},
		mrun.WithColumns(2),
		mrun.WithCommandLines(),
		// mrun.WithAutoQuit(),
		// mrun.WithFinalView(),
	)
	if err != nil {
		log.Fatal(err)
	}
	if !ok {
		log.Print("some commands failed")
	}
	for _, cmd := range commands {
		err = cmd.Err()
		if err != nil {
			log.Printf("%s: %s", cmd.CommandLine(), err)
		}
	}
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Command

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

func NewCommand

func NewCommand(cmd *exec.Cmd, opts ...CommandOption) *Command

NewCommand creates a new command to be run in its own pane. cmd can have its own Env and Dir. One can also use WithEnv and WithDir to set them for convenience.

To add a label to the bottom of the pane, use WithLabel.

To set a custom command line (see WithCommandLines), use WithCommandLine. Useful for hiding unnecessary details like a shell invocation.

See also NewCommandWithShell.

func NewCommandWithShell

func NewCommandWithShell(cmdline string, opts ...CommandOption) *Command

NewCommandWithShell takes a command line to be run with sh. It is a shorthand for

NewCommand(exec.Command("sh", "-c", cmdline), append([]CommandOption{WithCommandLine(cmdline)}, opts...)...)

func Run

func Run(commands []*Command, opts ...RunOption) (c []*Command, allSuccessful bool, err error)

Run runs the given commands simultaneously in a TUI grid.

Return values are:

  • Slice of commands, now with checkable Err() and ProcessState().
  • allSuccessful, only true if all commands ran to completion and exited with 0 (if the user prematurely quit, this will be false even if the terminated commands responded with exit status 0 on SIGINT/SIGTERM). If it's false, use Err() or ProcessState() on each command to determine which ones failed and why.
  • err is for error from the mrun runner itself, not including errors from commands.

Various options can be used to customize the experience:

  • WithColumns sets the number of columns in the grid, default to 1.
  • WithCommandLines turns on printing the command line before command output in each pane.
  • WithAutoQuit turns on auto quitting after all commands are done without user interaction.
  • WithFinalView leaves a final, non-interactive view of the grid on screen after quitting.

func (Command) CommandLine

func (c Command) CommandLine() string

CommandLine returns the set or generated command line of the command.

func (Command) Err

func (c Command) Err() error

Err returns the error from running the command (including non-zero exit).

func (Command) ProcessState

func (c Command) ProcessState() *os.ProcessState

ProcessState returns the exit state of the command, if the command was successfully started and waited for.

type CommandOption

type CommandOption func(*Command)

func WithCommandLine

func WithCommandLine(cmdline string) CommandOption

WithCommandLine sets the command line to be shown at the bottom of the pane when WithCommandLines is used. By default it is auto-generated from the *exec.Cmd.

func WithDir

func WithDir(dir string) CommandOption

WithDir sets the working directory of the command.

It's a convenience function equivalent to manually setting the Dir field of cmd before calling NewCommand:

cmd.Dir = dir

func WithEnv

func WithEnv(env []string) CommandOption

WithEnv sets the environment of the command.

It's a convenience function equivalent to manually setting the Env field of cmd before calling NewCommand:

cmd.Env = append(os.Environ(), env...)

Note: you need to manually set Env on the exec.Cmd if you don't want to inherit the current process's environment.

func WithLabel

func WithLabel(label string) CommandOption

WithLabel adds a label that is shown at the bottom of the pane.

type RunOption

type RunOption func(*runOpts)

func WithAutoQuit

func WithAutoQuit() RunOption

WithAutoQuit turns on auto quitting after all commands are done. By default a dialog pops up asking if they want to quit.

func WithColumns

func WithColumns(cols int) RunOption

WithColumns sets the number of columns in the grid. The default is 1.

func WithCommandLines

func WithCommandLines() RunOption

WithCommandLines turns on printing the command line before command output in each pane.

func WithFinalView

func WithFinalView() RunOption

WithFinalView leaves a final, non-interactive view of the grid on screen after quitting. The default behavior is that of a typical TUI program: nothing is left in the scroll buffer.

Directories

Path Synopsis
cmd
demo command
test command

Jump to

Keyboard shortcuts

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