boone

package
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Feb 23, 2020 License: MPL-2.0 Imports: 27 Imported by: 0

Documentation

Overview

Package boone provides mechanisms for configuration, file-activity monitoring, command execution, and UI.

Index

Constants

View Source
const (
	// DefaultDebounce is the default Target.Debounce value.
	DefaultDebounce = "15s"

	// DefaultTimeout is the default Target.Handler.Exec.Timeout value.
	DefaultCmdTimeout = "15m"

	// DefaultCooldown is the default Global value.
	DefaultCooldown = "5s"
)
View Source
const (
	// BodyBoxTopPad selects top-padding of ListItemWidget body areas.
	BodyBoxTopPad = 1

	// DetailListMaxLen is the static row length of the status-detail list.
	DetailListMaxLen = 3

	// DetailStderrPos positions standard error as the first status-detail list item.
	DetailStderrPos = 0

	// DetailStdoutPos positions standard error as the second status-detail list item.
	DetailStdoutPos = 1

	// DetailMiscPos positions misc. details as the third status-detail list item.
	DetailMiscPos = 2

	// ListItemWidgetPad is the all-sides padding of every ListItemWidget.
	ListItemWidgetPad = 1

	// StatusListMaxLen is the static row length of the status list.
	StatusListMaxLen = 9
)
View Source
const (
	// ExecRequestQueueTick defines how often to dequeue exectution requests which have been
	// debounced and are ready to be fulfilled.
	ExecRequestQueueTick = time.Second
)

Variables

This section is empty.

Functions

func FinalizeConfig

func FinalizeConfig(all []*Target, c *Config) error

FinalizeConfig validates and finalizes Config fields.

func GetGlobInclude

func GetGlobInclude(globs []cage_filepath.GlobAnyOutput) (paths map[string]cage_filepath.Glob, err error)

GetGlobInclude extracts all included file/directory paths from the GlobAny outputs.

If a single path was covered by multiple globs, the first encountered Include will be output.

func GetTargetGlob

func GetTargetGlob(include []cage_filepath.Glob, exclude []cage_filepath.Glob) (list []cage_filepath.GlobAnyOutput, err error)

GetTargetGlob searches for files which match at least one Include or one Exclude pattern.

It returns one GlobAnyOutput per input inclusion Glob. GlobAnyOutput.Include holds the concrete paths which matched at least one inclusion pattern and no exclusion pattern. GlobAnyOutput.Exclude holds concrete paths which matched at least one inclusion pattern but was rejected because it matched at least one exclusion pattern.

func VisitDownstream

func VisitDownstream(t *Target, visit func(t *Target) error) (err error)

VisitDownstream calls the visitor with all targets found downstream recursively.

Types

type CmdTemplateData

type CmdTemplateData struct {
	// Dir is the absolute path of the parent directory of Path.
	Dir string

	// HandlerLabel is a copy of Target.Handler.Label.
	HandlerLabel string

	// IncludeGlob pattern from the config file Include responsible for the command being triggered.
	IncludeGlob string

	// IncludeRoot is the ancestor root directory from the config file Include responsible for the
	// command being triggered.
	IncludeRoot string

	// Path is the absolute path of the file/directory that was created or written to.
	Path string

	// TargetLabel is a copy of Target.Label.
	TargetLabel string
}

CmdTemplateData describes the built-in template variables available in Target.Handler.Exec.Cmd config strings.

type Config

type Config struct {
	// Data defines how to store program state.
	Data DataConfig

	// Global defines properties which should be applied to all targets, e.g. as default values.
	Global GlobalConfig

	// Target defines file/directory paths to watch and commands to run when they receive writes.
	Target []Target

	// Template holds key/value pairs which can be used in some string fields via {{.key_name}} syntax.
	//
	// Key names must use lowercase due to viper(/mapstructure?) limitation. Convention: "some_key_name".
	// https://github.com/spf13/viper/issues/411
	// https://github.com/spf13/viper/pull/635
	Template map[string]string

	// AutoStartTarget holds an Id for each Target that should run when the main process starts.
	AutoStartTarget []string
	// contains filtered or unexported fields
}

