mirror of
https://github.com/go-task/task.git
synced 2026-06-24 13:15:51 +00:00
Walk up from task dir to find .git instead of threading rootDir through Globs, checkers, and itemsFromFor. Gitignore rules are discarded if no .git is found, matching ripgrep's require_git behavior. This keeps the Globs signature clean and makes the future .taskignore integration straightforward (loaded at setup like .taskrc, separate boundary).
260 lines
6.8 KiB
Go
260 lines
6.8 KiB
Go
package ast
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"go.yaml.in/yaml/v3"
|
|
|
|
"github.com/go-task/task/v3/errors"
|
|
"github.com/go-task/task/v3/internal/deepcopy"
|
|
)
|
|
|
|
// Task represents a task
|
|
type Task struct {
|
|
Task string `hash:"ignore"`
|
|
Cmds []*Cmd
|
|
Deps []*Dep
|
|
Label string
|
|
Desc string
|
|
Prompt Prompt
|
|
Summary string
|
|
Requires *Requires
|
|
Aliases []string
|
|
Sources []*Glob
|
|
Generates []*Glob
|
|
Status []string
|
|
Preconditions []*Precondition
|
|
Dir string
|
|
Set []string
|
|
Shopt []string
|
|
Vars *Vars
|
|
Env *Vars
|
|
Dotenv []string
|
|
Silent *bool
|
|
Interactive bool
|
|
Internal bool
|
|
Method string
|
|
Prefix string `hash:"ignore"`
|
|
IgnoreError bool
|
|
Gitignore *bool
|
|
Run string
|
|
Platforms []*Platform
|
|
If string
|
|
Watch bool
|
|
Location *Location
|
|
Failfast bool
|
|
// Populated during merging
|
|
Namespace string `hash:"ignore"`
|
|
IncludeVars *Vars
|
|
IncludedTaskfileVars *Vars
|
|
|
|
FullName string `hash:"ignore"`
|
|
}
|
|
|
|
func (t *Task) Name() string {
|
|
if t.Label != "" {
|
|
return t.Label
|
|
}
|
|
if t.FullName != "" {
|
|
return t.FullName
|
|
}
|
|
return t.Task
|
|
}
|
|
|
|
func (t *Task) LocalName() string {
|
|
name := t.FullName
|
|
name = strings.TrimPrefix(name, t.Namespace)
|
|
name = strings.TrimPrefix(name, ":")
|
|
return name
|
|
}
|
|
|
|
// IsSilent returns true if the task has silent mode explicitly enabled.
|
|
// Returns false if Silent is nil (not set) or explicitly set to false.
|
|
func (t *Task) IsSilent() bool {
|
|
return t.Silent != nil && *t.Silent
|
|
}
|
|
|
|
// IsGitignore returns true if the task has gitignore filtering explicitly enabled.
|
|
// Returns false if Gitignore is nil (not set) or explicitly set to false.
|
|
func (t *Task) IsGitignore() bool {
|
|
return t.Gitignore != nil && *t.Gitignore
|
|
}
|
|
|
|
// WildcardMatch will check if the given string matches the name of the Task and returns any wildcard values.
|
|
func (t *Task) WildcardMatch(name string) (bool, []string) {
|
|
names := append([]string{t.Task}, t.Aliases...)
|
|
|
|
for _, taskName := range names {
|
|
regexStr := fmt.Sprintf("^%s$", strings.ReplaceAll(taskName, "*", "(.*)"))
|
|
regex := regexp.MustCompile(regexStr)
|
|
wildcards := regex.FindStringSubmatch(name)
|
|
|
|
if len(wildcards) == 0 {
|
|
continue
|
|
}
|
|
|
|
// Remove the first match, which is the full string
|
|
wildcards = wildcards[1:]
|
|
wildcardCount := strings.Count(taskName, "*")
|
|
|
|
if len(wildcards) != wildcardCount {
|
|
continue
|
|
}
|
|
|
|
return true, wildcards
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
func (t *Task) UnmarshalYAML(node *yaml.Node) error {
|
|
switch node.Kind {
|
|
|
|
// Shortcut syntax for a task with a single command
|
|
case yaml.ScalarNode:
|
|
var cmd Cmd
|
|
if err := node.Decode(&cmd); err != nil {
|
|
return errors.NewTaskfileDecodeError(err, node)
|
|
}
|
|
t.Cmds = append(t.Cmds, &cmd)
|
|
return nil
|
|
|
|
// Shortcut syntax for a simple task with a list of commands
|
|
case yaml.SequenceNode:
|
|
var cmds []*Cmd
|
|
if err := node.Decode(&cmds); err != nil {
|
|
return errors.NewTaskfileDecodeError(err, node)
|
|
}
|
|
t.Cmds = cmds
|
|
return nil
|
|
|
|
// Full task object
|
|
case yaml.MappingNode:
|
|
var task struct {
|
|
Cmds []*Cmd
|
|
Cmd *Cmd
|
|
Deps []*Dep
|
|
Label string
|
|
Desc string
|
|
Prompt Prompt
|
|
Summary string
|
|
Aliases []string
|
|
Sources []*Glob
|
|
Generates []*Glob
|
|
Status []string
|
|
Preconditions []*Precondition
|
|
Dir string
|
|
Set []string
|
|
Shopt []string
|
|
Vars *Vars
|
|
Env *Vars
|
|
Dotenv []string
|
|
Silent *bool `yaml:"silent,omitempty"`
|
|
Interactive bool
|
|
Internal bool
|
|
Method string
|
|
Prefix string
|
|
IgnoreError bool `yaml:"ignore_error"`
|
|
Gitignore *bool `yaml:"gitignore,omitempty"`
|
|
Run string
|
|
Platforms []*Platform
|
|
If string
|
|
Requires *Requires
|
|
Watch bool
|
|
Failfast bool
|
|
}
|
|
if err := node.Decode(&task); err != nil {
|
|
return errors.NewTaskfileDecodeError(err, node)
|
|
}
|
|
if task.Cmd != nil {
|
|
if task.Cmds != nil {
|
|
return errors.NewTaskfileDecodeError(nil, node).WithMessage("task cannot have both cmd and cmds")
|
|
}
|
|
t.Cmds = []*Cmd{task.Cmd}
|
|
} else {
|
|
t.Cmds = task.Cmds
|
|
}
|
|
t.Deps = task.Deps
|
|
t.Label = task.Label
|
|
t.Desc = task.Desc
|
|
t.Prompt = task.Prompt
|
|
t.Summary = task.Summary
|
|
t.Aliases = task.Aliases
|
|
t.Sources = task.Sources
|
|
t.Generates = task.Generates
|
|
t.Status = task.Status
|
|
t.Preconditions = task.Preconditions
|
|
t.Dir = task.Dir
|
|
t.Set = task.Set
|
|
t.Shopt = task.Shopt
|
|
t.Vars = task.Vars
|
|
t.Env = task.Env
|
|
t.Dotenv = task.Dotenv
|
|
t.Silent = deepcopy.Scalar(task.Silent)
|
|
t.Interactive = task.Interactive
|
|
t.Internal = task.Internal
|
|
t.Method = task.Method
|
|
t.Prefix = task.Prefix
|
|
t.IgnoreError = task.IgnoreError
|
|
t.Gitignore = deepcopy.Scalar(task.Gitignore)
|
|
t.Run = task.Run
|
|
t.Platforms = task.Platforms
|
|
t.If = task.If
|
|
t.Requires = task.Requires
|
|
t.Watch = task.Watch
|
|
t.Failfast = task.Failfast
|
|
return nil
|
|
}
|
|
|
|
return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("task")
|
|
}
|
|
|
|
// DeepCopy creates a new instance of Task and copies
|
|
// data by value from the source struct.
|
|
func (t *Task) DeepCopy() *Task {
|
|
if t == nil {
|
|
return nil
|
|
}
|
|
c := &Task{
|
|
Task: t.Task,
|
|
Cmds: deepcopy.Slice(t.Cmds),
|
|
Deps: deepcopy.Slice(t.Deps),
|
|
Label: t.Label,
|
|
Desc: t.Desc,
|
|
Prompt: t.Prompt,
|
|
Summary: t.Summary,
|
|
Aliases: deepcopy.Slice(t.Aliases),
|
|
Sources: deepcopy.Slice(t.Sources),
|
|
Generates: deepcopy.Slice(t.Generates),
|
|
Status: deepcopy.Slice(t.Status),
|
|
Preconditions: deepcopy.Slice(t.Preconditions),
|
|
Dir: t.Dir,
|
|
Set: deepcopy.Slice(t.Set),
|
|
Shopt: deepcopy.Slice(t.Shopt),
|
|
Vars: t.Vars.DeepCopy(),
|
|
Env: t.Env.DeepCopy(),
|
|
Dotenv: deepcopy.Slice(t.Dotenv),
|
|
Silent: deepcopy.Scalar(t.Silent),
|
|
Interactive: t.Interactive,
|
|
Internal: t.Internal,
|
|
Method: t.Method,
|
|
Prefix: t.Prefix,
|
|
IgnoreError: t.IgnoreError,
|
|
Gitignore: deepcopy.Scalar(t.Gitignore),
|
|
Run: t.Run,
|
|
IncludeVars: t.IncludeVars.DeepCopy(),
|
|
IncludedTaskfileVars: t.IncludedTaskfileVars.DeepCopy(),
|
|
Platforms: deepcopy.Slice(t.Platforms),
|
|
If: t.If,
|
|
Location: t.Location.DeepCopy(),
|
|
Requires: t.Requires.DeepCopy(),
|
|
Namespace: t.Namespace,
|
|
FullName: t.FullName,
|
|
Watch: t.Watch,
|
|
Failfast: t.Failfast,
|
|
}
|
|
return c
|
|
}
|