2024-02-28 18:12:19 -06:00
package flags
import (
2024-09-21 11:17:15 -04:00
"cmp"
2024-02-28 18:12:19 -06:00
"log"
2024-04-24 21:40:52 +01:00
"os"
2025-04-19 12:20:33 +01:00
"path/filepath"
2024-02-28 18:12:19 -06:00
"time"
"github.com/spf13/pflag"
2025-03-10 20:38:25 +00:00
"github.com/go-task/task/v3"
2024-11-30 17:00:58 +02:00
"github.com/go-task/task/v3/errors"
2025-04-21 13:55:24 -03:00
"github.com/go-task/task/v3/experiments"
2025-03-10 20:38:25 +00:00
"github.com/go-task/task/v3/internal/sort"
2024-02-28 18:12:19 -06:00
"github.com/go-task/task/v3/taskfile/ast"
2025-09-10 17:57:52 +02:00
"github.com/go-task/task/v3/taskrc"
taskrcast "github.com/go-task/task/v3/taskrc/ast"
2024-02-28 18:12:19 -06:00
)
const usage = ` Usage : task [ flags ... ] [ task ... ]
Runs the specified task ( s ) . Falls back to the "default" task if no task name
was specified , or lists all tasks if an unknown task name was specified .
Example : ' task hello ' with the following ' Taskfile . yml ' file will generate an
' output . txt ' file with the content "hello" .
'''
version : '3'
tasks :
hello :
cmds :
- echo "I am going to write a file named 'output.txt' now."
- echo "hello" > output . txt
generates :
- output . txt
'''
Options :
`
var (
2025-04-19 12:12:08 +01:00
Version bool
Help bool
Init bool
Completion string
List bool
ListAll bool
ListJson bool
TaskSort string
Status bool
NoStatus bool
2025-09-11 10:26:59 +01:00
Nested bool
2025-04-19 12:12:08 +01:00
Insecure bool
Force bool
ForceAll bool
Watch bool
Verbose bool
Silent bool
2025-11-23 21:12:25 +01:00
DisableFuzzy bool
2025-04-19 12:12:08 +01:00
AssumeYes bool
Dry bool
Summary bool
ExitCode bool
Parallel bool
Concurrency int
Dir string
Entrypoint string
Output ast . Output
Color bool
Interval time . Duration
Global bool
Experiments bool
Download bool
Offline bool
ClearCache bool
Timeout time . Duration
CacheExpiryDuration time . Duration
2024-02-28 18:12:19 -06:00
)
func init ( ) {
2025-04-19 12:20:33 +01:00
// Config files can enable experiments which alter the availability and/or
// behavior of some flags, so we need to parse the experiments before the
// flags. However, we need the --taskfile and --dir flags before we can
// parse the experiments as they can alter the location of the config files.
// Because of this circular dependency, we parse the flags twice. First, we
// get the --taskfile and --dir flags, then we parse the experiments, then
// we parse the flags again to get the full set. We use a flagset here so
// that we can parse a subset of flags without exiting on error.
var dir , entrypoint string
fs := pflag . NewFlagSet ( "experiments" , pflag . ContinueOnError )
fs . StringVarP ( & dir , "dir" , "d" , "" , "" )
fs . StringVarP ( & entrypoint , "taskfile" , "t" , "" , "" )
fs . Usage = func ( ) { }
_ = fs . Parse ( os . Args [ 1 : ] )
// Parse the experiments
dir = cmp . Or ( dir , filepath . Dir ( entrypoint ) )
2025-09-10 17:57:52 +02:00
config , _ := taskrc . GetConfig ( dir )
experiments . ParseWithConfig ( dir , config )
2025-04-19 12:20:33 +01:00
// Parse the rest of the flags
2024-04-24 21:40:52 +01:00
log . SetFlags ( 0 )
log . SetOutput ( os . Stderr )
2024-02-28 18:12:19 -06:00
pflag . Usage = func ( ) {
log . Print ( usage )
pflag . PrintDefaults ( )
}
2025-09-10 17:57:52 +02:00
2024-02-28 18:12:19 -06:00
pflag . BoolVar ( & Version , "version" , false , "Show Task version." )
pflag . BoolVarP ( & Help , "help" , "h" , false , "Shows Task usage." )
pflag . BoolVarP ( & Init , "init" , "i" , false , "Creates a new Taskfile.yml in the current folder." )
2024-09-02 20:21:53 +01:00
pflag . StringVar ( & Completion , "completion" , "" , "Generates shell completion script." )
2024-02-28 18:12:19 -06:00
pflag . BoolVarP ( & List , "list" , "l" , false , "Lists tasks with description of current Taskfile." )
pflag . BoolVarP ( & ListAll , "list-all" , "a" , false , "Lists tasks with or without a description." )
pflag . BoolVarP ( & ListJson , "json" , "j" , false , "Formats task list as JSON." )
pflag . StringVar ( & TaskSort , "sort" , "" , "Changes the order of the tasks when listed. [default|alphanumeric|none]." )
pflag . BoolVar ( & Status , "status" , false , "Exits with non-zero exit code if any of the given tasks is not up-to-date." )
pflag . BoolVar ( & NoStatus , "no-status" , false , "Ignore status when listing tasks as JSON" )
2025-09-11 10:26:59 +01:00
pflag . BoolVar ( & Nested , "nested" , false , "Nest namespaces when listing tasks as JSON" )
2025-09-11 10:02:51 -03:00
pflag . BoolVar ( & Insecure , "insecure" , getConfig ( config , func ( ) * bool { return config . Remote . Insecure } , false ) , "Forces Task to download Taskfiles over insecure connections." )
2024-02-28 18:12:19 -06:00
pflag . BoolVarP ( & Watch , "watch" , "w" , false , "Enables watch of the given task." )
2025-09-11 10:02:51 -03:00
pflag . BoolVarP ( & Verbose , "verbose" , "v" , getConfig ( config , func ( ) * bool { return config . Verbose } , false ) , "Enables verbose mode." )
2024-02-28 18:12:19 -06:00
pflag . BoolVarP ( & Silent , "silent" , "s" , false , "Disables echoing." )
2025-11-23 21:12:25 +01:00
pflag . BoolVar ( & DisableFuzzy , "disable-fuzzy" , getConfig ( config , func ( ) * bool { return config . DisableFuzzy } , false ) , "Disables fuzzy matching for task names." )
2024-02-28 18:12:19 -06:00
pflag . BoolVarP ( & AssumeYes , "yes" , "y" , false , "Assume \"yes\" as answer to all prompts." )
pflag . BoolVarP ( & Parallel , "parallel" , "p" , false , "Executes tasks provided on command line in parallel." )
pflag . BoolVarP ( & Dry , "dry" , "n" , false , "Compiles and prints tasks in the order that they would be run, without executing them." )
pflag . BoolVar ( & Summary , "summary" , false , "Show summary about a task." )
pflag . BoolVarP ( & ExitCode , "exit-code" , "x" , false , "Pass-through the exit code of the task command." )
2025-04-17 19:10:57 -04:00
pflag . StringVarP ( & Dir , "dir" , "d" , "" , "Sets the directory in which Task will execute and look for a Taskfile." )
2024-02-28 18:12:19 -06:00
pflag . StringVarP ( & Entrypoint , "taskfile" , "t" , "" , ` Choose which Taskfile to run. Defaults to "Taskfile.yml". ` )
pflag . StringVarP ( & Output . Name , "output" , "o" , "" , "Sets output style: [interleaved|group|prefixed]." )
pflag . StringVar ( & Output . Group . Begin , "output-group-begin" , "" , "Message template to print before a task's grouped output." )
pflag . StringVar ( & Output . Group . End , "output-group-end" , "" , "Message template to print after a task's grouped output." )
pflag . BoolVar ( & Output . Group . ErrorOnly , "output-group-error-only" , false , "Swallow output from successful tasks." )
pflag . BoolVarP ( & Color , "color" , "c" , true , "Colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable." )
2025-09-11 10:02:51 -03:00
pflag . IntVarP ( & Concurrency , "concurrency" , "C" , getConfig ( config , func ( ) * int { return config . Concurrency } , 0 ) , "Limit number of tasks to run concurrently." )
2024-02-28 18:12:19 -06:00
pflag . DurationVarP ( & Interval , "interval" , "I" , 0 , "Interval to watch for changes." )
pflag . BoolVarP ( & Global , "global" , "g" , false , "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml}." )
pflag . BoolVar ( & Experiments , "experiments" , false , "Lists all the available experiments and whether or not they are enabled." )
// Gentle force experiment will override the force flag and add a new force-all flag
2025-02-08 23:02:51 +00:00
if experiments . GentleForce . Enabled ( ) {
2024-02-28 18:12:19 -06:00
pflag . BoolVarP ( & Force , "force" , "f" , false , "Forces execution of the directly called task." )
pflag . BoolVar ( & ForceAll , "force-all" , false , "Forces execution of the called task and all its dependant tasks." )
} else {
pflag . BoolVarP ( & ForceAll , "force" , "f" , false , "Forces execution even when the task is up-to-date." )
}
// Remote Taskfiles experiment will adds the "download" and "offline" flags
2025-02-08 23:02:51 +00:00
if experiments . RemoteTaskfiles . Enabled ( ) {
2024-02-28 18:12:19 -06:00
pflag . BoolVar ( & Download , "download" , false , "Downloads a cached version of a remote Taskfile." )
2025-09-11 10:02:51 -03:00
pflag . BoolVar ( & Offline , "offline" , getConfig ( config , func ( ) * bool { return config . Remote . Offline } , false ) , "Forces Task to only use local or cached Taskfiles." )
pflag . DurationVar ( & Timeout , "timeout" , getConfig ( config , func ( ) * time . Duration { return config . Remote . Timeout } , time . Second * 10 ) , "Timeout for downloading remote Taskfiles." )
2024-06-28 18:42:16 +02:00
pflag . BoolVar ( & ClearCache , "clear-cache" , false , "Clear the remote cache." )
2025-09-16 19:35:12 +02:00
pflag . DurationVar ( & CacheExpiryDuration , "expiry" , getConfig ( config , func ( ) * time . Duration { return config . Remote . CacheExpiry } , 0 ) , "Expiry duration for cached remote Taskfiles." )
2024-02-28 18:12:19 -06:00
}
pflag . Parse ( )
}
func Validate ( ) error {
if Download && Offline {
return errors . New ( "task: You can't set both --download and --offline flags" )
}
2024-06-28 18:42:16 +02:00
if Download && ClearCache {
return errors . New ( "task: You can't set both --download and --clear-cache flags" )
}
2024-02-28 18:12:19 -06:00
if Global && Dir != "" {
2025-03-10 20:38:25 +00:00
return errors . New ( "task: You can't set both --global and --dir" )
2024-02-28 18:12:19 -06:00
}
if Output . Name != "group" {
if Output . Group . Begin != "" {
return errors . New ( "task: You can't set --output-group-begin without --output=group" )
}
if Output . Group . End != "" {
return errors . New ( "task: You can't set --output-group-end without --output=group" )
}
if Output . Group . ErrorOnly {
return errors . New ( "task: You can't set --output-group-error-only without --output=group" )
}
}
2025-03-10 20:38:25 +00:00
if List && ListAll {
return errors . New ( "task: cannot use --list and --list-all at the same time" )
}
if ListJson && ! List && ! ListAll {
return errors . New ( "task: --json only applies to --list or --list-all" )
}
if NoStatus && ! ListJson {
return errors . New ( "task: --no-status only applies to --json with --list or --list-all" )
}
2025-09-11 10:26:59 +01:00
if Nested && ! ListJson {
return errors . New ( "task: --nested only applies to --json with --list or --list-all" )
}
2024-02-28 18:12:19 -06:00
return nil
}
2025-03-10 20:38:25 +00:00
2025-03-31 21:49:00 +01:00
// WithFlags is a special internal functional option that is used to pass flags
// from the CLI into any constructor that accepts functional options.
func WithFlags ( ) task . ExecutorOption {
return & flagsOption { }
}
2025-03-10 20:38:25 +00:00
2025-03-31 21:49:00 +01:00
type flagsOption struct { }
2025-03-10 20:38:25 +00:00
2025-03-31 21:49:00 +01:00
func ( o * flagsOption ) ApplyToExecutor ( e * task . Executor ) {
// Set the sorter
var sorter sort . Sorter
switch TaskSort {
case "none" :
sorter = sort . NoSort
case "alphanumeric" :
sorter = sort . AlphaNumeric
2025-03-10 20:38:25 +00:00
}
2025-03-31 21:49:00 +01:00
// Change the directory to the user's home directory if the global flag is set
dir := Dir
if Global {
home , err := os . UserHomeDir ( )
if err == nil {
dir = home
}
}
e . Options (
task . WithDir ( dir ) ,
task . WithEntrypoint ( Entrypoint ) ,
task . WithForce ( Force ) ,
task . WithForceAll ( ForceAll ) ,
task . WithInsecure ( Insecure ) ,
task . WithDownload ( Download ) ,
task . WithOffline ( Offline ) ,
task . WithTimeout ( Timeout ) ,
2025-04-19 12:12:08 +01:00
task . WithCacheExpiryDuration ( CacheExpiryDuration ) ,
2025-03-31 21:49:00 +01:00
task . WithWatch ( Watch ) ,
task . WithVerbose ( Verbose ) ,
task . WithSilent ( Silent ) ,
2025-11-23 21:12:25 +01:00
task . WithDisableFuzzy ( DisableFuzzy ) ,
2025-03-31 21:49:00 +01:00
task . WithAssumeYes ( AssumeYes ) ,
task . WithDry ( Dry || Status ) ,
task . WithSummary ( Summary ) ,
task . WithParallel ( Parallel ) ,
task . WithColor ( Color ) ,
task . WithConcurrency ( Concurrency ) ,
task . WithInterval ( Interval ) ,
task . WithOutputStyle ( Output ) ,
task . WithTaskSorter ( sorter ) ,
task . WithVersionCheck ( true ) ,
)
2025-03-10 20:38:25 +00:00
}
2025-09-10 17:57:52 +02:00
// getConfig extracts a config value directly from a pointer field with a fallback default
2025-09-11 10:02:51 -03:00
func getConfig [ T any ] ( config * taskrcast . TaskRC , fieldFunc func ( ) * T , fallback T ) T {
2025-09-10 17:57:52 +02:00
if config == nil {
return fallback
}
2025-09-11 10:02:51 -03:00
field := fieldFunc ( )
2025-09-10 17:57:52 +02:00
if field != nil {
return * field
}
return fallback
}