From 0e23404d2380ad5243b3523b3bcb43db4ae5953c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrique=20Corr=C3=AAa?= <75134774+HeCorr@users.noreply.github.com> Date: Mon, 10 Feb 2025 08:22:49 -0300 Subject: [PATCH] feat: specify --init filename/path (#2018) * feat: specify init filename with --taskfile flag previously, it was not possible to specify which filename to use when initializing a new Taskfile as it was hardcoded as "Taskfile.yml". now the --taskfile flag specifies where to write the file to, and the first * contained in it will be replaced by "Taskfile", so `task -it *.yaml` will create a `Taskfile.yaml` file. * docs: update CLI reference * fix Flags header being inside tip admonition * change -t flag's default column and add a description * add Default Filenames section * docs: revert adding Default Filenames section I didn't realize it already existed elsewhere. * refactor: use path instead of filepath on InitTaskFile as requested to prevent ambiguity with the stdlib package. * fix TestInit (incorrectly merged) * docs: remove outdated info on --taskfile flag * refactor task initialization changes - remove const DefaultTaskInitFilename from taskfile/taskfile.go - revert description of Entrypoint flag - make InitTaskfile accept a path to either a file or a directory, and join the default Taskfile name+ext to it if it is a directory - take the target file path from the first argument instead of the Entrypoint flag - detect extension-only filenames (".yaml") instead of replacing "*" with "Taskfile" - use different format in success log so that it makes sense at different paths than the current dir * print colon instead of "at" it's a lot cleaner in most cases. * rewrite init tests test both initializing to a directory path and a file path * return final path from InitTaskfile ...and print it's relative representation * fix lint error (ineffassign) * use filepathext.TryAbsToRel() instead * define and use filepathext.IsExtOnly() * link to default filenames list in cli ref docs (specifically in the --taskfile flag description) --- cmd/task/task.go | 22 +++++++++++++++++----- init.go | 28 ++++++++++++++++++++-------- init_test.go | 26 ++++++++++++++++++++++++-- internal/filepathext/filepathext.go | 6 ++++++ website/docs/reference/cli.mdx | 6 +++--- 5 files changed, 70 insertions(+), 18 deletions(-) diff --git a/cmd/task/task.go b/cmd/task/task.go index 16c8ff190c..e59273b48f 100644 --- a/cmd/task/task.go +++ b/cmd/task/task.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + fp "path/filepath" "strings" "github.com/spf13/pflag" @@ -13,6 +14,7 @@ import ( "github.com/go-task/task/v3/args" "github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/internal/experiments" + "github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/internal/flags" "github.com/go-task/task/v3/internal/logger" "github.com/go-task/task/v3/internal/sort" @@ -77,18 +79,28 @@ func run() error { if err != nil { return err } - - if err := task.InitTaskfile(os.Stdout, wd); err != nil { + args, _, err := getArgs() + if err != nil { + return err + } + path := wd + if len(args) > 0 { + name := args[0] + if filepathext.IsExtOnly(name) { + name = filepathext.SmartJoin(fp.Dir(name), "Taskfile"+fp.Ext(name)) + } + path = filepathext.SmartJoin(wd, name) + } + finalPath, err := task.InitTaskfile(os.Stdout, path) + if err != nil { return err } - if !flags.Silent { if flags.Verbose { log.Outf(logger.Default, "%s\n", task.DefaultTaskfile) } - log.Outf(logger.Green, "%s created in the current directory\n", task.DefaultTaskFilename) + log.Outf(logger.Green, "Taskfile created: %s\n", filepathext.TryAbsToRel(finalPath)) } - return nil } diff --git a/init.go b/init.go index 69c31b9164..04f0c0b766 100644 --- a/init.go +++ b/init.go @@ -24,17 +24,29 @@ tasks: const DefaultTaskFilename = "Taskfile.yml" -// InitTaskfile creates a new Taskfile -func InitTaskfile(w io.Writer, dir string) error { - f := filepathext.SmartJoin(dir, DefaultTaskFilename) +// InitTaskfile creates a new Taskfile at path. +// +// path can be either a file path or a directory path. +// If path is a directory, path/Taskfile.yml will be created. +// +// The final file path is always returned and may be different from the input path. +func InitTaskfile(w io.Writer, path string) (string, error) { + fi, err := os.Stat(path) + if err == nil && !fi.IsDir() { + return path, errors.TaskfileAlreadyExistsError{} + } - if _, err := os.Stat(f); err == nil { - return errors.TaskfileAlreadyExistsError{} + if fi != nil && fi.IsDir() { + path = filepathext.SmartJoin(path, DefaultTaskFilename) + // path was a directory, so check if Taskfile.yml exists in it + if _, err := os.Stat(path); err == nil { + return path, errors.TaskfileAlreadyExistsError{} + } } - if err := os.WriteFile(f, []byte(DefaultTaskfile), 0o644); err != nil { - return err + if err := os.WriteFile(path, []byte(DefaultTaskfile), 0o644); err != nil { + return path, err } - return nil + return path, nil } diff --git a/init_test.go b/init_test.go index 5f4ccc79b9..ce401c0881 100644 --- a/init_test.go +++ b/init_test.go @@ -9,7 +9,7 @@ import ( "github.com/go-task/task/v3/internal/filepathext" ) -func TestInit(t *testing.T) { +func TestInitDir(t *testing.T) { t.Parallel() const dir = "testdata/init" @@ -20,12 +20,34 @@ func TestInit(t *testing.T) { t.Errorf("Taskfile.yml should not exist") } - if err := task.InitTaskfile(io.Discard, dir); err != nil { + if _, err := task.InitTaskfile(io.Discard, dir); err != nil { t.Error(err) } if _, err := os.Stat(file); err != nil { t.Errorf("Taskfile.yml should exist") } + + _ = os.Remove(file) +} + +func TestInitFile(t *testing.T) { + t.Parallel() + + const dir = "testdata/init" + file := filepathext.SmartJoin(dir, "Tasks.yml") + + _ = os.Remove(file) + if _, err := os.Stat(file); err == nil { + t.Errorf("Tasks.yml should not exist") + } + + if _, err := task.InitTaskfile(io.Discard, file); err != nil { + t.Error(err) + } + + if _, err := os.Stat(file); err != nil { + t.Errorf("Tasks.yml should exist") + } _ = os.Remove(file) } diff --git a/internal/filepathext/filepathext.go b/internal/filepathext/filepathext.go index db64695c8e..f2a1ba15a0 100644 --- a/internal/filepathext/filepathext.go +++ b/internal/filepathext/filepathext.go @@ -55,3 +55,9 @@ func TryAbsToRel(abs string) string { return rel } + +// IsExtOnly checks whether path points to a file with no name but with +// an extension, i.e. ".yaml" +func IsExtOnly(path string) bool { + return filepath.Base(path) == filepath.Ext(path) +} diff --git a/website/docs/reference/cli.mdx b/website/docs/reference/cli.mdx index e196341826..4afe84b836 100644 --- a/website/docs/reference/cli.mdx +++ b/website/docs/reference/cli.mdx @@ -16,10 +16,10 @@ task [--flags] [tasks...] [-- CLI_ARGS...] If `--` is given, all remaining arguments will be assigned to a special `CLI_ARGS` variable -## Flags - ::: +## Flags + | Short | Flag | Type | Default | Description | | ----- | --------------------------- | -------- | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `-c` | `--color` | `bool` | `true` | Colored output. Enabled by default. Set flag to `false` or use `NO_COLOR=1` to disable. | @@ -45,7 +45,7 @@ If `--` is given, all remaining arguments will be assigned to a special | `-y` | `--yes` | `bool` | `false` | Assume "yes" as answer to all prompts. | | | `--status` | `bool` | `false` | Exits with non-zero exit code if any of the given tasks is not up-to-date. | | | `--summary` | `bool` | `false` | Show summary about a task. | -| `-t` | `--taskfile` | `string` | `Taskfile.yml` or `Taskfile.yaml` | | +| `-t` | `--taskfile` | `string` | | Taskfile path to run.
Check the list of default filenames [here](../usage/#supported-file-names). | | `-v` | `--verbose` | `bool` | `false` | Enables verbose mode. | | | `--version` | `bool` | `false` | Show Task version. | | `-w` | `--watch` | `bool` | `false` | Enables watch of the given task.