Config defines the structure of a config file.

func ReadConfigFile

func ReadConfigFile(name string) (c Config, err error)

ReadConfigFile converts a file to a Config value.

func (*Config) GetStartTarget

func (c *Config) GetStartTarget() (t []Target)

GetStartTarget returns all targets selected in the AutoStartTarget config.

type DataConfig

type DataConfig struct {
	// Session defines how to store sessions.
	Session SessionConfig
}

DataConfig defines how to store program state.

Its config section is Data.

type Dispatcher

type Dispatcher struct {
	// Clock supports timer mocking for debounce-sensitive tests.
	Clock cage_time.Clock

	// Cooldown is how long to wait after one command finishes before starting another.
	Cooldown time.Duration

	// Executor supports os/exec.Cmd mocking for tests.
	Executor cage_exec.Executor

	// ExecReqCh receives requests from Watcher when a target has been triggered.
	// Sends are blocked only for as long as it takes to manually add them to a slice queue.
	ExecReqCh chan ExecRequest

	// Log receives debug/info-level messages.
	Log *zap.Logger

	// TreePassCh transports messages from the Dispatcher to the UI about the successful execution of all
	// commands of the activity-triggered target and all commands of downstream targets.
	TreePassCh chan TreePass

	// TargetStartCh transports messages from Dispatcher to the UI about newly pending targets and the start
	// of every executed command.
	TargetStartCh chan Status

	// TargetPassCh transports messages from the Dispatcher to the UI about the successful execution of all
	// commands of a single target.
	TargetPassCh chan TargetPass

	// TargetFailCh transports messages from the Dispatcher to the UI about the failed execution of a command.
	TargetFailCh chan Status
	// contains filtered or unexported fields
}

Dispatcher receives ExecRequest messages from Watcher, runs/cancels target commands, and informs the UI of new target statuses via channels.

func NewDispatcher

func NewDispatcher(log *zap.Logger, targets []Target, panicCh chan interface{}, globalConfig GlobalConfig) (*Dispatcher, error)

NewDispatcher returns a new instance which is already watching for writes to targets' configured paths and sending messages to its channels about target run starts, failures, etc.

func (*Dispatcher) Start

func (d *Dispatcher) Start()

Start debounces activity messages from Watcher, cancels in-progress commands if newer activity will make them redundant, and enqueues targets to run.

It should run in its own goroutine because its for-select blocks.

func (*Dispatcher) Stop

func (d *Dispatcher) Stop()

Stop prevents the Dispatcher from receiving any more Watcher messages (and running any more targets), and kills the in-progress command if present.

type Exec

type Exec struct {
	// Cmd holds a single command or multiple commands in a "|" pipeline.
	Cmd string

	// Dir is the working directory.
	//
	// It is relative to Target.Root and Target.Root by default.
	Dir string

	// Timeout is a time.Duration compatible string from the config file that defines
	// how long to wait before cancelling the command.
	Timeout string

	// Env holds "KEY=VALUE" pairs to overwrite in the current environment.
	Env []string
	// contains filtered or unexported fields
}

Exec defines what command to run and how to run it.

func (Exec) GetTimeout

func (e Exec) GetTimeout() time.Duration

GetTimeout returns the parsed value of Timeout.

type ExecRequest

type ExecRequest struct {
	// Origin is a free-form value, currently only for logging, which indicates the cause
	// of the request.
	Cause string

	// Debounce is how long to wait for file activity to stop before running the target.
	Debounce time.Duration

	// Event describes the filesystem operation which led to the request.
	Event watcher.Event

	// Include is the path pattern responsible for the Watcher capturing the activity.
	Include cage_filepath.Glob

	// RecvTime is when Dispatcher received the ExecRequest.
	//
	// It is used to cancel target runs before they start when the request has alraedy been sent to
	// Dispatcher.runTargetCh but should be ignored because a newer request was received in the meantime.
	RecvTime time.Time

	// Tree holds all targets downstream from the activity-triggered target.
	Tree []TargetTree

	// TargetId is a copy of the Id field of the activity-triggered Target.
	TargetId string

	// TargetLabel is a copy of the Label field of the activity-triggered Target.
	TargetLabel string
}

