这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion completion/fish/task.fish
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ complete -c $GO_TASK_PROGNAME -s p -l parallel -d 'executes tasks provided on c
complete -c $GO_TASK_PROGNAME -s s -l silent -d 'disables echoing'
complete -c $GO_TASK_PROGNAME -l status -d 'exits with non-zero exit code if any of the given tasks is not up-to-date'
complete -c $GO_TASK_PROGNAME -l summary -d 'show summary about a task'
complete -c $GO_TASK_PROGNAME -s t -l taskfile -d 'choose which Taskfile to run. Defaults to "Taskfile.yml"'
complete -c $GO_TASK_PROGNAME -s t -l taskfile -d 'choose which Taskfile to run. Defaults to "Taskfile.yml". Also searches for "Taskfile.yaml", "Taskfile.hcl", and "Taskfile"'
complete -c $GO_TASK_PROGNAME -s v -l verbose -d 'enables verbose mode'
complete -c $GO_TASK_PROGNAME -l version -d 'show Task version'
complete -c $GO_TASK_PROGNAME -s w -l watch -d 'enables watch of the given task'
4 changes: 2 additions & 2 deletions completion/zsh/_task
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ typeset -A opt_args

_GO_TASK_COMPLETION_LIST_OPTION="${GO_TASK_COMPLETION_LIST_OPTION:---list-all}"

# Listing commands from Taskfile.yml
# Listing commands from Taskfile
function __task_list() {
local -a scripts cmd
local -i enabled=0
Expand All @@ -19,7 +19,7 @@ function __task_list() {
enabled=1
cmd+=(--taskfile "$taskfile")
else
for taskfile in {T,t}askfile{,.dist}.{yaml,yml}; do
for taskfile in Taskfile.yml taskfile.yml Taskfile.yaml taskfile.yaml Taskfile.dist.yml taskfile.dist.yml Taskfile.dist.yaml taskfile.dist.yaml Taskfile.hcl taskfile.hcl Taskfile taskfile; do
if [[ -f "$taskfile" ]]; then
enabled=1
break
Expand Down
4 changes: 2 additions & 2 deletions internal/flags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,15 +128,15 @@ func init() {
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.")
pflag.StringVarP(&Dir, "dir", "d", "", "Sets the directory in which Task will execute and look for a Taskfile.")
pflag.StringVarP(&Entrypoint, "taskfile", "t", "", `Choose which Taskfile to run. Defaults to "Taskfile.yml".`)
pflag.StringVarP(&Entrypoint, "taskfile", "t", "", `Choose which Taskfile to run. Defaults to "Taskfile.yml". Also searches for "Taskfile.yaml", "Taskfile.hcl", and "Taskfile".`)
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.")
pflag.IntVarP(&Concurrency, "concurrency", "C", 0, "Limit number of tasks to run concurrently.")
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.BoolVarP(&Global, "global", "g", false, "Runs global Taskfile, from $HOME/{T,t}askfile.{yml,yaml,hcl} or $HOME/{T,t}askfile.")
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
Expand Down
62 changes: 62 additions & 0 deletions taskfile/discovery_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package taskfile

import (
"context"
"os"
"path/filepath"
"strings"
"testing"
)

func writeFile(t *testing.T, dir, name, content string) string {
t.Helper()
path := filepath.Join(dir, name)
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
t.Fatalf("failed to write %s: %v", name, err)
}
return path
}

func TestDiscoveryPrefersYAMLOverHCL(t *testing.T) {
dir := t.TempDir()
yamlPath := writeFile(t, dir, "Taskfile.yml", "version: '3'\n")
writeFile(t, dir, "Taskfile.hcl", "version = 3\n")

node, err := NewFileNode("", dir)
if err != nil {
t.Fatalf("NewFileNode returned error: %v", err)
}
if node.Location() != yamlPath {
t.Fatalf("expected %s, got %s", yamlPath, node.Location())
}
}

func TestHCLLoaderInvoked(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "Taskfile.hcl", "version = 3\n")

node, err := NewFileNode("", dir)
if err != nil {
t.Fatalf("NewFileNode returned error: %v", err)
}
r := NewReader()
_, err = r.Read(context.Background(), node)
if err == nil || !strings.Contains(err.Error(), "HCL parsing not implemented") {
t.Fatalf("expected HCL parsing not implemented error, got %v", err)
}
}

func TestExtensionlessTaskfile(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, "Taskfile", "version = 3\n")

node, err := NewFileNode("", dir)
if err != nil {
t.Fatalf("NewFileNode returned error: %v", err)
}
r := NewReader()
_, err = r.Read(context.Background(), node)
if err == nil || !strings.Contains(err.Error(), "HCL parsing not implemented") {
t.Fatalf("expected HCL parsing not implemented error, got %v", err)
}
}
24 changes: 18 additions & 6 deletions taskfile/loader.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package taskfile

import (
stdErrors "errors"
stdErrors "errors"

"gopkg.in/yaml.v3"
"gopkg.in/yaml.v3"

"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/taskfile/ast"
"github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/taskfile/ast"
)

// Loader defines the behavior required to load a Taskfile from raw data.
Expand All @@ -18,7 +18,7 @@ import (
// YAML-only helpers. Future work will extract those bindings out of the ast
// package so it becomes truly format agnostic.
type Loader interface {
Load(data []byte, location string) (*ast.Taskfile, error)
Load(data []byte, location string) (*ast.Taskfile, error)
}

// YAMLLoader implements [Loader] using YAML as the configuration format.
Expand All @@ -41,3 +41,15 @@ func (YAMLLoader) Load(data []byte, location string) (*ast.Taskfile, error) {
}
return &tf, nil
}

