From eb8bd3d829a1027ae30a59a07859aad5799c7c3a Mon Sep 17 00:00:00 2001 From: Ryan Prior Date: Thu, 5 Feb 2026 00:46:39 +0000 Subject: [PATCH 1/4] Adds method Task.DisplayJSON Uses encoding/json package to write a task to the target writer as JSON. Co-authored-by: jgart --- models/models.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/models/models.go b/models/models.go index 67aa86f..1241e5e 100644 --- a/models/models.go +++ b/models/models.go @@ -1,6 +1,7 @@ package models import ( + "encoding/json" "fmt" "io" "strings" @@ -58,6 +59,17 @@ func (t Task) Display(w io.Writer) { } } +// Display writes a Task as JSON. +func (t Task) DisplayJSON(w io.Writer) (err error) { + buf, _ := json.Marshal(t) + // I tried to find a way to trigger an marshaling error for testing + // purposes but failed. The Task struct doesn't have any fields which + // are amenable to un-marshalable objects. Therefore this just eats an + // error. + fmt.Fprintln(w, string(buf)) + return +} + // Tasks is an alias type for []Task type Tasks []Task From f7674b1c381b91c11ff468f873d46a8223214be5 Mon Sep 17 00:00:00 2001 From: Ryan Prior Date: Thu, 5 Feb 2026 05:51:53 +0000 Subject: [PATCH 2/4] Add output type flag Co-authored-by: jgart --- cmd/xc/main.go | 16 ++++++++++++++-- cmd/xc/usage.txt | 2 ++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/cmd/xc/main.go b/cmd/xc/main.go index 7f8f4b4..9882793 100644 --- a/cmd/xc/main.go +++ b/cmd/xc/main.go @@ -31,7 +31,7 @@ var ErrNoTaskFile = errors.New("no xc compatible documentation file found") type config struct { version, help, short, display, noTTY, complete, uncomplete bool - filename, heading, filetype string + filename, heading, filetype, outputType string } var version = "" @@ -72,6 +72,8 @@ func flags() config { flag.BoolVar(&cfg.display, "d", false, "print the plain text code of a task rather than running it") flag.BoolVar(&cfg.display, "display", false, "print the plain text code of a task rather than running it") + flag.StringVar(&cfg.outputType, "output", "markdown", "specify the output type to display") + flag.BoolVar(&cfg.complete, "complete", false, "install shell completion for xc") flag.BoolVar(&cfg.uncomplete, "uncomplete", false, "uninstall shell completion for xc") @@ -251,7 +253,17 @@ func runMain() error { } // xc -display task1 if cfg.display { - ta.Display(os.Stdout) + switch strings.ToLower(cfg.outputType) { + case "markdown": + ta.Display(os.Stdout) + case "json": + err = ta.DisplayJSON(os.Stdout) + if err != nil { + return fmt.Errorf("xc: %w", err) + } + default: + fmt.Printf("xc: unrecognized output type: %s", cfg.outputType) + } return nil } // xc task1 diff --git a/cmd/xc/usage.txt b/cmd/xc/usage.txt index 019ad78..121dd42 100644 --- a/cmd/xc/usage.txt +++ b/cmd/xc/usage.txt @@ -6,6 +6,8 @@ xc [inputs...] Specify a markdown file that contains tasks (default: "README.md"). -d -display Print the markdown code of a task rather than running it. + -output + Specify the output type to display. (default: "markdown", consider: "json") -H -heading Specify the heading for xc tasks (default: "Tasks"). From c75bfac5e66c3514bde7c5cc164c6e593e7513b9 Mon Sep 17 00:00:00 2001 From: Ryan Prior Date: Fri, 6 Feb 2026 20:55:32 +0000 Subject: [PATCH 3/4] Adds tests for Display and DisplayJSON in models module --- models/models_test.go | 83 ++++++++++++++++++++++ models/testdata/generate-all.json | 1 + models/testdata/generate-all.md | 6 ++ models/testdata/generate-templ.json | 1 + models/testdata/generate-templ.md | 9 +++ models/testdata/generate-translations.json | 1 + models/testdata/generate-translations.md | 8 +++ 7 files changed, 109 insertions(+) create mode 100644 models/models_test.go create mode 100644 models/testdata/generate-all.json create mode 100644 models/testdata/generate-all.md create mode 100644 models/testdata/generate-templ.json create mode 100644 models/testdata/generate-templ.md create mode 100644 models/testdata/generate-translations.json create mode 100644 models/testdata/generate-translations.md diff --git a/models/models_test.go b/models/models_test.go new file mode 100644 index 0000000..523afcb --- /dev/null +++ b/models/models_test.go @@ -0,0 +1,83 @@ +package models + +import ( + _ "embed" + "io" + "os" + "strings" + "testing" +) + +var gnTempl = Task{ + Name: "generate-templ", + Script: "go run -mod=mod github.com/a-h/templ/cmd/templ generate\ngo mod tidy\n", +} +var gnTranslations = Task{ + Name: "generate-translations", + Script: "go run ./i18n/generate\n", +} +var gnAll = Task{ + Name: "generate-all", + DependsOn: []string{ + "generate-templ", + "generate-translations", + }, + DepsBehaviour: DependencyBehaviourAsync, +} + +//go:embed testdata/generate-templ.md +var gnTemplExpected string + +//go:embed testdata/generate-translations.md +var gnTranslationsExpected string + +//go:embed testdata/generate-all.md +var gnAllExpected string + +//go:embed testdata/generate-templ.json +var gnTemplExpectedJSON string + +//go:embed testdata/generate-translations.json +var gnTranslationsExpectedJSON string + +//go:embed testdata/generate-all.json +var gnAllExpectedJSON string + +func assertTask(t *testing.T, expected, actual string) { + t.Helper() + if strings.TrimSpace(expected) != strings.TrimSpace(actual) { + t.Fatalf("model want=%s got=%s", expected, actual) + } +} + +func taskToMarkdown(task Task) string { + r, w, _ := os.Pipe() + defer w.Close() + defer r.Close() + task.Display(w) + w.Close() + res, _ := io.ReadAll(r) + return string(res) +} + +func taskToJSON(task Task) string { + r, w, _ := os.Pipe() + defer w.Close() + defer r.Close() + _ = task.DisplayJSON(w) + w.Close() + res, _ := io.ReadAll(r) + return string(res) +} + +func TestDisplay(t *testing.T) { + assertTask(t, gnTemplExpected, taskToMarkdown(gnTempl)) + assertTask(t, gnTranslationsExpected, taskToMarkdown(gnTranslations)) + assertTask(t, gnAllExpected, taskToMarkdown(gnAll)) +} + +func TestDisplayJSON(t *testing.T) { + assertTask(t, gnTemplExpectedJSON, taskToJSON(gnTempl)) + assertTask(t, gnTranslationsExpectedJSON, taskToJSON(gnTranslations)) + assertTask(t, gnAllExpectedJSON, taskToJSON(gnAll)) +} diff --git a/models/testdata/generate-all.json b/models/testdata/generate-all.json new file mode 100644 index 0000000..a47fe8a --- /dev/null +++ b/models/testdata/generate-all.json @@ -0,0 +1 @@ +{"Name":"generate-all","Description":null,"Script":"","Dir":"","Env":null,"DependsOn":["generate-templ","generate-translations"],"Inputs":null,"ParsingError":"","RequiredBehaviour":0,"DepsBehaviour":1,"Interactive":false} diff --git a/models/testdata/generate-all.md b/models/testdata/generate-all.md new file mode 100644 index 0000000..d83cf64 --- /dev/null +++ b/models/testdata/generate-all.md @@ -0,0 +1,6 @@ +## generate-all + +Requires: generate-templ, generate-translations +RunDeps: async + +Run: always diff --git a/models/testdata/generate-templ.json b/models/testdata/generate-templ.json new file mode 100644 index 0000000..f4fbe19 --- /dev/null +++ b/models/testdata/generate-templ.json @@ -0,0 +1 @@ +{"Name":"generate-templ","Description":null,"Script":"go run -mod=mod github.com/a-h/templ/cmd/templ generate\ngo mod tidy\n","Dir":"","Env":null,"DependsOn":null,"Inputs":null,"ParsingError":"","RequiredBehaviour":0,"DepsBehaviour":0,"Interactive":false} diff --git a/models/testdata/generate-templ.md b/models/testdata/generate-templ.md new file mode 100644 index 0000000..de8a69d --- /dev/null +++ b/models/testdata/generate-templ.md @@ -0,0 +1,9 @@ +## generate-templ + +Run: always + +``` +go run -mod=mod github.com/a-h/templ/cmd/templ generate +go mod tidy + +``` diff --git a/models/testdata/generate-translations.json b/models/testdata/generate-translations.json new file mode 100644 index 0000000..5fbfdea --- /dev/null +++ b/models/testdata/generate-translations.json @@ -0,0 +1 @@ +{"Name":"generate-translations","Description":null,"Script":"go run ./i18n/generate\n","Dir":"","Env":null,"DependsOn":null,"Inputs":null,"ParsingError":"","RequiredBehaviour":0,"DepsBehaviour":0,"Interactive":false} diff --git a/models/testdata/generate-translations.md b/models/testdata/generate-translations.md new file mode 100644 index 0000000..4485512 --- /dev/null +++ b/models/testdata/generate-translations.md @@ -0,0 +1,8 @@ +## generate-translations + +Run: always + +``` +go run ./i18n/generate + +``` From 6a816d96e8b5e511cc99a27470ad9e99b4fd601d Mon Sep 17 00:00:00 2001 From: Ryan Prior Date: Wed, 15 Jul 2026 22:58:27 +0000 Subject: [PATCH 4/4] Improved comments, removed vestigial error return --- cmd/xc/main.go | 5 +---- models/models.go | 9 +++------ models/models_test.go | 2 +- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/cmd/xc/main.go b/cmd/xc/main.go index 9882793..d9e41b6 100644 --- a/cmd/xc/main.go +++ b/cmd/xc/main.go @@ -257,10 +257,7 @@ func runMain() error { case "markdown": ta.Display(os.Stdout) case "json": - err = ta.DisplayJSON(os.Stdout) - if err != nil { - return fmt.Errorf("xc: %w", err) - } + ta.DisplayJSON(os.Stdout) default: fmt.Printf("xc: unrecognized output type: %s", cfg.outputType) } diff --git a/models/models.go b/models/models.go index 1241e5e..a5ec305 100644 --- a/models/models.go +++ b/models/models.go @@ -60,14 +60,11 @@ func (t Task) Display(w io.Writer) { } // Display writes a Task as JSON. -func (t Task) DisplayJSON(w io.Writer) (err error) { +func (t Task) DisplayJSON(w io.Writer) { buf, _ := json.Marshal(t) - // I tried to find a way to trigger an marshaling error for testing - // purposes but failed. The Task struct doesn't have any fields which - // are amenable to un-marshalable objects. Therefore this just eats an - // error. + // Error discarded because the Task struct doesn't have any fields with + // types at risk of being un-marshalable. fmt.Fprintln(w, string(buf)) - return } // Tasks is an alias type for []Task diff --git a/models/models_test.go b/models/models_test.go index 523afcb..5c84878 100644 --- a/models/models_test.go +++ b/models/models_test.go @@ -64,7 +64,7 @@ func taskToJSON(task Task) string { r, w, _ := os.Pipe() defer w.Close() defer r.Close() - _ = task.DisplayJSON(w) + task.DisplayJSON(w) w.Close() res, _ := io.ReadAll(r) return string(res)