ExecRequest is a channel-sent request to run a target (and its downstream targets). Typically it orignates from a Watcher which detected target-specific activity, but it may also originate directly from sub-commands, e.g. to support AutoStartTarget.

type GlobalConfig

type GlobalConfig struct {
	// Cooldown is a time.Duration compatible string which selects how long to wait after one command
	// finishes before starting another.
	Cooldown string

	// Exclude are appended to every Target.Exclude list.
	Exclude []cage_filepath.Glob
	// contains filtered or unexported fields
}

GlobalConfig defines properties which should be applied to all targets, e.g. as default values.

func (GlobalConfig) GetCooldown

func (c GlobalConfig) GetCooldown() time.Duration

GetCooldown returns the converted value of Cooldown.

type Handler

type Handler struct {
	// Label is displayed to users in output for reference/debugging/etc. and also
	// provides a documentation in the config file on the intent.
	//
	// It is a required field.
	Label string

	// Exec defines the commands to execute.
	Exec []Exec
}

Handler defines one or more commands that must execute in response to a target trigger.

type ListItemWidget

type ListItemWidget struct {
	// Container is the flexible height/width box which bounds the Header and Body areas.
	Container *tview.Flex

	// Header areas are single-lined and display target labels/statuses/shortcuts in the status list,
	// and display detail types/shortcuts in the status-detail list.
	Header *tview.TextView

	// Body areas expand to use all space unused by the Header and display a snippet of standard error
	// in the status list, and display a detail snippets in the status-detail list.
	Body *tview.TextView
}

ListItemWidget is used to represent the status and status-detail lists.

func NewListItemWidget

func NewListItemWidget() *ListItemWidget

NewListItemWidget returns a widget initialized with its container, header, and body areas.

type Session

type Session struct {
	// Statuses holds one Status per Target which was displayed in the UI when the Session value is
	// created.
	Statuses []Status

	// Version is a copy of the SessionVersion constant when the Session value is created.
	Version int
}

Session is written to file periodically to support resumption of targets which were pending/running, and tracking unresolved target failures.

type SessionConfig

type SessionConfig struct {
	File string
}

SessionConfig defines how to store sessions.

Its config section is Data.Session.

type Status

type Status struct {
	// Cause explains why the status is in the list.
	Cause TargetStatus

	// Cmd was the final command string after template expansion.
	Cmd string

	// Downstream holds labels of all downstream targets included in the run.
	Downstream []string

	// EndTime is when the Cmd finished.
	EndTime time.Time

	// Err is non-nil if Cmd failed.
	Err string

	// HandlerLabel is from the source of the status.
	HandlerLabel string

	// Include is the one responsible for the capturing the file activity which led to running the target.
	Include cage_filepath.Glob

	// Op is the type of filesystem operation which led to target execution.
	//
	// It is "Create", "Rename", or "Write"
	Op string

	// Path identifies the file whose Op activity triggered the target.
	Path string

	// Pid is the process Id of Cmd.
	Pid []int

	// RunLen is how long Cmd ran.
	RunLen time.Duration

	// StartTime is when Cmd started.
	StartTime time.Time

	// Stderr is collected from Cmd exceution.
	Stderr string

	// Stdout is collected from Cmd exceution.
	Stdout string

	// TargetId is from the source of the status.
	TargetId string

	// TargetLabel is from the source of the status.
	TargetLabel string

	// UpstreamTargetLabel is the one whose activity triggered the handler execution flow
	// that may include one or more (of its) downstream targets. If there were no
	// downstream targets, it should equal the Target field.
	UpstreamTargetLabel string
}

Status describes a target listed in the UI on its initial screen.

type Target

