Repository: hamba/avro
Pull request #302 (2023-09-05 18:46:35)

feat: add stdout output support to avrogen
Hello, this small PR adds support for stdout output to `avrogen`.
It is implemented by making the `-o FILE` argument optional, writing the generated Go code to stdout if not provided.

I updated the unit tests accordingly and fixed some wrong wording in error messages as well 👍.

The use case for this feature is quick debugging of Go structs generation without having to use temporary files.


Patch:
diff --git a/README.md b/README.md
index 9b9c862..8c569c8 100644
--- a/README.md
+++ b/README.md
@@ -176,6 +176,8 @@ Example usage assuming there's a valid schema in `in.avsc`:
 avrogen -pkg avro -o bla.go -tags json:snake,yaml:upper-camel in.avsc
 ```
 
+**Tip:** Omit `-o FILE` to dump the generated Go structs to stdout instead of a file.
+
 Check the options and usage with `-h`:
 
 ```shell
diff --git a/cmd/avrogen/main.go b/cmd/avrogen/main.go
index ef28186..7ba35a7 100644
--- a/cmd/avrogen/main.go
+++ b/cmd/avrogen/main.go
@@ -23,15 +23,15 @@ type config struct {
 }
 
 func main() {
-	os.Exit(realMain(os.Args, os.Stderr))
+	os.Exit(realMain(os.Args, os.Stderr, os.Stdout))
 }
 
-func realMain(args []string, out io.Writer) int {
+func realMain(args []string, out, dumpout io.Writer) int {
 	var cfg config
 	flgs := flag.NewFlagSet("avrogen", flag.ExitOnError)
 	flgs.SetOutput(out)
 	flgs.StringVar(&cfg.Pkg, "pkg", "", "The package name of the output file.")
-	flgs.StringVar(&cfg.Out, "o", "", "The output file path.")
+	flgs.StringVar(&cfg.Out, "o", "", "The output file path to write to instead of stdout.")
 	flgs.StringVar(&cfg.Tags, "tags", "", "The additional field tags <tag-name>:{snake|camel|upper-camel|kebab}>[,...]")
 	flgs.BoolVar(&cfg.FullName, "fullname", false, "Use the full name of the Record schema to create the struct name.")
 	flgs.BoolVar(&cfg.Encoders, "encoders", false, "Generate encoders for the structs.")
@@ -75,13 +75,27 @@ func realMain(args []string, out io.Writer) int {
 	}
 	formatted, err := format.Source(buf.Bytes())
 	if err != nil {
-		_, _ = fmt.Fprintf(out, "Error: could format code: %v\n", err)
+		_, _ = fmt.Fprintf(out, "Error: could not format code: %v\n", err)
 		return 3
 	}
-	if err = os.WriteFile(cfg.Out, formatted, 0o600); err != nil {
-		_, _ = fmt.Fprintf(out, "Error: could write file: %v\n", err)
+
+	writer := dumpout
+	if cfg.Out != "" {
+		file, err := os.Create(cfg.Out)
+		if err != nil {
+			_, _ = fmt.Fprintf(out, "Error: could not create output file: %v\n", err)
+			return 4
+		}
+		defer func() { _ = file.Close() }()
+
+		writer = file
+	}
+
+	if _, err := writer.Write(formatted); err != nil {
+		_, _ = fmt.Fprintf(out, "Error: could not write code: %v\n", err)
 		return 4
 	}
+
 	return 0
 }
 
@@ -94,10 +108,6 @@ func validateOpts(nargs int, cfg config) error {
 		return fmt.Errorf("a package is required")
 	}
 
-	if cfg.Out == "" {
-		return fmt.Errorf("an output file is reqired")
-	}
-
 	return nil
 }
 


Test patch:
diff --git a/cmd/avrogen/main_test.go b/cmd/avrogen/main_test.go
index 677277d..42b7301 100644
--- a/cmd/avrogen/main_test.go
+++ b/cmd/avrogen/main_test.go
@@ -1,6 +1,7 @@
 package main
 
 import (
+	"bytes"
 	"flag"
 	"io"
 	"os"
@@ -34,11 +35,6 @@ func TestAvroGen_RequiredFlags(t *testing.T) {
 			args:    []string{"avrogen", "-o", "some/file", "schema.avsc"},
 			wantErr: true,
 		},
-		{
-			name:    "validates output file is set",
-			args:    []string{"avrogen", "-pkg", "test", "schema.avsc"},
-			wantErr: true,
-		},
 		{
 			name:    "validates tag format are valid",
 			args:    []string{"avrogen", "-o", "some/file", "-pkg", "test", "-tags", "snake", "schema.avsc"},
@@ -59,7 +55,7 @@ func TestAvroGen_RequiredFlags(t *testing.T) {
 	for _, test := range tests {
 		test := test
 		t.Run(test.name, func(t *testing.T) {
-			got := realMain(test.args, io.Discard)
+			got := realMain(test.args, io.Discard, io.Discard)
 
 			if !test.wantErr {
 				assert.Equal(t, 0, got)
@@ -71,6 +67,18 @@ func TestAvroGen_RequiredFlags(t *testing.T) {
 	}
 }
 
+func TestAvroGen_GeneratesSchemaStdout(t *testing.T) {
+	var buf bytes.Buffer
+
+	args := []string{"avrogen", "-pkg", "testpkg", "testdata/schema.avsc"}
+	gotCode := realMain(args, io.Discard, &buf)
+	require.Equal(t, 0, gotCode)
+
+	want, err := os.ReadFile("testdata/golden.go")
+	require.NoError(t, err)
+	assert.Equal(t, want, buf.Bytes())
+}
+
 func TestAvroGen_GeneratesSchema(t *testing.T) {
 	path, err := os.MkdirTemp("./", "avrogen")
 	require.NoError(t, err)
@@ -78,7 +86,7 @@ func TestAvroGen_GeneratesSchema(t *testing.T) {
 
 	file := filepath.Join(path, "test.go")
 	args := []string{"avrogen", "-pkg", "testpkg", "-o", file, "testdata/schema.avsc"}
-	gotCode := realMain(args, io.Discard)
+	gotCode := realMain(args, io.Discard, io.Discard)
 	require.Equal(t, 0, gotCode)
 
 	got, err := os.ReadFile(file)
@@ -101,7 +109,7 @@ func TestAvroGen_GeneratesSchemaWithFullname(t *testing.T) {
 
 	file := filepath.Join(path, "test.go")
 	args := []string{"avrogen", "-pkg", "testpkg", "-o", file, "-fullname", "testdata/schema.avsc"}
-	gotCode := realMain(args, io.Discard)
+	gotCode := realMain(args, io.Discard, io.Discard)
 	require.Equal(t, 0, gotCode)
 
 	got, err := os.ReadFile(file)
@@ -124,7 +132,7 @@ func TestAvroGen_GeneratesSchemaWithEncoders(t *testing.T) {
 
 	file := filepath.Join(path, "test.go")
 	args := []string{"avrogen", "-pkg", "testpkg", "-o", file, "-encoders", "testdata/schema.avsc"}
-	gotCode := realMain(args, io.Discard)
+	gotCode := realMain(args, io.Discard, io.Discard)
 	require.Equal(t, 0, gotCode)
 
 	got, err := os.ReadFile(file)