From b6af2e254333b7b91671107560773d587d0335e0 Mon Sep 17 00:00:00 2001 From: Nate Meyer <672246+notnmeyer@users.noreply.github.com> Date: Tue, 16 Jul 2024 20:46:32 -0700 Subject: [PATCH] feat: add --output formats (#77) --- cmd/tsk/tsk.go | 15 +++- internal/outputformat/outputformat.go | 25 ++++++ internal/outputformat/outputformat_test.go | 17 ++++ internal/task/task.go | 98 ++++++++++++---------- 4 files changed, 111 insertions(+), 44 deletions(-) create mode 100644 internal/outputformat/outputformat.go create mode 100644 internal/outputformat/outputformat_test.go diff --git a/cmd/tsk/tsk.go b/cmd/tsk/tsk.go index 321da78..793e6c2 100644 --- a/cmd/tsk/tsk.go +++ b/cmd/tsk/tsk.go @@ -6,7 +6,9 @@ import ( "regexp" "strings" + output "github.com/notnmeyer/tsk/internal/outputformat" "github.com/notnmeyer/tsk/internal/task" + flag "github.com/spf13/pflag" ) @@ -21,11 +23,14 @@ type Options struct { filter string init bool listTasks bool + output string pure bool taskFile string tasks []string } +const defaultOutputFormat = output.OutputFormat(output.Text) + func init() { // TOML 1.1 features are behind a flag until officially released os.Setenv("BURNTSUSHI_TOML_110", "") @@ -35,8 +40,9 @@ func main() { opts := Options{} flag.BoolVarP(&opts.displayVersion, "version", "V", false, "display tsk version") flag.StringVarP(&opts.filter, "filter", "F", ".*", "regex filter for --list") - flag.BoolVarP(&opts.listTasks, "list", "l", false, "list tasks") flag.BoolVar(&opts.init, "init", false, "create a tasks.toml file in $PWD") + flag.BoolVarP(&opts.listTasks, "list", "l", false, "list tasks") + flag.StringVarP(&opts.output, "output", "o", "text", fmt.Sprintf("output format (applies only to --list) (one of: %s, %s)", string(output.Text), string(output.Markdown))) flag.BoolVarP(&opts.pure, "pure", "", false, "don't inherit the parent env") flag.StringVarP(&opts.taskFile, "file", "f", "tasks.toml", "taskfile to use") flag.BoolVarP(&help, "help", "h", false, "") @@ -63,6 +69,11 @@ func main() { return } + if !output.IsValid(opts.output) { + fmt.Printf("--output must one of: %s\n", output.String()) + os.Exit(1) + } + // check if there are args passed after "--". // - if "--" is not present ArgsLenAtDash() returns -1. // - dash position 0 would be invocations like, `tsk -l -- foo` @@ -87,7 +98,7 @@ func main() { } if opts.listTasks { - exec.ListTasksFromTaskFile(regexp.MustCompile(opts.filter)) + exec.ListTasksFromTaskFile(regexp.MustCompile(opts.filter), output.OutputFormat(opts.output)) return } diff --git a/internal/outputformat/outputformat.go b/internal/outputformat/outputformat.go new file mode 100644 index 0000000..e3f2d76 --- /dev/null +++ b/internal/outputformat/outputformat.go @@ -0,0 +1,25 @@ +package outputformat + +import ( + "fmt" +) + +type OutputFormat string + +const ( + Markdown OutputFormat = "md" + Text OutputFormat = "text" + TOML OutputFormat = "toml" +) + +func String() string { + return fmt.Sprintf("%s, %s, %s", string(Markdown), string(Text), string(TOML)) +} + +func IsValid(format string) bool { + switch format { + case string(Markdown), string(Text), string(TOML): + return true + } + return false +} diff --git a/internal/outputformat/outputformat_test.go b/internal/outputformat/outputformat_test.go new file mode 100644 index 0000000..fcc0eb5 --- /dev/null +++ b/internal/outputformat/outputformat_test.go @@ -0,0 +1,17 @@ +package outputformat + +import ( + "testing" +) + +func TestIsValid(t *testing.T) { + want, got := true, IsValid("toml") + if want != got { + t.Errorf("got %t, wanted %t\n", got, want) + } + + want, got = false, IsValid("XML") + if want != got { + t.Errorf("got %t, wanted %t\n", got, want) + } +} diff --git a/internal/task/task.go b/internal/task/task.go index df30296..0e81907 100644 --- a/internal/task/task.go +++ b/internal/task/task.go @@ -11,6 +11,8 @@ import ( "strings" "sync" + output "github.com/notnmeyer/tsk/internal/outputformat" + "github.com/BurntSushi/toml" "mvdan.cc/sh/v3/expand" "mvdan.cc/sh/v3/interp" @@ -167,62 +169,74 @@ func (exec *Executor) runCommand(cmd string, dir string, env []string) error { return nil } -func (exec *Executor) ListTasksFromTaskFile(regex *regexp.Regexp) { +func (exec *Executor) ListTasksFromTaskFile(regex *regexp.Regexp, format output.OutputFormat) { tasks := filterTasks(&exec.Config.Tasks, regex) - - // gaaaah, i like the end result but i hate this indent := " " - for name, t := range tasks { - // name - fmt.Printf("%s:\n", name) - - // description - if t.Description != "" { - fmt.Printf("%sdescription:\n", indent) - trimmed := strings.TrimSpace(t.Description) - for _, line := range strings.Split(trimmed, "\n") { - fmt.Printf("%s%s\n", strings.Repeat(indent, 2), line) + + switch format { + case output.Markdown: + for name, t := range tasks { + fmt.Printf("## %s\n", name) + if len(t.Cmds) > 0 { + for _, cmd := range t.Cmds { + fmt.Printf("%s- %s\n", indent, cmd) + } + } else { + fmt.Printf("%s- %s/%s\n", indent, exec.Config.ScriptDir, name) } } + case output.TOML: + toml.NewEncoder(os.Stdout).Encode(tasks) + case output.Text: + for name, t := range tasks { + // name + fmt.Printf("%s:\n", name) + + // description + if t.Description != "" { + fmt.Printf("%sdescription:\n", indent) + trimmed := strings.TrimSpace(t.Description) + for _, line := range strings.Split(trimmed, "\n") { + fmt.Printf("%s%s\n", strings.Repeat(indent, 2), line) + } + } - // deps - if len(t.Deps) > 0 { - fmt.Printf("%sdeps:\n", indent) - for _, dep := range t.Deps { - fmt.Printf("%s%v\n", indent+indent, dep) + // deps + if len(t.Deps) > 0 { + fmt.Printf("%sdeps:\n", indent) + for _, dep := range t.Deps { + fmt.Printf("%s%v\n", indent+indent, dep) + } } - } - // cmds - if len(t.Cmds) > 0 { + // cmds fmt.Printf("%scommands:\n", indent) - for _, cmd := range t.Cmds { - fmt.Printf("%s\n", indent+indent+cmd) + if len(t.Cmds) > 0 { + for _, cmd := range t.Cmds { + fmt.Printf("%s\n", indent+indent+cmd) + } + } else { + fmt.Printf("%s%s/%s\n", indent+indent, exec.Config.ScriptDir, name) } - } else { - fmt.Printf("%s# will run `%s/%s`\n", indent, exec.Config.ScriptDir, name) - } - // dir - if t.Dir != "" { - fmt.Printf("%sdir: %s\n", indent, t.Dir) - } + // dir + if t.Dir != "" { + fmt.Printf("%sdir: %s\n", indent, t.Dir) + } - // dotenv - if t.DotEnv != "" { - fmt.Printf("%sdotenv: %s\n", indent, t.DotEnv) - } + // dotenv + if t.DotEnv != "" { + fmt.Printf("%sdotenv: %s\n", indent, t.DotEnv) + } - // pure - if t.Pure == true { - fmt.Printf("%spure: %t\n", indent, t.Pure) - } + // pure + if t.Pure == true { + fmt.Printf("%spure: %t\n", indent, t.Pure) + } - fmt.Println("") + fmt.Println("") + } } - - // pure toml representation. simple but includes blank task attributes. - // toml.NewEncoder(os.Stdout).Encode(tasks) } func filterTasks(tasks *map[string]Task, regex *regexp.Regexp) map[string]Task {