type Target struct {
	// Debounce is a time.Duration compatible string from the config file that defines
	// how long to wait after file activity settles before executing handlers.
	Debounce string

	// Downstream holds all direct descendants.
	//
	// It is generated at startup.
	Downstream []*Target

	// Exclude defines the path patterns of files/directories which should invalidate an Include match.
	Exclude []cage_filepath.Glob

	// Handler defines the commands to run if the target is triggered by write-activity.
	Handler []Handler

	// Id is user-defined, ideally short, and must be unique in the config file.
	//
	// It is an optional field and supports features like upstream-target triggers.
	Id string

	// Include defines the path patterns of files/directories whose write-activity can trigger this target.
	Include []cage_filepath.Glob

	// Label is displayed to users in output for reference/debugging/etc. and also
	// provides a documentation in the config file on the intent.
	//
	// It is a required field.
	Label string

	// Root is the default path prefix value for Include.Root fields.
	Root string

	// Tree holds one item per Target which Dispatcher should execute when this Target is
	// triggered. It includes ths Target in the first item, followed by all downstream
	// targets found recursively.
	//
	// It only holds the minimum details of each target in order to avoid data races,
	// e.g. that might happen with a map of Target/*Target.
	//
	// It is generated at startup.
	Tree []TargetTree

	// Upstream holds Id values of targets that, when triggered, also trigger this target.
	Upstream []string
	// contains filtered or unexported fields
}

Target defines upstream-target and/or filesystem triggers, and the handlers to run as a result.

Upstream Target.Id values will be stored in an app-level map instead of this type.

func (*Target) ContainsDownstream

func (t *Target) ContainsDownstream(targetId string) (found bool)

ContainsDownstream returns true if a target is found downstream recursively.

func (*Target) ExpandTemplateVars

func (t *Target) ExpandTemplateVars(data map[string]string) error

ExpandTemplateVars updates Target configuration string fields by expanding template variables with associated input values.

func (Target) GetDebounce

func (t Target) GetDebounce() time.Duration

GetDebounce returns the parsed version of Debounce.

func (*Target) MatchPath

func (t *Target) MatchPath(name string) (cage_filepath.MatchAnyOutput, error)

MatchPath checks if the input path matches one of the target's inclusion patterns and no exclusion pattern.

type TargetContext

type TargetContext struct {
	// Ctx is initialized by Dispatcher when it starts the first handler.
	// If there are multiple handlers, they all share the same value in order
	// to allow Dispatcher to cancel the target regardless of which handler
	// is running.
	Ctx context.Context

	// Cancel is invoked to kill the running command.
	Cancel context.CancelFunc
}

TargetContext enables Dispatcher to cancel a target's command execution if its watched paths receive activity in the meantime, invalidating the current execution.

type TargetPass

type TargetPass struct {
	// RunLen is how long it took to run a target's command list.
	RunLen time.Duration

	// TargetId is a copy of Target.Id.
	TargetId string
}

TargetPass describes a target whose commands all finished successfully.

type TargetStatus

type TargetStatus string

TargetStatus explains why a target is listed in the UI on the initial screen.

const (
	// PreDebounce prevents cage/os/file/watcher.Fsnotify from sending duplicate events,
	// in some situations, when both a file and its directory are watched.
	//
	// This value was selected because it's assumed to be long enough to capture all the
	// duplicates and less than user-selected per-Target debounce values.
	PreDebounce = 500 * time.Millisecond

	// SessionVersion is included in the encoded Session file to support potential compatibility work.
	SessionVersion = 1

	// Target
	TargetCanceled TargetStatus = "canceled"

	// TargetFailed indicates a target command returned a non-zero exit code and the Dispatcher
	// will not proceed any further with that target until it is activated again.
	TargetFailed TargetStatus = "failed"

	// TargetPending indicates the target's latest file activity has been debounced, the target
	// has been enqueued to run, and it is waiting to start.
	TargetPending TargetStatus = "pending"

	// TargetResumed indicates the program saved a TargetStarted-status target in its session file
	// (if configured) at shutdown, then enqueued it during startup.
	TargetResumed TargetStatus = "resumed"

	// TargetStarted indicates a Dispatcher has started running the target's command(s).
	TargetStarted TargetStatus = "started"
)

