From bd454b3a9283aeda3da1041c186d128f6c49cfc3 Mon Sep 17 00:00:00 2001 From: Ben Yanke Date: Mon, 16 Sep 2024 13:49:12 -0400 Subject: [PATCH 1/5] Add ci fail flag --- cmd/templ/fmtcmd/main.go | 62 +++++++++++++++-------- cmd/templ/main.go | 6 ++- cmd/templ/processor/processor.go | 20 +++++--- docs/docs/09-commands-and-tools/01-cli.md | 7 +++ 4 files changed, 65 insertions(+), 30 deletions(-) diff --git a/cmd/templ/fmtcmd/main.go b/cmd/templ/fmtcmd/main.go index 6e0584365..cccdd15b6 100644 --- a/cmd/templ/fmtcmd/main.go +++ b/cmd/templ/fmtcmd/main.go @@ -17,6 +17,7 @@ import ( ) type Arguments struct { + FailIfChanged bool ToStdout bool StdinFilepath string Files []string @@ -26,9 +27,10 @@ type Arguments struct { func Run(log *slog.Logger, stdin io.Reader, stdout io.Writer, args Arguments) (err error) { // If no files are provided, read from stdin and write to stdout. if len(args.Files) == 0 { - return format(writeToWriter(stdout), readFromReader(stdin, args.StdinFilepath), true) + out, _ := format(writeToWriter(stdout), readFromReader(stdin, args.StdinFilepath), true) + return out } - process := func(fileName string) error { + process := func(fileName string) (error, bool) { read := readFromFile(fileName) write := writeToFile if args.ToStdout { @@ -38,22 +40,24 @@ func Run(log *slog.Logger, stdin io.Reader, stdout io.Writer, args Arguments) (e return format(write, read, writeIfUnchanged) } dir := args.Files[0] - return NewFormatter(log, dir, process, args.WorkerCount).Run() + return NewFormatter(log, dir, process, args.WorkerCount, args.FailIfChanged).Run() } type Formatter struct { - Log *slog.Logger - Dir string - Process func(fileName string) error - WorkerCount int + Log *slog.Logger + Dir string + Process func(fileName string) (error, bool) + WorkerCount int + FailIfChange bool } -func NewFormatter(log *slog.Logger, dir string, process func(fileName string) error, workerCount int) *Formatter { +func NewFormatter(log *slog.Logger, dir string, process func(fileName string) (error, bool), workerCount int, failIfChange bool) *Formatter { f := &Formatter{ - Log: log, - Dir: dir, - Process: process, - WorkerCount: workerCount, + Log: log, + Dir: dir, + Process: process, + WorkerCount: workerCount, + FailIfChange: failIfChange, } if f.WorkerCount == 0 { f.WorkerCount = runtime.NumCPU() @@ -62,12 +66,16 @@ func NewFormatter(log *slog.Logger, dir string, process func(fileName string) er } func (f *Formatter) Run() (err error) { + changesMade := 0 start := time.Now() results := make(chan processor.Result) f.Log.Debug("Walking directory", slog.String("path", f.Dir)) go processor.Process(f.Dir, f.Process, f.WorkerCount, results) var successCount, errorCount int for r := range results { + if r.ChangesMade { + changesMade += 1 + } if r.Error != nil { f.Log.Error(r.FileName, slog.Any("error", r.Error)) errorCount++ @@ -76,10 +84,18 @@ func (f *Formatter) Run() (err error) { f.Log.Debug(r.FileName, slog.Duration("duration", r.Duration)) successCount++ } - f.Log.Info("Format complete", slog.Int("count", successCount+errorCount), slog.Int("errors", errorCount), slog.Duration("duration", time.Since(start))) + + if f.FailIfChange && changesMade > 0 { + f.Log.Error("Templates were valid but not properly formatted", slog.Int("count", successCount+errorCount), slog.Int("changed", changesMade), slog.Int("errors", errorCount), slog.Duration("duration", time.Since(start))) + return fmt.Errorf("templates were not formatted properly") + } + + f.Log.Info("Format Complete", slog.Int("count", successCount+errorCount), slog.Int("errors", errorCount), slog.Int("changed", changesMade), slog.Duration("duration", time.Since(start))) + if errorCount > 0 { return fmt.Errorf("formatting failed") } + return } @@ -122,26 +138,30 @@ func writeToFile(fileName, tgt string) error { return atomic.WriteFile(fileName, bytes.NewBufferString(tgt)) } -func format(write writer, read reader, writeIfUnchanged bool) (err error) { +// TODO DO CHANGE TRACKING HERE +func format(write writer, read reader, writeIfUnchanged bool) (err error, fileChanged bool) { fileName, src, err := read() if err != nil { - return err + return err, false } t, err := parser.ParseString(src) if err != nil { - return err + return err, false } t.Filepath = fileName t, err = imports.Process(t) if err != nil { - return err + return err, false } w := new(bytes.Buffer) if err = t.Write(w); err != nil { - return fmt.Errorf("formatting error: %w", err) + return fmt.Errorf("formatting error: %w", err), false } - if !writeIfUnchanged && src == w.String() { - return nil + + fileChanged = (src != w.String()) + + if !writeIfUnchanged && !fileChanged { + return nil, fileChanged } - return write(fileName, w.String()) + return write(fileName, w.String()), fileChanged } diff --git a/cmd/templ/main.go b/cmd/templ/main.go index 7df4fd4bd..6bc48fdee 100644 --- a/cmd/templ/main.go +++ b/cmd/templ/main.go @@ -297,7 +297,9 @@ Args: -log-level Set log verbosity level. (default "info", options: "debug", "info", "warn", "error") -w - Number of workers to use when formatting code. (default runtime.NumCPUs). + Number of workers to use when formatting code. (default runtime.NumCPUs).\ + -fail-if-changed + Fails with an error exit code if files are changed for use in CI. -help Print help and exit. ` @@ -308,6 +310,7 @@ func fmtCmd(stdin io.Reader, stdout, stderr io.Writer, args []string) (code int) workerCountFlag := cmd.Int("w", runtime.NumCPU(), "") verboseFlag := cmd.Bool("v", false, "") logLevelFlag := cmd.String("log-level", "info", "") + failIfChanged := cmd.Bool("fail-if-changed", false, "") stdoutFlag := cmd.Bool("stdout", false, "") stdinFilepath := cmd.String("stdin-filepath", "", "") err := cmd.Parse(args) @@ -327,6 +330,7 @@ func fmtCmd(stdin io.Reader, stdout, stderr io.Writer, args []string) (code int) Files: cmd.Args(), WorkerCount: *workerCountFlag, StdinFilepath: *stdinFilepath, + FailIfChanged: *failIfChanged, }) if err != nil { return 1 diff --git a/cmd/templ/processor/processor.go b/cmd/templ/processor/processor.go index c628f29ef..3398650c7 100644 --- a/cmd/templ/processor/processor.go +++ b/cmd/templ/processor/processor.go @@ -10,12 +10,13 @@ import ( ) type Result struct { - FileName string - Duration time.Duration - Error error + FileName string + Duration time.Duration + Error error + ChangesMade bool } -func Process(dir string, f func(fileName string) error, workerCount int, results chan<- Result) { +func Process(dir string, f func(fileName string) (error, bool), workerCount int, results chan<- Result) { templates := make(chan string) go func() { defer close(templates) @@ -56,7 +57,7 @@ func FindTemplates(srcPath string, output chan<- string) (err error) { }) } -func ProcessChannel(templates <-chan string, dir string, f func(fileName string) error, workerCount int, results chan<- Result) { +func ProcessChannel(templates <-chan string, dir string, f func(fileName string) (error, bool), workerCount int, results chan<- Result) { defer close(results) var wg sync.WaitGroup wg.Add(workerCount) @@ -65,10 +66,13 @@ func ProcessChannel(templates <-chan string, dir string, f func(fileName string) defer wg.Done() for sourceFileName := range templates { start := time.Now() + outErr, outChanged := f(sourceFileName) results <- Result{ - FileName: sourceFileName, - Error: f(sourceFileName), - Duration: time.Since(start), + FileName: sourceFileName, + Error: outErr, + Duration: time.Since(start), + ChangesMade: outChanged, + // TODO get this working } } }() diff --git a/docs/docs/09-commands-and-tools/01-cli.md b/docs/docs/09-commands-and-tools/01-cli.md index 9d1992834..2a5fd9998 100644 --- a/docs/docs/09-commands-and-tools/01-cli.md +++ b/docs/docs/09-commands-and-tools/01-cli.md @@ -87,6 +87,13 @@ templ fmt . templ fmt ``` +Alternatively, you can run `fmt` in CI to ensure that invalidly formatted templatess do not pass CI. This will cause the command +to exit with unix error-code `1` if any templates needed to be modified. + +``` +templ fmt -fail-if-changed . +``` + ## Language Server for IDE integration `templ lsp` provides a Language Server Protocol (LSP) implementation to support IDE integrations. From d09552399830a3d161ebdf591117a1e649389d63 Mon Sep 17 00:00:00 2001 From: Ben Yanke Date: Mon, 16 Sep 2024 13:52:08 -0400 Subject: [PATCH 2/5] Tidy up --- cmd/templ/fmtcmd/main.go | 1 - cmd/templ/main.go | 2 +- cmd/templ/processor/processor.go | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/cmd/templ/fmtcmd/main.go b/cmd/templ/fmtcmd/main.go index cccdd15b6..2f2b7fc4c 100644 --- a/cmd/templ/fmtcmd/main.go +++ b/cmd/templ/fmtcmd/main.go @@ -138,7 +138,6 @@ func writeToFile(fileName, tgt string) error { return atomic.WriteFile(fileName, bytes.NewBufferString(tgt)) } -// TODO DO CHANGE TRACKING HERE func format(write writer, read reader, writeIfUnchanged bool) (err error, fileChanged bool) { fileName, src, err := read() if err != nil { diff --git a/cmd/templ/main.go b/cmd/templ/main.go index 6bc48fdee..3e605049f 100644 --- a/cmd/templ/main.go +++ b/cmd/templ/main.go @@ -297,7 +297,7 @@ Args: -log-level Set log verbosity level. (default "info", options: "debug", "info", "warn", "error") -w - Number of workers to use when formatting code. (default runtime.NumCPUs).\ + Number of workers to use when formatting code. (default runtime.NumCPUs). -fail-if-changed Fails with an error exit code if files are changed for use in CI. -help diff --git a/cmd/templ/processor/processor.go b/cmd/templ/processor/processor.go index 3398650c7..7f0466c3f 100644 --- a/cmd/templ/processor/processor.go +++ b/cmd/templ/processor/processor.go @@ -72,7 +72,6 @@ func ProcessChannel(templates <-chan string, dir string, f func(fileName string) Error: outErr, Duration: time.Since(start), ChangesMade: outChanged, - // TODO get this working } } }() From 417e39064f8dd44e935c5e42745bfc25933fb97d Mon Sep 17 00:00:00 2001 From: Ben Yanke Date: Tue, 17 Sep 2024 12:01:16 -0400 Subject: [PATCH 3/5] change flag name --- cmd/templ/main.go | 4 ++-- docs/docs/09-commands-and-tools/01-cli.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/templ/main.go b/cmd/templ/main.go index 3e605049f..008179e7c 100644 --- a/cmd/templ/main.go +++ b/cmd/templ/main.go @@ -298,7 +298,7 @@ Args: Set log verbosity level. (default "info", options: "debug", "info", "warn", "error") -w Number of workers to use when formatting code. (default runtime.NumCPUs). - -fail-if-changed + -fail Fails with an error exit code if files are changed for use in CI. -help Print help and exit. @@ -310,7 +310,7 @@ func fmtCmd(stdin io.Reader, stdout, stderr io.Writer, args []string) (code int) workerCountFlag := cmd.Int("w", runtime.NumCPU(), "") verboseFlag := cmd.Bool("v", false, "") logLevelFlag := cmd.String("log-level", "info", "") - failIfChanged := cmd.Bool("fail-if-changed", false, "") + failIfChanged := cmd.Bool("fail", false, "") stdoutFlag := cmd.Bool("stdout", false, "") stdinFilepath := cmd.String("stdin-filepath", "", "") err := cmd.Parse(args) diff --git a/docs/docs/09-commands-and-tools/01-cli.md b/docs/docs/09-commands-and-tools/01-cli.md index 2a5fd9998..f384161f1 100644 --- a/docs/docs/09-commands-and-tools/01-cli.md +++ b/docs/docs/09-commands-and-tools/01-cli.md @@ -91,7 +91,7 @@ Alternatively, you can run `fmt` in CI to ensure that invalidly formatted templa to exit with unix error-code `1` if any templates needed to be modified. ``` -templ fmt -fail-if-changed . +templ fmt -fail . ``` ## Language Server for IDE integration From b367dcb90f86a0efefe46d0a9aa57639310e454f Mon Sep 17 00:00:00 2001 From: Ben Yanke Date: Tue, 17 Sep 2024 17:14:47 -0400 Subject: [PATCH 4/5] add test case for -fail flag --- cmd/templ/fmtcmd/main_test.go | 48 +++++++++++++++++++++++++++++++++ cmd/templ/fmtcmd/testdata.txtar | 22 ++++++++++++++- 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/cmd/templ/fmtcmd/main_test.go b/cmd/templ/fmtcmd/main_test.go index fd7e85347..9a70f36a6 100644 --- a/cmd/templ/fmtcmd/main_test.go +++ b/cmd/templ/fmtcmd/main_test.go @@ -84,6 +84,7 @@ func TestFormat(t *testing.T) { Files: []string{ tp.testFiles["a.templ"].name, }, + FailIfChanged: false, }); err != nil { t.Fatalf("failed to run format command: %v", err) } @@ -101,6 +102,7 @@ func TestFormat(t *testing.T) { Files: []string{ tp.testFiles["a.templ"].name, }, + FailIfChanged: false, }); err != nil { t.Fatalf("failed to run format command: %v", err) } @@ -112,4 +114,50 @@ func TestFormat(t *testing.T) { t.Error(diff) } }) + + t.Run("fails when fail flag used and change occurs", func(t *testing.T) { + tp, err := setupProjectDir() + if err != nil { + t.Fatalf("failed to setup project dir: %v", err) + } + defer tp.cleanup() + if err = Run(log, nil, nil, Arguments{ + Files: []string{ + tp.testFiles["a.templ"].name, + }, + FailIfChanged: true, + }); err == nil { + t.Fatal("command should have exited with an error and did not") + } + data, err := os.ReadFile(tp.testFiles["a.templ"].name) + if err != nil { + t.Fatalf("failed to read file: %v", err) + } + if diff := cmp.Diff(tp.testFiles["a.templ"].expected, string(data)); diff != "" { + t.Error(diff) + } + }) + + t.Run("passes when fail flag used and no change occurs", func(t *testing.T) { + tp, err := setupProjectDir() + if err != nil { + t.Fatalf("failed to setup project dir: %v", err) + } + defer tp.cleanup() + if err = Run(log, nil, nil, Arguments{ + Files: []string{ + tp.testFiles["c.templ"].name, + }, + FailIfChanged: true, + }); err != nil { + t.Fatalf("failed to run format command: %v", err) + } + data, err := os.ReadFile(tp.testFiles["c.templ"].name) + if err != nil { + t.Fatalf("failed to read file: %v", err) + } + if diff := cmp.Diff(tp.testFiles["c.templ"].expected, string(data)); diff != "" { + t.Error(diff) + } + }) } diff --git a/cmd/templ/fmtcmd/testdata.txtar b/cmd/templ/fmtcmd/testdata.txtar index 4ed3ba2dd..8041fce38 100644 --- a/cmd/templ/fmtcmd/testdata.txtar +++ b/cmd/templ/fmtcmd/testdata.txtar @@ -22,7 +22,7 @@ templ b() {

B

} --- a.templ -- +-- b.templ -- package test templ b() { @@ -32,3 +32,23 @@ templ b() {

} +-- c.templ -- +package test + +templ c() { +
+

+ C +

+
+} +-- c.templ -- +package test + +templ c() { +
+

+ C +

+
+} From 6772d6ce62210c34c89c834507ab2a3e9b055335 Mon Sep 17 00:00:00 2001 From: Ben Yanke Date: Wed, 18 Sep 2024 07:31:02 -0400 Subject: [PATCH 5/5] Update cmd/templ/main.go Co-authored-by: Joe Davidson --- cmd/templ/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/templ/main.go b/cmd/templ/main.go index 008179e7c..9da4a3d2c 100644 --- a/cmd/templ/main.go +++ b/cmd/templ/main.go @@ -299,7 +299,7 @@ Args: -w Number of workers to use when formatting code. (default runtime.NumCPUs). -fail - Fails with an error exit code if files are changed for use in CI. + Fails with exit code 1 if files are changed. (e.g. in CI) -help Print help and exit. `