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

feat: remove logger from taskfile package (#2082)

* refactor: remove logger from the taskfile node interface

* refactor: functional options on taskfile.Reader

* feat: use pass in debug/prompt functions to Reader rather than task Logger

* chore: reader docstrings

* fix: typo
This commit is contained in:
Pete Davison 2025-02-22 16:00:37 +00:00 committed by GitHub
parent cbde4c33f8
commit 4d15a8be8f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 149 additions and 66 deletions

View File

@ -56,7 +56,7 @@ func (e *Executor) Setup() error {
} }
func (e *Executor) getRootNode() (taskfile.Node, error) { func (e *Executor) getRootNode() (taskfile.Node, error) {
node, err := taskfile.NewRootNode(e.Logger, e.Entrypoint, e.Dir, e.Insecure, e.Timeout) node, err := taskfile.NewRootNode(e.Entrypoint, e.Dir, e.Insecure, e.Timeout)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -65,14 +65,21 @@ func (e *Executor) getRootNode() (taskfile.Node, error) {
} }
func (e *Executor) readTaskfile(node taskfile.Node) error { func (e *Executor) readTaskfile(node taskfile.Node) error {
debugFunc := func(s string) {
e.Logger.VerboseOutf(logger.Magenta, s)
}
promptFunc := func(s string) error {
return e.Logger.Prompt(logger.Yellow, s, "n", "y", "yes")
}
reader := taskfile.NewReader( reader := taskfile.NewReader(
node, node,
e.Insecure, taskfile.WithInsecure(e.Insecure),
e.Download, taskfile.WithDownload(e.Download),
e.Offline, taskfile.WithOffline(e.Offline),
e.Timeout, taskfile.WithTimeout(e.Timeout),
e.TempDir.Remote, taskfile.WithTempDir(e.TempDir.Remote),
e.Logger, taskfile.WithDebugFunc(debugFunc),
taskfile.WithPromptFunc(promptFunc),
) )
graph, err := reader.Read() graph, err := reader.Read()
if err != nil { if err != nil {

View File

@ -11,7 +11,6 @@ import (
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/experiments" "github.com/go-task/task/v3/internal/experiments"
"github.com/go-task/task/v3/internal/logger"
) )
type Node interface { type Node interface {
@ -26,7 +25,6 @@ type Node interface {
} }
func NewRootNode( func NewRootNode(
l *logger.Logger,
entrypoint string, entrypoint string,
dir string, dir string,
insecure bool, insecure bool,
@ -37,11 +35,10 @@ func NewRootNode(
if entrypoint == "-" { if entrypoint == "-" {
return NewStdinNode(dir) return NewStdinNode(dir)
} }
return NewNode(l, entrypoint, dir, insecure, timeout) return NewNode(entrypoint, dir, insecure, timeout)
} }
func NewNode( func NewNode(
l *logger.Logger,
entrypoint string, entrypoint string,
dir string, dir string,
insecure bool, insecure bool,
@ -58,9 +55,9 @@ func NewNode(
case "git": case "git":
node, err = NewGitNode(entrypoint, dir, insecure, opts...) node, err = NewGitNode(entrypoint, dir, insecure, opts...)
case "http", "https": case "http", "https":
node, err = NewHTTPNode(l, entrypoint, dir, insecure, timeout, opts...) node, err = NewHTTPNode(entrypoint, dir, insecure, timeout, opts...)
default: default:
node, err = NewFileNode(l, entrypoint, dir, opts...) node, err = NewFileNode(entrypoint, dir, opts...)
} }

View File

@ -9,7 +9,6 @@ import (
"github.com/go-task/task/v3/internal/execext" "github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/logger"
) )
// A FileNode is a node that reads a taskfile from the local filesystem. // A FileNode is a node that reads a taskfile from the local filesystem.
@ -18,10 +17,10 @@ type FileNode struct {
Entrypoint string Entrypoint string
} }
func NewFileNode(l *logger.Logger, entrypoint, dir string, opts ...NodeOption) (*FileNode, error) { func NewFileNode(entrypoint, dir string, opts ...NodeOption) (*FileNode, error) {
var err error var err error
base := NewBaseNode(dir, opts...) base := NewBaseNode(dir, opts...)
entrypoint, base.dir, err = resolveFileNodeEntrypointAndDir(l, entrypoint, base.dir) entrypoint, base.dir, err = resolveFileNodeEntrypointAndDir(entrypoint, base.dir)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -50,10 +49,10 @@ func (node *FileNode) Read(ctx context.Context) ([]byte, error) {
// resolveFileNodeEntrypointAndDir resolves checks the values of entrypoint and dir and // resolveFileNodeEntrypointAndDir resolves checks the values of entrypoint and dir and
// populates them with default values if necessary. // populates them with default values if necessary.
func resolveFileNodeEntrypointAndDir(l *logger.Logger, entrypoint, dir string) (string, string, error) { func resolveFileNodeEntrypointAndDir(entrypoint, dir string) (string, string, error) {
var err error var err error
if entrypoint != "" { if entrypoint != "" {
entrypoint, err = Exists(l, entrypoint) entrypoint, err = Exists(entrypoint)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
@ -68,7 +67,7 @@ func resolveFileNodeEntrypointAndDir(l *logger.Logger, entrypoint, dir string) (
return "", "", err return "", "", err
} }
} }
entrypoint, err = ExistsWalk(l, dir) entrypoint, err = ExistsWalk(dir)
if err != nil { if err != nil {
return "", "", err return "", "", err
} }

View File

@ -11,7 +11,6 @@ import (
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/execext" "github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/logger"
) )
// An HTTPNode is a node that reads a Taskfile from a remote location via HTTP. // An HTTPNode is a node that reads a Taskfile from a remote location via HTTP.
@ -19,12 +18,10 @@ type HTTPNode struct {
*BaseNode *BaseNode
URL *url.URL // stores url pointing actual remote file. (e.g. with Taskfile.yml) URL *url.URL // stores url pointing actual remote file. (e.g. with Taskfile.yml)
entrypoint string // stores entrypoint url. used for building graph vertices. entrypoint string // stores entrypoint url. used for building graph vertices.
logger *logger.Logger
timeout time.Duration timeout time.Duration
} }
func NewHTTPNode( func NewHTTPNode(
l *logger.Logger,
entrypoint string, entrypoint string,
dir string, dir string,
insecure bool, insecure bool,
@ -45,7 +42,6 @@ func NewHTTPNode(
URL: url, URL: url,
entrypoint: entrypoint, entrypoint: entrypoint,
timeout: timeout, timeout: timeout,
logger: l,
}, nil }, nil
} }
@ -58,7 +54,7 @@ func (node *HTTPNode) Remote() bool {
} }
func (node *HTTPNode) Read(ctx context.Context) ([]byte, error) { func (node *HTTPNode) Read(ctx context.Context) ([]byte, error) {
url, err := RemoteExists(ctx, node.logger, node.URL, node.timeout) url, err := RemoteExists(ctx, node.URL, node.timeout)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -14,7 +14,6 @@ import (
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/compiler" "github.com/go-task/task/v3/internal/compiler"
"github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/templater" "github.com/go-task/task/v3/internal/templater"
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
) )
@ -28,40 +27,115 @@ Continue?`
Continue?` Continue?`
) )
// A Reader will recursively read Taskfiles from a given source using a directed type (
// acyclic graph (DAG). // ReaderDebugFunc is a function that is called when the reader wants to
type Reader struct { // log debug messages
graph *ast.TaskfileGraph ReaderDebugFunc func(string)
node Node // ReaderPromptFunc is a function that is called when the reader wants to
insecure bool // prompt the user in some way
download bool ReaderPromptFunc func(string) error
offline bool // ReaderOption is a function that configures a Reader.
timeout time.Duration ReaderOption func(*Reader)
tempDir string // A Reader will recursively read Taskfiles from a given source using a directed
logger *logger.Logger // acyclic graph (DAG).
promptMutex sync.Mutex Reader struct {
} graph *ast.TaskfileGraph
node Node
insecure bool
download bool
offline bool
timeout time.Duration
tempDir string
debugFunc ReaderDebugFunc
promptFunc ReaderPromptFunc
promptMutex sync.Mutex
}
)
// NewReader constructs a new Taskfile Reader using the given Node and options.
func NewReader( func NewReader(
node Node, node Node,
insecure bool, opts ...ReaderOption,
download bool,
offline bool,
timeout time.Duration,
tempDir string,
logger *logger.Logger,
) *Reader { ) *Reader {
return &Reader{ reader := &Reader{
graph: ast.NewTaskfileGraph(), graph: ast.NewTaskfileGraph(),
node: node, node: node,
insecure: insecure, insecure: false,
download: download, download: false,
offline: offline, offline: false,
timeout: timeout, timeout: time.Second * 10,
tempDir: tempDir, tempDir: os.TempDir(),
logger: logger, debugFunc: nil,
promptFunc: nil,
promptMutex: sync.Mutex{}, promptMutex: sync.Mutex{},
} }
for _, opt := range opts {
opt(reader)
}
return reader
}
// WithInsecure enables insecure connections when reading remote taskfiles. By
// default, insecure connections are rejected.
func WithInsecure(insecure bool) ReaderOption {
return func(r *Reader) {
r.insecure = insecure
}
}
// WithDownload forces the reader to download a fresh copy of the taskfile from
// the remote source.
func WithDownload(download bool) ReaderOption {
return func(r *Reader) {
r.download = download
}
}
// WithOffline stops the reader from being able to make network connections.
// It will still be able to read local files and cached copies of remote files.
func WithOffline(offline bool) ReaderOption {
return func(r *Reader) {
r.offline = offline
}
}
// WithTimeout sets the timeout for reading remote taskfiles. By default, the
// timeout is set to 10 seconds.
func WithTimeout(timeout time.Duration) ReaderOption {
return func(r *Reader) {
r.timeout = timeout
}
}
// WithTempDir sets the temporary directory to be used by the reader. By
// default, the reader uses `os.TempDir()`.
func WithTempDir(tempDir string) ReaderOption {
return func(r *Reader) {
r.tempDir = 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 ReaderDebugFunc) ReaderOption {
return func(r *Reader) {
r.debugFunc = 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
// accepted and the execution should continue. Otherwise, it should return an
// error which describes why the the prompt was rejected. This can then be
// caught and used later when calling the Read method. By default, no prompt
// function is set and all prompts are automatically accepted.
func WithPromptFunc(promptFunc ReaderPromptFunc) ReaderOption {
return func(r *Reader) {
r.promptFunc = promptFunc
}
} }
func (r *Reader) Read() (*ast.TaskfileGraph, error) { func (r *Reader) Read() (*ast.TaskfileGraph, error) {
@ -73,6 +147,19 @@ func (r *Reader) Read() (*ast.TaskfileGraph, error) {
return r.graph, nil return r.graph, nil
} }
func (r *Reader) debugf(format string, a ...any) {
if r.debugFunc != nil {
r.debugFunc(fmt.Sprintf(format, a...))
}
}
func (r *Reader) promptf(format string, a ...any) error {
if r.promptFunc != nil {
return r.promptFunc(fmt.Sprintf(format, a...))
}
return nil
}
func (r *Reader) include(node Node) error { func (r *Reader) include(node Node) error {
// Create a new vertex for the Taskfile // Create a new vertex for the Taskfile
vertex := &ast.TaskfileVertex{ vertex := &ast.TaskfileVertex{
@ -132,7 +219,7 @@ func (r *Reader) include(node Node) error {
return err return err
} }
includeNode, err := NewNode(r.logger, entrypoint, include.Dir, r.insecure, r.timeout, includeNode, err := NewNode(entrypoint, include.Dir, r.insecure, r.timeout,
WithParent(node), WithParent(node),
) )
if err != nil { if err != nil {
@ -246,7 +333,7 @@ func (r *Reader) loadNodeContent(node Node) ([]byte, error) {
} else if err != nil { } else if err != nil {
return nil, err return nil, err
} }
r.logger.VerboseOutf(logger.Magenta, "task: [%s] Fetched cached copy\n", node.Location()) r.debugf("task: [%s] Fetched cached copy\n", node.Location())
return cached, nil return cached, nil
} }
@ -270,14 +357,14 @@ func (r *Reader) loadNodeContent(node Node) ([]byte, error) {
} else if err != nil { } else if err != nil {
return nil, err return nil, err
} }
r.logger.VerboseOutf(logger.Magenta, "task: [%s] Network timeout. Fetched cached copy\n", node.Location()) r.debugf("task: [%s] Network timeout. Fetched cached copy\n", node.Location())
return cached, nil return cached, nil
} else if err != nil { } else if err != nil {
return nil, err return nil, err
} }
r.logger.VerboseOutf(logger.Magenta, "task: [%s] Fetched remote copy\n", node.Location()) r.debugf("task: [%s] Fetched remote copy\n", node.Location())
// Get the checksums // Get the checksums
checksum := checksum(b) checksum := checksum(b)
@ -286,17 +373,17 @@ func (r *Reader) loadNodeContent(node Node) ([]byte, error) {
var prompt string var prompt string
if cachedChecksum == "" { if cachedChecksum == "" {
// If the checksum doesn't exist, prompt the user to continue // If the checksum doesn't exist, prompt the user to continue
prompt = fmt.Sprintf(taskfileUntrustedPrompt, node.Location()) prompt = taskfileUntrustedPrompt
} else if checksum != cachedChecksum { } else if checksum != cachedChecksum {
// If there is a cached hash, but it doesn't match the expected hash, prompt the user to continue // If there is a cached hash, but it doesn't match the expected hash, prompt the user to continue
prompt = fmt.Sprintf(taskfileChangedPrompt, node.Location()) prompt = taskfileChangedPrompt
} }
if prompt != "" { if prompt != "" {
if err := func() error { if err := func() error {
r.promptMutex.Lock() r.promptMutex.Lock()
defer r.promptMutex.Unlock() defer r.promptMutex.Unlock()
return r.logger.Prompt(logger.Yellow, prompt, "n", "y", "yes") return r.promptf(prompt, node.Location())
}(); err != nil { }(); err != nil {
return nil, &errors.TaskfileNotTrustedError{URI: node.Location()} return nil, &errors.TaskfileNotTrustedError{URI: node.Location()}
} }
@ -307,7 +394,7 @@ func (r *Reader) loadNodeContent(node Node) ([]byte, error) {
} }
// Cache the file // Cache the file
r.logger.VerboseOutf(logger.Magenta, "task: [%s] Caching downloaded file\n", node.Location()) r.debugf("task: [%s] Caching downloaded file\n", node.Location())
if err = cache.write(node, b); err != nil { if err = cache.write(node, b); err != nil {
return nil, err return nil, err
} }

View File

@ -12,7 +12,6 @@ import (
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/sysinfo" "github.com/go-task/task/v3/internal/sysinfo"
) )
@ -41,7 +40,7 @@ var (
// at the given URL with any of the default Taskfile files names. If any of // at the given URL with any of the default Taskfile files names. If any of
// these match a file, the first matching path will be returned. If no files are // these match a file, the first matching path will be returned. If no files are
// found, an error will be returned. // found, an error will be returned.
func RemoteExists(ctx context.Context, l *logger.Logger, u *url.URL, timeout time.Duration) (*url.URL, error) { func RemoteExists(ctx context.Context, u *url.URL, timeout time.Duration) (*url.URL, error) {
// Create a new HEAD request for the given URL to check if the resource exists // Create a new HEAD request for the given URL to check if the resource exists
req, err := http.NewRequestWithContext(ctx, "HEAD", u.String(), nil) req, err := http.NewRequestWithContext(ctx, "HEAD", u.String(), nil)
if err != nil { if err != nil {
@ -89,7 +88,6 @@ func RemoteExists(ctx context.Context, l *logger.Logger, u *url.URL, timeout tim
// If the request was successful, return the URL // If the request was successful, return the URL
if resp.StatusCode == http.StatusOK { if resp.StatusCode == http.StatusOK {
l.VerboseOutf(logger.Magenta, "task: [%s] Not found - Using alternative (%s)\n", alt.String(), taskfile)
return alt, nil return alt, nil
} }
} }
@ -102,7 +100,7 @@ func RemoteExists(ctx context.Context, l *logger.Logger, u *url.URL, timeout tim
// given path with any of the default Taskfile files names. If any of these // given path with any of the default Taskfile files names. If any of these
// match a file, the first matching path will be returned. If no files are // match a file, the first matching path will be returned. If no files are
// found, an error will be returned. // found, an error will be returned.
func Exists(l *logger.Logger, path string) (string, error) { func Exists(path string) (string, error) {
fi, err := os.Stat(path) fi, err := os.Stat(path)
if err != nil { if err != nil {
return "", err return "", err
@ -117,7 +115,6 @@ func Exists(l *logger.Logger, path string) (string, error) {
for _, taskfile := range defaultTaskfiles { for _, taskfile := range defaultTaskfiles {
alt := filepathext.SmartJoin(path, taskfile) alt := filepathext.SmartJoin(path, taskfile)
if _, err := os.Stat(alt); err == nil { if _, err := os.Stat(alt); err == nil {
l.VerboseOutf(logger.Magenta, "task: [%s] Not found - Using alternative (%s)\n", path, taskfile)
return filepath.Abs(alt) return filepath.Abs(alt)
} }
} }
@ -130,14 +127,14 @@ func Exists(l *logger.Logger, path string) (string, error) {
// calling the exists function until it finds a file or reaches the root // calling the exists function until it finds a file or reaches the root
// directory. On supported operating systems, it will also check if the user ID // directory. On supported operating systems, it will also check if the user ID
// of the directory changes and abort if it does. // of the directory changes and abort if it does.
func ExistsWalk(l *logger.Logger, path string) (string, error) { func ExistsWalk(path string) (string, error) {
origPath := path origPath := path
owner, err := sysinfo.Owner(path) owner, err := sysinfo.Owner(path)
if err != nil { if err != nil {
return "", err return "", err
} }
for { for {
fpath, err := Exists(l, path) fpath, err := Exists(path)
if err == nil { if err == nil {
return fpath, nil return fpath, nil
} }