type TargetTree

type TargetTree struct {
	Id      string
	Label   string
	Handler []Handler
}

TargetTree is a limited copy of Target fields which describe a single downstream target.

It is used in Target as an abbreviated inventory of which targets should also run after the upstream target finishes.

type TreePass

type TreePass struct {
	// DispatchTargetId is the first target in the tree and whose activity led to the tree execution.
	DispatchTargetId string
}

TreePass describes a set of targets (activity-triggered target and all its downstream targets) whose commands all finished successfully.

type UI

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

UI displays the status of targets which are scheduled, currently running, or have stopped due to an error. It maintains the data necessary to describe the target statuses based on channel messages from Dispatcher. It also responds to keyboard events in order to support screen navigation.

func NewUI

func NewUI(log *zap.Logger, targetStartCh chan Status, targetPassCh chan TargetPass, targetFailCh chan Status, statusList []Status) *UI

NewUI returns a UI instance configured to listen for status updates from the input channel.

func (*UI) ExitCh

func (u *UI) ExitCh() <-chan struct{}

ExitCh provides external listeners to know when the UI is shutting down based on a keyboard event.

func (*UI) Init

func (u *UI) Init()

Init creates all the UI widgets and displays the status list.

func (*UI) InputCapture

func (u *UI) InputCapture(event *tcell.EventKey) *tcell.EventKey

InputCapture listens for keyboard events from all screens.

func (*UI) SessionCh

func (u *UI) SessionCh() <-chan Session

SessionCh provides external listeners to know when the newest session description is available

func (*UI) Start

func (u *UI) Start() error

Start begins the goroutines which update the UI based on new data from a Dispatcher, periodically update the displayed relative times, and which render the UI.

It blocks until the UI is exited via keyboard shortcut.

func (*UI) Stop

func (u *UI) Stop()

Stop ends UI rendering and keyboard event capturing.

It must be called to prevent corrupting the terminal such that `reset` is required. See tview's Fini function (https://github.com/rivo/tview/blob/11727c933b6d128d588006cc14105160c5413585/application.go#L90).

It unblocks the goroutine which executes Start.

type Watcher

type Watcher struct {
	// Watcher is the actual filesystem monitor. Watcher is a subscriber of events emitted by the monitor.
	//
	// See NewDispatcher for how it and Watcher are wired together.
	watcher.Watcher

	// PanicCh transports messages from Watcher to the CLI to support cleaner shutdowns.
	PanicCh chan<- interface{}

	// ExecReqCh transports messages from Watcher to the Dispatcher to run activated targets.
	ExecReqCh chan<- ExecRequest

	// AddPathCh transports messages from Watcher to listeners which contain paths to newly
	// created files/directories that are now themselves watched for writes.
	//
	// It is currently only used by tests.
	AddPathCh chan<- string

	// Target is the scope of this Watcher's write-activity detection.
	Target Target

	// Log receives debug/info-level messages.
	Log *zap.Logger
	// contains filtered or unexported fields
}

Watcher listens for the write-activity of a single target's files/directories and sends Dispatcher requests to execute the activated targets.

It does not itself monitor filesystem events and instead implements ca/cage/os/file/watcher.Subscriber to receive events/errors from the actual monitor (Watcher.watcher).

func (*Watcher) Error

func (w *Watcher) Error(err error)

Error receives errors from the filesystem monitor (Watcher.watcher).

It implements ca/cage/os/file/watcher.Subscriber.

func (*Watcher) Event

func (w *Watcher) Event(event watcher.Event)

Event receives activity descriptions from the filesystem monitor (Watcher.watcher).

It implements ca/cage/os/file/watcher.Subscriber.

func (*Watcher) SetInclude

func (w *Watcher) SetInclude(i map[string]cage_filepath.Glob)

SetInclude assigns the inclusion patterns to use when filtering write-activity.

Jump to

Keyboard shortcuts

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