// HCLLoader implements [Loader] using HCL as the configuration format.
//
// Note: HCL parsing is not yet implemented. This loader currently returns a
// descriptive error so that discovery logic can recognize HCL Taskfiles without
// breaking program flow.
type HCLLoader struct{}

// Load returns a not implemented error until HCL support is completed.
func (HCLLoader) Load(data []byte, location string) (*ast.Taskfile, error) {
return nil, errors.New("HCL parsing not implemented")
}
9 changes: 7 additions & 2 deletions taskfile/node_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import (
// An HTTPNode is a node that reads a Taskfile from a remote location via HTTP.
type HTTPNode struct {
*baseNode
url *url.URL // stores url pointing actual remote file. (e.g. with Taskfile.yml)
url *url.URL // original URL provided by the user
resolved *url.URL // resolved URL pointing to the actual remote file
}

func NewHTTPNode(
Expand All @@ -41,6 +42,9 @@ func NewHTTPNode(
}

func (node *HTTPNode) Location() string {
if node.resolved != nil {
return node.resolved.Redacted()
}
return node.url.Redacted()
}

Expand All @@ -53,6 +57,7 @@ func (node *HTTPNode) ReadContext(ctx context.Context) ([]byte, error) {
if err != nil {
return nil, err
}
node.resolved = url
req, err := http.NewRequest("GET", url.String(), nil)
if err != nil {
return nil, errors.TaskfileFetchFailedError{URI: node.Location()}
Expand Down Expand Up @@ -111,7 +116,7 @@ func (node *HTTPNode) ResolveDir(dir string) (string, error) {
}

func (node *HTTPNode) CacheKey() string {
checksum := strings.TrimRight(checksum([]byte(node.Location())), "=")
checksum := strings.TrimRight(checksum([]byte(node.url.Redacted())), "=")
dir, filename := filepath.Split(node.url.Path)
lastDir := filepath.Base(dir)
prefix := filename
Expand Down
25 changes: 13 additions & 12 deletions taskfile/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ func NewReader(opts ...ReaderOption) *Reader {
loaders: map[string]Loader{
".yml": YAMLLoader{},
".yaml": YAMLLoader{},
".hcl": HCLLoader{},
"": HCLLoader{},
},
insecure: false,
download: false,
Expand Down Expand Up @@ -230,28 +232,27 @@ func (r *Reader) promptf(format string, a ...any) error {
}

func (r *Reader) include(ctx context.Context, node Node) error {
// Create a new vertex for the Taskfile
// Read and parse the Taskfile from the file
tf, err := r.readNode(ctx, node)
if err != nil {
return err
}

// Create a new vertex for the Taskfile using the resolved location
vertex := &ast.TaskfileVertex{
URI: node.Location(),
Taskfile: nil,
Taskfile: tf,
}

// Add the included Taskfile to the DAG
// If the vertex already exists, we return early since its Taskfile has
// already been read and its children explored
// Add the included Taskfile to the DAG. If the vertex already exists, we
// return early since its Taskfile has already been read and its children
// explored
if err := r.graph.AddVertex(vertex); err == graph.ErrVertexAlreadyExists {
return nil
} else if err != nil {
return err
}

// Read and parse the Taskfile from the file and add it to the vertex
var err error
vertex.Taskfile, err = r.readNode(ctx, node)
if err != nil {
return err
}

// Create an error group to wait for all included Taskfiles to be read
var g errgroup.Group

Expand Down
41 changes: 24 additions & 17 deletions taskfile/taskfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ var (
"taskfile.dist.yml",
"Taskfile.dist.yaml",
"taskfile.dist.yaml",
"Taskfile.hcl",
"taskfile.hcl",
"Taskfile",
"taskfile",
}
allowedContentTypes = []string{
"text/plain",
Expand All @@ -43,25 +47,28 @@ func RemoteExists(ctx context.Context, u url.URL) (*url.URL, error) {
return nil, errors.TaskfileFetchFailedError{URI: u.Redacted()}
}

// Request the given URL
resp, err := http.DefaultClient.Do(req)
if err != nil {
if ctx.Err() != nil {
return nil, fmt.Errorf("checking remote file: %w", ctx.Err())
var resp *http.Response
if !strings.HasSuffix(u.Path, "/") {
// Request the given URL
resp, err = http.DefaultClient.Do(req)
if err != nil {
if ctx.Err() != nil {
return nil, fmt.Errorf("checking remote file: %w", ctx.Err())
}
return nil, errors.TaskfileFetchFailedError{URI: u.Redacted()}
}
return nil, errors.TaskfileFetchFailedError{URI: u.Redacted()}
}
defer resp.Body.Close()
defer resp.Body.Close()

// If the request was successful and the content type is allowed, return the
// URL The content type check is to avoid downloading files that are not
// Taskfiles It means we can try other files instead of downloading
// something that is definitely not a Taskfile
contentType := resp.Header.Get("Content-Type")
if resp.StatusCode == http.StatusOK && slices.ContainsFunc(allowedContentTypes, func(s string) bool {
return strings.Contains(contentType, s)
}) {
return &u, nil
// If the request was successful and the content type is allowed, return the
// URL. The content type check is to avoid downloading files that are not
// Taskfiles. It means we can try other files instead of downloading
// something that is definitely not a Taskfile.
contentType := resp.Header.Get("Content-Type")
if resp.StatusCode == http.StatusOK && slices.ContainsFunc(allowedContentTypes, func(s string) bool {
return strings.Contains(contentType, s)
}) {
return &u, nil
}
}

// If the request was not successful, append the default Taskfile names to
Expand Down