1
0
mirror of https://github.com/go-task/task.git synced 2025-06-23 00:38:19 +02:00

feat: better functional options for reader (#2148)

This commit is contained in:
Pete Davison
2025-04-01 14:51:25 +01:00
committed by GitHub
parent 1939f83ffe
commit cd81d94e18
5 changed files with 266 additions and 203 deletions

View File

@ -17,7 +17,8 @@ import (
) )
type ( type (
// An ExecutorOption is a functional option for an [Executor]. // An ExecutorOption is any type that can apply a configuration to an
// [Executor].
ExecutorOption interface { ExecutorOption interface {
ApplyToExecutor(*Executor) ApplyToExecutor(*Executor)
} }
@ -112,14 +113,14 @@ func (e *Executor) Options(opts ...ExecutorOption) {
// WithDir sets the working directory of the [Executor]. By default, the // WithDir sets the working directory of the [Executor]. By default, the
// directory is set to the user's current working directory. // directory is set to the user's current working directory.
func WithDir(dir string) ExecutorOption { func WithDir(dir string) ExecutorOption {
return dirOption{dir} return &dirOption{dir}
} }
type dirOption struct { type dirOption struct {
dir string dir string
} }
func (o dirOption) ApplyToExecutor(e *Executor) { func (o *dirOption) ApplyToExecutor(e *Executor) {
e.Dir = o.dir e.Dir = o.dir
} }
@ -127,14 +128,14 @@ func (o dirOption) ApplyToExecutor(e *Executor) {
// default, Task will search for one of the default Taskfiles in the given // default, Task will search for one of the default Taskfiles in the given
// directory. // directory.
func WithEntrypoint(entrypoint string) ExecutorOption { func WithEntrypoint(entrypoint string) ExecutorOption {
return entrypointOption{entrypoint} return &entrypointOption{entrypoint}
} }
type entrypointOption struct { type entrypointOption struct {
entrypoint string entrypoint string
} }
func (o entrypointOption) ApplyToExecutor(e *Executor) { func (o *entrypointOption) ApplyToExecutor(e *Executor) {
e.Entrypoint = o.entrypoint e.Entrypoint = o.entrypoint
} }
@ -142,98 +143,98 @@ func (o entrypointOption) ApplyToExecutor(e *Executor) {
// storing temporary files like checksums and cached remote files. By default, // storing temporary files like checksums and cached remote files. By default,
// the temporary directory is set to the user's temporary directory. // the temporary directory is set to the user's temporary directory.
func WithTempDir(tempDir TempDir) ExecutorOption { func WithTempDir(tempDir TempDir) ExecutorOption {
return tempDirOption{tempDir} return &tempDirOption{tempDir}
} }
type tempDirOption struct { type tempDirOption struct {
tempDir TempDir tempDir TempDir
} }
func (o tempDirOption) ApplyToExecutor(e *Executor) { func (o *tempDirOption) ApplyToExecutor(e *Executor) {
e.TempDir = o.tempDir e.TempDir = o.tempDir
} }
// WithForce ensures that the [Executor] always runs a task, even when // WithForce ensures that the [Executor] always runs a task, even when
// fingerprinting or prompts would normally stop it. // fingerprinting or prompts would normally stop it.
func WithForce(force bool) ExecutorOption { func WithForce(force bool) ExecutorOption {
return forceOption{force} return &forceOption{force}
} }
type forceOption struct { type forceOption struct {
force bool force bool
} }
func (o forceOption) ApplyToExecutor(e *Executor) { func (o *forceOption) ApplyToExecutor(e *Executor) {
e.Force = o.force e.Force = o.force
} }
// WithForceAll ensures that the [Executor] always runs all tasks (including // WithForceAll ensures that the [Executor] always runs all tasks (including
// subtasks), even when fingerprinting or prompts would normally stop them. // subtasks), even when fingerprinting or prompts would normally stop them.
func WithForceAll(forceAll bool) ExecutorOption { func WithForceAll(forceAll bool) ExecutorOption {
return forceAllOption{forceAll} return &forceAllOption{forceAll}
} }
type forceAllOption struct { type forceAllOption struct {
forceAll bool forceAll bool
} }
func (o forceAllOption) ApplyToExecutor(e *Executor) { func (o *forceAllOption) ApplyToExecutor(e *Executor) {
e.ForceAll = o.forceAll e.ForceAll = o.forceAll
} }
// WithInsecure allows the [Executor] to make insecure connections when reading // WithInsecure allows the [Executor] to make insecure connections when reading
// remote taskfiles. By default, insecure connections are rejected. // remote taskfiles. By default, insecure connections are rejected.
func WithInsecure(insecure bool) ExecutorOption { func WithInsecure(insecure bool) ExecutorOption {
return insecureOption{insecure} return &insecureOption{insecure}
} }
type insecureOption struct { type insecureOption struct {
insecure bool insecure bool
} }
func (o insecureOption) ApplyToExecutor(e *Executor) { func (o *insecureOption) ApplyToExecutor(e *Executor) {
e.Insecure = o.insecure e.Insecure = o.insecure
} }
// WithDownload forces the [Executor] to download a fresh copy of the taskfile // WithDownload forces the [Executor] to download a fresh copy of the taskfile
// from the remote source. // from the remote source.
func WithDownload(download bool) ExecutorOption { func WithDownload(download bool) ExecutorOption {
return downloadOption{download} return &downloadOption{download}
} }
type downloadOption struct { type downloadOption struct {
download bool download bool
} }
func (o downloadOption) ApplyToExecutor(e *Executor) { func (o *downloadOption) ApplyToExecutor(e *Executor) {
e.Download = o.download e.Download = o.download
} }
// WithOffline stops the [Executor] from being able to make network connections. // WithOffline stops the [Executor] from being able to make network connections.
// It will still be able to read local files and cached copies of remote files. // It will still be able to read local files and cached copies of remote files.
func WithOffline(offline bool) ExecutorOption { func WithOffline(offline bool) ExecutorOption {
return offlineOption{offline} return &offlineOption{offline}
} }
type offlineOption struct { type offlineOption struct {
offline bool offline bool
} }
func (o offlineOption) ApplyToExecutor(e *Executor) { func (o *offlineOption) ApplyToExecutor(e *Executor) {
e.Offline = o.offline e.Offline = o.offline
} }
// WithTimeout sets the [Executor]'s timeout for fetching remote taskfiles. By // WithTimeout sets the [Executor]'s timeout for fetching remote taskfiles. By
// default, the timeout is set to 10 seconds. // default, the timeout is set to 10 seconds.
func WithTimeout(timeout time.Duration) ExecutorOption { func WithTimeout(timeout time.Duration) ExecutorOption {
return timeoutOption{timeout} return &timeoutOption{timeout}
} }
type timeoutOption struct { type timeoutOption struct {
timeout time.Duration timeout time.Duration
} }
func (o timeoutOption) ApplyToExecutor(e *Executor) { func (o *timeoutOption) ApplyToExecutor(e *Executor) {
e.Timeout = o.timeout e.Timeout = o.timeout
} }
@ -241,166 +242,166 @@ func (o timeoutOption) ApplyToExecutor(e *Executor) {
// for changes to the fingerprint of the tasks that are run. When changes are // for changes to the fingerprint of the tasks that are run. When changes are
// detected, a new task run is triggered. // detected, a new task run is triggered.
func WithWatch(watch bool) ExecutorOption { func WithWatch(watch bool) ExecutorOption {
return watchOption{watch} return &watchOption{watch}
} }
type watchOption struct { type watchOption struct {
watch bool watch bool
} }
func (o watchOption) ApplyToExecutor(e *Executor) { func (o *watchOption) ApplyToExecutor(e *Executor) {
e.Watch = o.watch e.Watch = o.watch
} }
// WithVerbose tells the [Executor] to output more information about the tasks // WithVerbose tells the [Executor] to output more information about the tasks
// that are run. // that are run.
func WithVerbose(verbose bool) ExecutorOption { func WithVerbose(verbose bool) ExecutorOption {
return verboseOption{verbose} return &verboseOption{verbose}
} }
type verboseOption struct { type verboseOption struct {
verbose bool verbose bool
} }
func (o verboseOption) ApplyToExecutor(e *Executor) { func (o *verboseOption) ApplyToExecutor(e *Executor) {
e.Verbose = o.verbose e.Verbose = o.verbose
} }
// WithSilent tells the [Executor] to suppress all output except for the output // WithSilent tells the [Executor] to suppress all output except for the output
// of the tasks that are run. // of the tasks that are run.
func WithSilent(silent bool) ExecutorOption { func WithSilent(silent bool) ExecutorOption {
return silentOption{silent} return &silentOption{silent}
} }
type silentOption struct { type silentOption struct {
silent bool silent bool
} }
func (o silentOption) ApplyToExecutor(e *Executor) { func (o *silentOption) ApplyToExecutor(e *Executor) {
e.Silent = o.silent e.Silent = o.silent
} }
// WithAssumeYes tells the [Executor] to assume "yes" for all prompts. // WithAssumeYes tells the [Executor] to assume "yes" for all prompts.
func WithAssumeYes(assumeYes bool) ExecutorOption { func WithAssumeYes(assumeYes bool) ExecutorOption {
return assumeYesOption{assumeYes} return &assumeYesOption{assumeYes}
} }
type assumeYesOption struct { type assumeYesOption struct {
assumeYes bool assumeYes bool
} }
func (o assumeYesOption) ApplyToExecutor(e *Executor) { func (o *assumeYesOption) ApplyToExecutor(e *Executor) {
e.AssumeYes = o.assumeYes e.AssumeYes = o.assumeYes
} }
// WithAssumeTerm is used for testing purposes to simulate a terminal. // WithAssumeTerm is used for testing purposes to simulate a terminal.
func WithAssumeTerm(assumeTerm bool) ExecutorOption { func WithAssumeTerm(assumeTerm bool) ExecutorOption {
return assumeTermOption{assumeTerm} return &assumeTermOption{assumeTerm}
} }
type assumeTermOption struct { type assumeTermOption struct {
assumeTerm bool assumeTerm bool
} }
func (o assumeTermOption) ApplyToExecutor(e *Executor) { func (o *assumeTermOption) ApplyToExecutor(e *Executor) {
e.AssumeTerm = o.assumeTerm e.AssumeTerm = o.assumeTerm
} }
// WithDry tells the [Executor] to output the commands that would be run without // WithDry tells the [Executor] to output the commands that would be run without
// actually running them. // actually running them.
func WithDry(dry bool) ExecutorOption { func WithDry(dry bool) ExecutorOption {
return dryOption{dry} return &dryOption{dry}
} }
type dryOption struct { type dryOption struct {
dry bool dry bool
} }
func (o dryOption) ApplyToExecutor(e *Executor) { func (o *dryOption) ApplyToExecutor(e *Executor) {
e.Dry = o.dry e.Dry = o.dry
} }
// WithSummary tells the [Executor] to output a summary of the given tasks // WithSummary tells the [Executor] to output a summary of the given tasks
// instead of running them. // instead of running them.
func WithSummary(summary bool) ExecutorOption { func WithSummary(summary bool) ExecutorOption {
return summaryOption{summary} return &summaryOption{summary}
} }
type summaryOption struct { type summaryOption struct {
summary bool summary bool
} }
func (o summaryOption) ApplyToExecutor(e *Executor) { func (o *summaryOption) ApplyToExecutor(e *Executor) {
e.Summary = o.summary e.Summary = o.summary
} }
// WithParallel tells the [Executor] to run tasks given in the same call in // WithParallel tells the [Executor] to run tasks given in the same call in
// parallel. // parallel.
func WithParallel(parallel bool) ExecutorOption { func WithParallel(parallel bool) ExecutorOption {
return parallelOption{parallel} return &parallelOption{parallel}
} }
type parallelOption struct { type parallelOption struct {
parallel bool parallel bool
} }
func (o parallelOption) ApplyToExecutor(e *Executor) { func (o *parallelOption) ApplyToExecutor(e *Executor) {
e.Parallel = o.parallel e.Parallel = o.parallel
} }
// WithColor tells the [Executor] whether or not to output using colorized // WithColor tells the [Executor] whether or not to output using colorized
// strings. // strings.
func WithColor(color bool) ExecutorOption { func WithColor(color bool) ExecutorOption {
return colorOption{color} return &colorOption{color}
} }
type colorOption struct { type colorOption struct {
color bool color bool
} }
func (o colorOption) ApplyToExecutor(e *Executor) { func (o *colorOption) ApplyToExecutor(e *Executor) {
e.Color = o.color e.Color = o.color
} }
// WithConcurrency sets the maximum number of tasks that the [Executor] can run // WithConcurrency sets the maximum number of tasks that the [Executor] can run
// in parallel. // in parallel.
func WithConcurrency(concurrency int) ExecutorOption { func WithConcurrency(concurrency int) ExecutorOption {
return concurrencyOption{concurrency} return &concurrencyOption{concurrency}
} }
type concurrencyOption struct { type concurrencyOption struct {
concurrency int concurrency int
} }
func (o concurrencyOption) ApplyToExecutor(e *Executor) { func (o *concurrencyOption) ApplyToExecutor(e *Executor) {
e.Concurrency = o.concurrency e.Concurrency = o.concurrency
} }
// WithInterval sets the interval at which the [Executor] will wait for // WithInterval sets the interval at which the [Executor] will wait for
// duplicated events before running a task. // duplicated events before running a task.
func WithInterval(interval time.Duration) ExecutorOption { func WithInterval(interval time.Duration) ExecutorOption {
return intervalOption{interval} return &intervalOption{interval}
} }
type intervalOption struct { type intervalOption struct {
interval time.Duration interval time.Duration
} }
func (o intervalOption) ApplyToExecutor(e *Executor) { func (o *intervalOption) ApplyToExecutor(e *Executor) {
e.Interval = o.interval e.Interval = o.interval
} }
// WithOutputStyle sets the output style of the [Executor]. By default, the // WithOutputStyle sets the output style of the [Executor]. By default, the
// output style is set to the style defined in the Taskfile. // output style is set to the style defined in the Taskfile.
func WithOutputStyle(outputStyle ast.Output) ExecutorOption { func WithOutputStyle(outputStyle ast.Output) ExecutorOption {
return outputStyleOption{outputStyle} return &outputStyleOption{outputStyle}
} }
type outputStyleOption struct { type outputStyleOption struct {
outputStyle ast.Output outputStyle ast.Output
} }
func (o outputStyleOption) ApplyToExecutor(e *Executor) { func (o *outputStyleOption) ApplyToExecutor(e *Executor) {
e.OutputStyle = o.outputStyle e.OutputStyle = o.outputStyle
} }
@ -408,67 +409,67 @@ func (o outputStyleOption) ApplyToExecutor(e *Executor) {
// default, the sorter is set to sort tasks alphabetically, but with tasks with // default, the sorter is set to sort tasks alphabetically, but with tasks with
// no namespace (in the root Taskfile) first. // no namespace (in the root Taskfile) first.
func WithTaskSorter(sorter sort.Sorter) ExecutorOption { func WithTaskSorter(sorter sort.Sorter) ExecutorOption {
return taskSorterOption{sorter} return &taskSorterOption{sorter}
} }
type taskSorterOption struct { type taskSorterOption struct {
sorter sort.Sorter sorter sort.Sorter
} }
func (o taskSorterOption) ApplyToExecutor(e *Executor) { func (o *taskSorterOption) ApplyToExecutor(e *Executor) {
e.TaskSorter = o.sorter e.TaskSorter = o.sorter
} }
// WithStdin sets the [Executor]'s standard input [io.Reader]. // WithStdin sets the [Executor]'s standard input [io.Reader].
func WithStdin(stdin io.Reader) ExecutorOption { func WithStdin(stdin io.Reader) ExecutorOption {
return stdinOption{stdin} return &stdinOption{stdin}
} }
type stdinOption struct { type stdinOption struct {
stdin io.Reader stdin io.Reader
} }
func (o stdinOption) ApplyToExecutor(e *Executor) { func (o *stdinOption) ApplyToExecutor(e *Executor) {
e.Stdin = o.stdin e.Stdin = o.stdin
} }
// WithStdout sets the [Executor]'s standard output [io.Writer]. // WithStdout sets the [Executor]'s standard output [io.Writer].
func WithStdout(stdout io.Writer) ExecutorOption { func WithStdout(stdout io.Writer) ExecutorOption {
return stdoutOption{stdout} return &stdoutOption{stdout}
} }
type stdoutOption struct { type stdoutOption struct {
stdout io.Writer stdout io.Writer
} }
func (o stdoutOption) ApplyToExecutor(e *Executor) { func (o *stdoutOption) ApplyToExecutor(e *Executor) {
e.Stdout = o.stdout e.Stdout = o.stdout
} }
// WithStderr sets the [Executor]'s standard error [io.Writer]. // WithStderr sets the [Executor]'s standard error [io.Writer].
func WithStderr(stderr io.Writer) ExecutorOption { func WithStderr(stderr io.Writer) ExecutorOption {
return stderrOption{stderr} return &stderrOption{stderr}
} }
type stderrOption struct { type stderrOption struct {
stderr io.Writer stderr io.Writer
} }
func (o stderrOption) ApplyToExecutor(e *Executor) { func (o *stderrOption) ApplyToExecutor(e *Executor) {
e.Stderr = o.stderr e.Stderr = o.stderr
} }
// WithIO sets the [Executor]'s standard input, output, and error to the same // WithIO sets the [Executor]'s standard input, output, and error to the same
// [io.ReadWriter]. // [io.ReadWriter].
func WithIO(rw io.ReadWriter) ExecutorOption { func WithIO(rw io.ReadWriter) ExecutorOption {
return ioOption{rw} return &ioOption{rw}
} }
type ioOption struct { type ioOption struct {
rw io.ReadWriter rw io.ReadWriter
} }
func (o ioOption) ApplyToExecutor(e *Executor) { func (o *ioOption) ApplyToExecutor(e *Executor) {
e.Stdin = o.rw e.Stdin = o.rw
e.Stdout = o.rw e.Stdout = o.rw
e.Stderr = o.rw e.Stderr = o.rw
@ -476,13 +477,13 @@ func (o ioOption) ApplyToExecutor(e *Executor) {
// WithVersionCheck tells the [Executor] whether or not to check the version of // WithVersionCheck tells the [Executor] whether or not to check the version of
func WithVersionCheck(enableVersionCheck bool) ExecutorOption { func WithVersionCheck(enableVersionCheck bool) ExecutorOption {
return versionCheckOption{enableVersionCheck} return &versionCheckOption{enableVersionCheck}
} }
type versionCheckOption struct { type versionCheckOption struct {
enableVersionCheck bool enableVersionCheck bool
} }
func (o versionCheckOption) ApplyToExecutor(e *Executor) { func (o *versionCheckOption) ApplyToExecutor(e *Executor) {
e.EnableVersionCheck = o.enableVersionCheck e.EnableVersionCheck = o.enableVersionCheck
} }

View File

@ -72,13 +72,13 @@ func (e *Executor) readTaskfile(node taskfile.Node) error {
} }
reader := taskfile.NewReader( reader := taskfile.NewReader(
node, node,
taskfile.ReaderWithInsecure(e.Insecure), taskfile.WithInsecure(e.Insecure),
taskfile.ReaderWithDownload(e.Download), taskfile.WithDownload(e.Download),
taskfile.ReaderWithOffline(e.Offline), taskfile.WithOffline(e.Offline),
taskfile.ReaderWithTimeout(e.Timeout), taskfile.WithTimeout(e.Timeout),
taskfile.ReaderWithTempDir(e.TempDir.Remote), taskfile.WithTempDir(e.TempDir.Remote),
taskfile.ReaderWithDebugFunc(debugFunc), taskfile.WithDebugFunc(debugFunc),
taskfile.ReaderWithPromptFunc(promptFunc), taskfile.WithPromptFunc(promptFunc),
) )
graph, err := reader.Read() graph, err := reader.Read()
if err != nil { if err != nil {

View File

@ -28,14 +28,14 @@ Continue?`
) )
type ( type (
// ReaderDebugFunc is a function that is called when the [Reader] wants to // DebugFunc is a function that can be called to log debug messages.
// log debug messages DebugFunc func(string)
ReaderDebugFunc func(string) // PromptFunc is a function that can be called to prompt the user for input.
// ReaderPromptFunc is a function that is called when the [Reader] wants to PromptFunc func(string) error
// prompt the user in some way // A ReaderOption is any type that can apply a configuration to a [Reader].
ReaderPromptFunc func(string) error ReaderOption interface {
// ReaderOption is a function that configures a [Reader]. ApplyToReader(*Reader)
ReaderOption func(*Reader) }
// A Reader will recursively read Taskfiles from a given [Node] and build a // A Reader will recursively read Taskfiles from a given [Node] and build a
// [ast.TaskfileGraph] from them. // [ast.TaskfileGraph] from them.
Reader struct { Reader struct {
@ -46,8 +46,8 @@ type (
offline bool offline bool
timeout time.Duration timeout time.Duration
tempDir string tempDir string
debugFunc ReaderDebugFunc debugFunc DebugFunc
promptFunc ReaderPromptFunc promptFunc PromptFunc
promptMutex sync.Mutex promptMutex sync.Mutex
} }
) )
@ -78,72 +78,113 @@ func NewReader(
// the [Reader]. // the [Reader].
func (r *Reader) Options(opts ...ReaderOption) { func (r *Reader) Options(opts ...ReaderOption) {
for _, opt := range opts { for _, opt := range opts {
opt(r) opt.ApplyToReader(r)
} }
} }
// ReaderWithInsecure allows the [Reader] to make insecure connections when // WithInsecure allows the [Reader] to make insecure connections when reading
// reading remote taskfiles. By default, insecure connections are rejected. // remote taskfiles. By default, insecure connections are rejected.
func ReaderWithInsecure(insecure bool) ReaderOption { func WithInsecure(insecure bool) ReaderOption {
return func(r *Reader) { return &insecureOption{insecure: insecure}
r.insecure = insecure
}
} }
// ReaderWithDownload forces the [Reader] to download a fresh copy of the type insecureOption struct {
// taskfile from the remote source. insecure bool
func ReaderWithDownload(download bool) ReaderOption {
return func(r *Reader) {
r.download = download
}
} }
// ReaderWithOffline stops the [Reader] from being able to make network func (o *insecureOption) ApplyToReader(r *Reader) {
// connections. It will still be able to read local files and cached copies of r.insecure = o.insecure
// remote files.
func ReaderWithOffline(offline bool) ReaderOption {
return func(r *Reader) {
r.offline = offline
}
} }
// ReaderWithTimeout sets the [Reader]'s timeout for fetching remote taskfiles. // WithDownload forces the [Reader] to download a fresh copy of the taskfile
// By default, the timeout is set to 10 seconds. // from the remote source.
func ReaderWithTimeout(timeout time.Duration) ReaderOption { func WithDownload(download bool) ReaderOption {
return func(r *Reader) { return &downloadOption{download: download}
r.timeout = timeout
}
} }
// ReaderWithTempDir sets the temporary directory that will be used by the type downloadOption struct {
// [Reader]. By default, the reader uses [os.TempDir]. download bool
func ReaderWithTempDir(tempDir string) ReaderOption {
return func(r *Reader) {
r.tempDir = tempDir
}
} }
// ReaderWithDebugFunc sets the debug function to be used by the [Reader]. If func (o *downloadOption) ApplyToReader(r *Reader) {
// set, this function will be called with debug messages. This can be useful if r.download = o.download
// the caller wants to log debug messages from the [Reader]. By default, no
// debug function is set and the logs are not written.
func ReaderWithDebugFunc(debugFunc ReaderDebugFunc) ReaderOption {
return func(r *Reader) {
r.debugFunc = debugFunc
}
} }
// ReaderWithPromptFunc sets the prompt function to be used by the [Reader]. If // WithOffline stops the [Reader] from being able to make network connections.
// set, this function will be called with prompt messages. The function should // It will still be able to read local files and cached copies of remote files.
func WithOffline(offline bool) ReaderOption {
return &offlineOption{offline: offline}
}
type offlineOption struct {
offline bool
}
func (o *offlineOption) ApplyToReader(r *Reader) {
r.offline = o.offline
}
// WithTimeout sets the [Reader]'s timeout for fetching remote taskfiles. By
// default, the timeout is set to 10 seconds.
func WithTimeout(timeout time.Duration) ReaderOption {
return &timeoutOption{timeout: timeout}
}
type timeoutOption struct {
timeout time.Duration
}
func (o *timeoutOption) ApplyToReader(r *Reader) {
r.timeout = o.timeout
}
// WithTempDir sets the temporary directory that will be used by the [Reader].
// By default, the reader uses [os.TempDir].
func WithTempDir(tempDir string) ReaderOption {
return &tempDirOption{tempDir: tempDir}
}
type tempDirOption struct {
tempDir string
}
func (o *tempDirOption) ApplyToReader(r *Reader) {
r.tempDir = o.tempDir
}
// WithDebugFunc sets the debug function to be used by the [Reader]. If set,
// this function will be called with debug messages. This can be useful if the
// caller wants to log debug messages from the [Reader]. By default, no debug
// function is set and the logs are not written.
func WithDebugFunc(debugFunc DebugFunc) ReaderOption {
return &debugFuncOption{debugFunc: debugFunc}
}
type debugFuncOption struct {
debugFunc DebugFunc
}
func (o *debugFuncOption) ApplyToReader(r *Reader) {
r.debugFunc = o.debugFunc
}
// WithPromptFunc sets the prompt function to be used by the [Reader]. If set,
// this function will be called with prompt messages. The function should
// optionally log the message to the user and return nil if the prompt is // optionally log the message to the user and return nil if the prompt is
// accepted and the execution should continue. Otherwise, it should return an // accepted and the execution should continue. Otherwise, it should return an
// error which describes why the the prompt was rejected. This can then be // error which describes why the prompt was rejected. This can then be caught
// caught and used later when calling the [Reader.Read] method. By default, no // and used later when calling the [Reader.Read] method. By default, no prompt
// prompt function is set and all prompts are automatically accepted. // function is set and all prompts are automatically accepted.
func ReaderWithPromptFunc(promptFunc ReaderPromptFunc) ReaderOption { func WithPromptFunc(promptFunc PromptFunc) ReaderOption {
return func(r *Reader) { return &promptFuncOption{promptFunc: promptFunc}
r.promptFunc = promptFunc
} }
type promptFuncOption struct {
promptFunc PromptFunc
}
func (o *promptFuncOption) ApplyToReader(r *Reader) {
r.promptFunc = o.promptFunc
} }
// Read will read the Taskfile defined by the [Reader]'s [Node] and recurse // Read will read the Taskfile defined by the [Reader]'s [Node] and recurse
@ -292,9 +333,9 @@ func (r *Reader) readNode(node Node) (*ast.Taskfile, error) {
taskfileDecodeErr := &errors.TaskfileDecodeError{} taskfileDecodeErr := &errors.TaskfileDecodeError{}
if errors.As(err, &taskfileDecodeErr) { if errors.As(err, &taskfileDecodeErr) {
snippet := NewSnippet(b, snippet := NewSnippet(b,
SnippetWithLine(taskfileDecodeErr.Line), WithLine(taskfileDecodeErr.Line),
SnippetWithColumn(taskfileDecodeErr.Column), WithColumn(taskfileDecodeErr.Column),
SnippetWithPadding(2), WithPadding(2),
) )
return nil, taskfileDecodeErr.WithFileInfo(node.Location(), snippet.String()) return nil, taskfileDecodeErr.WithFileInfo(node.Location(), snippet.String())
} }

View File

@ -33,8 +33,10 @@ func init() {
} }
type ( type (
// SnippetOption is a function that configures a [Snippet]. // A SnippetOption is any type that can apply a configuration to a [Snippet].
SnippetOption func(*Snippet) SnippetOption interface {
ApplyToSnippet(*Snippet)
}
// A Snippet is a syntax highlighted snippet of a Taskfile with optional // A Snippet is a syntax highlighted snippet of a Taskfile with optional
// padding and a line and column indicator. // padding and a line and column indicator.
Snippet struct { Snippet struct {
@ -55,9 +57,7 @@ type (
// determines the number of lines to include before and after the chosen line. // determines the number of lines to include before and after the chosen line.
func NewSnippet(b []byte, opts ...SnippetOption) *Snippet { func NewSnippet(b []byte, opts ...SnippetOption) *Snippet {
snippet := &Snippet{} snippet := &Snippet{}
for _, opt := range opts { snippet.Options(opts...)
opt(snippet)
}
// Syntax highlight the input and split it into lines // Syntax highlight the input and split it into lines
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
@ -80,40 +80,61 @@ func NewSnippet(b []byte, opts ...SnippetOption) *Snippet {
// to the [Snippet]. // to the [Snippet].
func (s *Snippet) Options(opts ...SnippetOption) { func (s *Snippet) Options(opts ...SnippetOption) {
for _, opt := range opts { for _, opt := range opts {
opt(s) opt.ApplyToSnippet(s)
} }
} }
// SnippetWithLine specifies the line number that the [Snippet] should center // WithLine specifies the line number that the [Snippet] should center around
// around and point to. // and point to.
func SnippetWithLine(line int) SnippetOption { func WithLine(line int) SnippetOption {
return func(snippet *Snippet) { return &lineOption{line: line}
snippet.line = line
}
} }
// SnippetWithColumn specifies the column number that the [Snippet] should type lineOption struct {
// point to. line int
func SnippetWithColumn(column int) SnippetOption {
return func(snippet *Snippet) {
snippet.column = column
}
} }
// SnippetWithPadding specifies the number of lines to include before and after func (o *lineOption) ApplyToSnippet(s *Snippet) {
// the selected line in the [Snippet]. s.line = o.line
func SnippetWithPadding(padding int) SnippetOption {
return func(snippet *Snippet) {
snippet.padding = padding
}
} }
// SnippetWithNoIndicators specifies that the [Snippet] should not include line // WithColumn specifies the column number that the [Snippet] should point to.
// or column indicators. func WithColumn(column int) SnippetOption {
func SnippetWithNoIndicators() SnippetOption { return &columnOption{column: column}
return func(snippet *Snippet) {
snippet.noIndicators = true
} }
type columnOption struct {
column int
}
func (o *columnOption) ApplyToSnippet(s *Snippet) {
s.column = o.column
}
// WithPadding specifies the number of lines to include before and after the
// selected line in the [Snippet].
func WithPadding(padding int) SnippetOption {
return &paddingOption{padding: padding}
}
type paddingOption struct {
padding int
}
func (o *paddingOption) ApplyToSnippet(s *Snippet) {
s.padding = o.padding
}
// WithNoIndicators specifies that the [Snippet] should not include line or
// column indicators.
func WithNoIndicators() SnippetOption {
return &noIndicatorsOption{}
}
type noIndicatorsOption struct{}
func (o *noIndicatorsOption) ApplyToSnippet(s *Snippet) {
s.noIndicators = true
} }
func (s *Snippet) String() string { func (s *Snippet) String() string {

View File

@ -31,8 +31,8 @@ func TestNewSnippet(t *testing.T) {
name: "first line, first column", name: "first line, first column",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(1), WithLine(1),
SnippetWithColumn(1), WithColumn(1),
}, },
want: &Snippet{ want: &Snippet{
linesRaw: []string{ linesRaw: []string{
@ -52,9 +52,9 @@ func TestNewSnippet(t *testing.T) {
name: "first line, first column, padding=2", name: "first line, first column, padding=2",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(1), WithLine(1),
SnippetWithColumn(1), WithColumn(1),
SnippetWithPadding(2), WithPadding(2),
}, },
want: &Snippet{ want: &Snippet{
linesRaw: []string{ linesRaw: []string{
@ -96,8 +96,8 @@ func TestSnippetString(t *testing.T) {
name: "empty", name: "empty",
b: []byte{}, b: []byte{},
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(1), WithLine(1),
SnippetWithColumn(1), WithColumn(1),
}, },
want: "", want: "",
}, },
@ -110,7 +110,7 @@ func TestSnippetString(t *testing.T) {
name: "1st line, 0th column (line indicator only)", name: "1st line, 0th column (line indicator only)",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(1), WithLine(1),
}, },
want: "> 1 | \x1b[33mversion\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36m3\x1b[0m\x1b[1m\x1b[30m\x1b[0m", want: "> 1 | \x1b[33mversion\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36m3\x1b[0m\x1b[1m\x1b[30m\x1b[0m",
}, },
@ -118,7 +118,7 @@ func TestSnippetString(t *testing.T) {
name: "0th line, 1st column (column indicator only)", name: "0th line, 1st column (column indicator only)",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithColumn(1), WithColumn(1),
}, },
want: "", want: "",
}, },
@ -126,8 +126,8 @@ func TestSnippetString(t *testing.T) {
name: "0th line, 1st column, padding=2 (column indicator only)", name: "0th line, 1st column, padding=2 (column indicator only)",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithColumn(1), WithColumn(1),
SnippetWithPadding(2), WithPadding(2),
}, },
want: " 1 | \x1b[33mversion\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36m3\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 2 | \x1b[1m\x1b[30m\x1b[0m\n | ^", want: " 1 | \x1b[33mversion\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36m3\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 2 | \x1b[1m\x1b[30m\x1b[0m\n | ^",
}, },
@ -135,8 +135,8 @@ func TestSnippetString(t *testing.T) {
name: "1st line, 1st column", name: "1st line, 1st column",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(1), WithLine(1),
SnippetWithColumn(1), WithColumn(1),
}, },
want: "> 1 | \x1b[33mversion\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36m3\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^", want: "> 1 | \x1b[33mversion\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36m3\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^",
}, },
@ -144,8 +144,8 @@ func TestSnippetString(t *testing.T) {
name: "1st line, 10th column", name: "1st line, 10th column",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(1), WithLine(1),
SnippetWithColumn(10), WithColumn(10),
}, },
want: "> 1 | \x1b[33mversion\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36m3\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^", want: "> 1 | \x1b[33mversion\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36m3\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^",
}, },
@ -153,9 +153,9 @@ func TestSnippetString(t *testing.T) {
name: "1st line, 1st column, padding=2", name: "1st line, 1st column, padding=2",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(1), WithLine(1),
SnippetWithColumn(1), WithColumn(1),
SnippetWithPadding(2), WithPadding(2),
}, },
want: "> 1 | \x1b[33mversion\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36m3\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^\n 2 | \x1b[1m\x1b[30m\x1b[0m\n 3 | \x1b[33mtasks\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m", want: "> 1 | \x1b[33mversion\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36m3\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^\n 2 | \x1b[1m\x1b[30m\x1b[0m\n 3 | \x1b[33mtasks\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m",
}, },
@ -163,9 +163,9 @@ func TestSnippetString(t *testing.T) {
name: "1st line, 10th column, padding=2", name: "1st line, 10th column, padding=2",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(1), WithLine(1),
SnippetWithColumn(10), WithColumn(10),
SnippetWithPadding(2), WithPadding(2),
}, },
want: "> 1 | \x1b[33mversion\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36m3\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^\n 2 | \x1b[1m\x1b[30m\x1b[0m\n 3 | \x1b[33mtasks\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m", want: "> 1 | \x1b[33mversion\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36m3\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^\n 2 | \x1b[1m\x1b[30m\x1b[0m\n 3 | \x1b[33mtasks\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m",
}, },
@ -173,8 +173,8 @@ func TestSnippetString(t *testing.T) {
name: "5th line, 1st column", name: "5th line, 1st column",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(5), WithLine(5),
SnippetWithColumn(1), WithColumn(1),
}, },
want: "> 5 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mvars\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^", want: "> 5 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mvars\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^",
}, },
@ -182,8 +182,8 @@ func TestSnippetString(t *testing.T) {
name: "5th line, 5th column", name: "5th line, 5th column",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(5), WithLine(5),
SnippetWithColumn(5), WithColumn(5),
}, },
want: "> 5 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mvars\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^", want: "> 5 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mvars\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^",
}, },
@ -191,9 +191,9 @@ func TestSnippetString(t *testing.T) {
name: "5th line, 5th column, padding=2", name: "5th line, 5th column, padding=2",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(5), WithLine(5),
SnippetWithColumn(5), WithColumn(5),
SnippetWithPadding(2), WithPadding(2),
}, },
want: " 3 | \x1b[33mtasks\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 4 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mdefault\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n> 5 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mvars\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^\n 6 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mFOO\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36mfoo\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 7 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mBAR\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36mbar\x1b[0m\x1b[1m\x1b[30m\x1b[0m", want: " 3 | \x1b[33mtasks\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 4 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mdefault\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n> 5 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mvars\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^\n 6 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mFOO\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36mfoo\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 7 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mBAR\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36mbar\x1b[0m\x1b[1m\x1b[30m\x1b[0m",
}, },
@ -201,10 +201,10 @@ func TestSnippetString(t *testing.T) {
name: "5th line, 5th column, padding=2, no indicators", name: "5th line, 5th column, padding=2, no indicators",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(5), WithLine(5),
SnippetWithColumn(5), WithColumn(5),
SnippetWithPadding(2), WithPadding(2),
SnippetWithNoIndicators(), WithNoIndicators(),
}, },
want: " 3 | \x1b[33mtasks\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 4 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mdefault\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 5 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mvars\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 6 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mFOO\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36mfoo\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 7 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mBAR\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36mbar\x1b[0m\x1b[1m\x1b[30m\x1b[0m", want: " 3 | \x1b[33mtasks\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 4 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mdefault\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 5 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mvars\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 6 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mFOO\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36mfoo\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 7 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mBAR\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36mbar\x1b[0m\x1b[1m\x1b[30m\x1b[0m",
}, },
@ -212,8 +212,8 @@ func TestSnippetString(t *testing.T) {
name: "10th line, 1st column", name: "10th line, 1st column",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(10), WithLine(10),
SnippetWithColumn(1), WithColumn(1),
}, },
want: "> 10 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.BAR}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^", want: "> 10 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.BAR}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^",
}, },
@ -221,8 +221,8 @@ func TestSnippetString(t *testing.T) {
name: "10th line, 23rd column", name: "10th line, 23rd column",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(10), WithLine(10),
SnippetWithColumn(23), WithColumn(23),
}, },
want: "> 10 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.BAR}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^", want: "> 10 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.BAR}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^",
}, },
@ -230,8 +230,8 @@ func TestSnippetString(t *testing.T) {
name: "10th line, 24th column (out of bounds)", name: "10th line, 24th column (out of bounds)",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(10), WithLine(10),
SnippetWithColumn(24), WithColumn(24),
}, },
want: "> 10 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.BAR}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m", want: "> 10 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.BAR}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m",
}, },
@ -239,9 +239,9 @@ func TestSnippetString(t *testing.T) {
name: "10th line, 23rd column, padding=2", name: "10th line, 23rd column, padding=2",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(10), WithLine(10),
SnippetWithColumn(23), WithColumn(23),
SnippetWithPadding(2), WithPadding(2),
}, },
want: " 8 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mcmds\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 9 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.FOO}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n> 10 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.BAR}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^", want: " 8 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mcmds\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 9 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.FOO}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n> 10 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.BAR}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^",
}, },
@ -249,9 +249,9 @@ func TestSnippetString(t *testing.T) {
name: "5th line, 5th column, padding=100", name: "5th line, 5th column, padding=100",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(5), WithLine(5),
SnippetWithColumn(5), WithColumn(5),
SnippetWithPadding(100), WithPadding(100),
}, },
want: " 1 | \x1b[33mversion\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36m3\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 2 | \x1b[1m\x1b[30m\x1b[0m\n 3 | \x1b[33mtasks\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 4 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mdefault\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n> 5 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mvars\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^\n 6 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mFOO\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36mfoo\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 7 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mBAR\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36mbar\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 8 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mcmds\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 9 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.FOO}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 10 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.BAR}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m", want: " 1 | \x1b[33mversion\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36m3\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 2 | \x1b[1m\x1b[30m\x1b[0m\n 3 | \x1b[33mtasks\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 4 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mdefault\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n> 5 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mvars\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n | ^\n 6 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mFOO\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36mfoo\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 7 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mBAR\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m \x1b[0m\x1b[36mbar\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 8 | \x1b[1m\x1b[30m \x1b[0m\x1b[33mcmds\x1b[0m\x1b[1m\x1b[30m:\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 9 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.FOO}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 10 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.BAR}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m",
}, },
@ -259,8 +259,8 @@ func TestSnippetString(t *testing.T) {
name: "11th line (out of bounds), 1st column", name: "11th line (out of bounds), 1st column",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(11), WithLine(11),
SnippetWithColumn(1), WithColumn(1),
}, },
want: "", want: "",
}, },
@ -268,9 +268,9 @@ func TestSnippetString(t *testing.T) {
name: "11th line (out of bounds), 1st column, padding=2", name: "11th line (out of bounds), 1st column, padding=2",
b: []byte(sample), b: []byte(sample),
opts: []SnippetOption{ opts: []SnippetOption{
SnippetWithLine(11), WithLine(11),
SnippetWithColumn(1), WithColumn(1),
SnippetWithPadding(2), WithPadding(2),
}, },
want: " 9 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.FOO}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 10 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.BAR}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m", want: " 9 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.FOO}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m\n 10 | \x1b[1m\x1b[30m \x1b[0m\x1b[1m\x1b[30m- \x1b[0m\x1b[36mecho \"{{.BAR}}\"\x1b[0m\x1b[1m\x1b[30m\x1b[0m",
}, },