From 7f987f8e386a1bf874dd4acd369d64ddf979d274 Mon Sep 17 00:00:00 2001 From: Hugo Hromic Date: Tue, 5 Sep 2023 19:37:27 +0100 Subject: [PATCH 1/6] feat: add stdout output support to avrogen --- README.md | 2 ++ cmd/avrogen/main.go | 23 ++++++++++++----------- cmd/avrogen/main_test.go | 26 +++++++++++++++++--------- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 9b9c862..6b15b01 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 schema 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..8352a33 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 (dump to stdout if not provided).") flgs.StringVar(&cfg.Tags, "tags", "", "The additional field tags :{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,12 +75,17 @@ 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) - return 4 + + if cfg.Out == "" { + dumpout.Write(formatted) + } else { + if err = os.WriteFile(cfg.Out, formatted, 0o600); err != nil { + _, _ = fmt.Fprintf(out, "Error: could not write file: %v\n", err) + return 4 + } } return 0 } @@ -94,10 +99,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 } 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) From 5909ff2cc0ee08a81af3568f8da34758127b496a Mon Sep 17 00:00:00 2001 From: Hugo Hromic Date: Tue, 5 Sep 2023 19:49:45 +0100 Subject: [PATCH 2/6] check return values from `dumpout.Write` --- cmd/avrogen/main.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/avrogen/main.go b/cmd/avrogen/main.go index 8352a33..818bada 100644 --- a/cmd/avrogen/main.go +++ b/cmd/avrogen/main.go @@ -80,7 +80,10 @@ func realMain(args []string, out, dumpout io.Writer) int { } if cfg.Out == "" { - dumpout.Write(formatted) + if _, err = dumpout.Write(formatted); err != nil { + _, _ = fmt.Fprintf(out, "Error: could not write to stdout: %v\n", err) + return 4 + } } else { if err = os.WriteFile(cfg.Out, formatted, 0o600); err != nil { _, _ = fmt.Fprintf(out, "Error: could not write file: %v\n", err) From 13d4857aaf2c815713c368fc201420b47868c648 Mon Sep 17 00:00:00 2001 From: Hugo Hromic Date: Tue, 5 Sep 2023 19:53:04 +0100 Subject: [PATCH 3/6] use correct terminology --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6b15b01..8c569c8 100644 --- a/README.md +++ b/README.md @@ -176,7 +176,7 @@ 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 schema to stdout instead of a file. +**Tip:** Omit `-o FILE` to dump the generated Go structs to stdout instead of a file. Check the options and usage with `-h`: From 269cbb5474ada7073f4af238504f269cdd337654 Mon Sep 17 00:00:00 2001 From: Hugo Hromic Date: Tue, 5 Sep 2023 20:57:38 +0100 Subject: [PATCH 4/6] Update cmd/avrogen/main.go Co-authored-by: Nicholas Wiersma --- cmd/avrogen/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/avrogen/main.go b/cmd/avrogen/main.go index 818bada..d770fa9 100644 --- a/cmd/avrogen/main.go +++ b/cmd/avrogen/main.go @@ -31,7 +31,7 @@ func realMain(args []string, out, dumpout io.Writer) int { 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 (dump to stdout if not provided).") + flgs.StringVar(&cfg.Out, "o", "", "The output file path to write to instead of stdout.") flgs.StringVar(&cfg.Tags, "tags", "", "The additional field tags :{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.") From 381d8a4eb424dd786c62c105558349949808ebec Mon Sep 17 00:00:00 2001 From: Hugo Hromic Date: Tue, 5 Sep 2023 21:04:56 +0100 Subject: [PATCH 5/6] docouple file opening/writing/closing for better code --- cmd/avrogen/main.go | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/cmd/avrogen/main.go b/cmd/avrogen/main.go index d770fa9..3e94091 100644 --- a/cmd/avrogen/main.go +++ b/cmd/avrogen/main.go @@ -79,17 +79,26 @@ func realMain(args []string, out, dumpout io.Writer) int { return 3 } - if cfg.Out == "" { - if _, err = dumpout.Write(formatted); err != nil { - _, _ = fmt.Fprintf(out, "Error: could not write to stdout: %v\n", err) + writer := dumpout + if cfg.Out != "" { + if writer, err = os.Create(cfg.Out); err != nil { + _, _ = fmt.Fprintf(out, "Error: could not create output file: %v\n", err) return 4 } - } else { - if err = os.WriteFile(cfg.Out, formatted, 0o600); err != nil { - _, _ = fmt.Fprintf(out, "Error: could not write file: %v\n", err) + } + + if _, err := writer.Write(formatted); err != nil { + _, _ = fmt.Fprintf(out, "Error: could not write code: %v\n", err) + return 4 + } + + if f, ok := writer.(*os.File); ok { + if err := f.Close(); err != nil { + _, _ = fmt.Fprintf(out, "Error: could not close output file: %v\n", err) return 4 } } + return 0 } From 0ec15de3a53f66091e6ccfeb32e07853cced5180 Mon Sep 17 00:00:00 2001 From: Hugo Hromic Date: Wed, 6 Sep 2023 00:18:36 +0100 Subject: [PATCH 6/6] defer file close after opening --- cmd/avrogen/main.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/cmd/avrogen/main.go b/cmd/avrogen/main.go index 3e94091..7ba35a7 100644 --- a/cmd/avrogen/main.go +++ b/cmd/avrogen/main.go @@ -81,10 +81,14 @@ func realMain(args []string, out, dumpout io.Writer) int { writer := dumpout if cfg.Out != "" { - if writer, err = os.Create(cfg.Out); err != nil { + 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 { @@ -92,13 +96,6 @@ func realMain(args []string, out, dumpout io.Writer) int { return 4 } - if f, ok := writer.(*os.File); ok { - if err := f.Close(); err != nil { - _, _ = fmt.Fprintf(out, "Error: could not close output file: %v\n", err) - return 4 - } - } - return 0 }