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() {
}
--- a.templ --
+-- b.templ --
package test
templ b() {
@@ -32,3 +32,23 @@ templ b() {
}
+-- c.templ --
+package test
+
+templ c() {
+
+}
+-- c.templ --
+package test
+
+templ 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.
`