cmd/gotext: fix misbehaviors

The existing implementation has misbehaviors described in golang/go#56842:

- `gotext extract` ignores `lang` flag
- `gotext generate` ignores `out` flag
- Misbehavior of `gotext generate` when no flag specified

This commit fixes these bugs:

- Update `Command.UsageLine` for `cmdGenerate`
- Fix flag misbehaviors by encapsuling flag definition statements into individual `Command.Init()` functions of commands
- Fix `gotext generate` misbehavior by executing `pipeline.State.Merge()` before `pipeline.State.Generate()`

Fixes golang/go#56842

Change-Id: Id5e324b573b2b389bec22181482a97445230d0cc
Reviewed-on: https://go-review.googlesource.com/c/text/+/452115
Auto-Submit: Dmitri Shuralyov <dmitshur@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Run-TryBot: Dmitri Shuralyov <dmitshur@golang.org>
diff --git a/cmd/gotext/doc.go b/cmd/gotext/doc.go
index d363ae2..fcf3605 100644
--- a/cmd/gotext/doc.go
+++ b/cmd/gotext/doc.go
@@ -46,5 +46,5 @@
 //
 // Usage:
 //
-//	gotext generate <package>
+//	gotext generate <package> [-out <gofile>]
 package main
diff --git a/cmd/gotext/extract.go b/cmd/gotext/extract.go
index 103d7e6..80a3a72 100644
--- a/cmd/gotext/extract.go
+++ b/cmd/gotext/extract.go
@@ -14,16 +14,17 @@
 // - handle features (gender, plural)
 // - message rewriting
 
-func init() {
-	lang = cmdExtract.Flag.String("lang", "en-US", "comma-separated list of languages to process")
-}
-
 var cmdExtract = &Command{
+	Init:      initExtract,
 	Run:       runExtract,
 	UsageLine: "extract <package>*",
 	Short:     "extracts strings to be translated from code",
 }
 
+func initExtract(cmd *Command) {
+	lang = cmd.Flag.String("lang", "en-US", "comma-separated list of languages to process")
+}
+
 func runExtract(cmd *Command, config *pipeline.Config, args []string) error {
 	config.Packages = args
 	state, err := pipeline.Extract(config)
diff --git a/cmd/gotext/generate.go b/cmd/gotext/generate.go
index 36820df..77b3c81 100644
--- a/cmd/gotext/generate.go
+++ b/cmd/gotext/generate.go
@@ -8,14 +8,15 @@
 	"golang.org/x/text/message/pipeline"
 )
 
-func init() {
-	out = cmdGenerate.Flag.String("out", "", "output file to write to")
+var cmdGenerate = &Command{
+	Init:      initGenerate,
+	Run:       runGenerate,
+	UsageLine: "generate <package> [-out <gofile>]",
+	Short:     "generates code to insert translated messages",
 }
 
-var cmdGenerate = &Command{
-	Run:       runGenerate,
-	UsageLine: "generate <package>",
-	Short:     "generates code to insert translated messages",
+func initGenerate(cmd *Command) {
+	out = cmd.Flag.String("out", "", "output file to write to")
 }
 
 func runGenerate(cmd *Command, config *pipeline.Config, args []string) error {
@@ -27,5 +28,8 @@
 	if err := s.Import(); err != nil {
 		return wrap(err, "import failed")
 	}
+	if err := s.Merge(); err != nil {
+		return wrap(err, "merge failed")
+	}
 	return wrap(s.Generate(), "generation failed")
 }
diff --git a/cmd/gotext/main.go b/cmd/gotext/main.go
index aad1d4a..f69ea93 100644
--- a/cmd/gotext/main.go
+++ b/cmd/gotext/main.go
@@ -35,6 +35,10 @@
 }
 
 var (
+	lang      *string
+	out       *string
+	overwrite *bool
+
 	srcLang = flag.String("srclang", "en-US", "the source-code language")
 	dir     = flag.String("dir", "locales", "default subdirectory to store translation files")
 )
@@ -57,6 +61,9 @@
 // A Command is an implementation of a go command
 // like go build or go fix.
 type Command struct {
+	// Init initializes the flag set of the command.
+	Init func(cmd *Command)
+
 	// Run runs the command.
 	// The args are the arguments after the command name.
 	Run func(cmd *Command, c *pipeline.Config, args []string) error
@@ -139,6 +146,7 @@
 
 	for _, cmd := range commands {
 		if cmd.Name() == args[0] && cmd.Runnable() {
+			cmd.Init(cmd)
 			cmd.Flag.Usage = func() { cmd.Usage() }
 			cmd.Flag.Parse(args[1:])
 			args = cmd.Flag.Args()
@@ -331,6 +339,9 @@
 }
 
 func getLangs() (tags []language.Tag) {
+	if lang == nil {
+		return []language.Tag{language.AmericanEnglish}
+	}
 	for _, t := range strings.Split(*lang, ",") {
 		if t == "" {
 			continue
diff --git a/cmd/gotext/rewrite.go b/cmd/gotext/rewrite.go
index 9702ca7..5a6fd3a 100644
--- a/cmd/gotext/rewrite.go
+++ b/cmd/gotext/rewrite.go
@@ -19,15 +19,8 @@
 // - handle features (gender, plural)
 // - message rewriting
 
-func init() {
-	overwrite = cmdRewrite.Flag.Bool("w", false, "write files in place")
-}
-
-var (
-	overwrite *bool
-)
-
 var cmdRewrite = &Command{
+	Init:      initRewrite,
 	Run:       runRewrite,
 	UsageLine: "rewrite <package>",
 	Short:     "rewrites fmt functions to use a message Printer",
@@ -39,6 +32,10 @@
 `,
 }
 
+func initRewrite(cmd *Command) {
+	overwrite = cmd.Flag.Bool("w", false, "write files in place")
+}
+
 func runRewrite(cmd *Command, _ *pipeline.Config, args []string) error {
 	var w io.Writer
 	if !*overwrite {
diff --git a/cmd/gotext/update.go b/cmd/gotext/update.go
index 1260750..50f1c2d 100644
--- a/cmd/gotext/update.go
+++ b/cmd/gotext/update.go
@@ -14,22 +14,18 @@
 // - handle features (gender, plural)
 // - message rewriting
 
-var (
-	lang *string
-	out  *string
-)
-
-func init() {
-	lang = cmdUpdate.Flag.String("lang", "en-US", "comma-separated list of languages to process")
-	out = cmdUpdate.Flag.String("out", "", "output file to write to")
-}
-
 var cmdUpdate = &Command{
+	Init:      initUpdate,
 	Run:       runUpdate,
 	UsageLine: "update <package>* [-out <gofile>]",
 	Short:     "merge translations and generate catalog",
 }
 
+func initUpdate(cmd *Command) {
+	lang = cmd.Flag.String("lang", "en-US", "comma-separated list of languages to process")
+	out = cmd.Flag.String("out", "", "output file to write to")
+}
+
 func runUpdate(cmd *Command, config *pipeline.Config, args []string) error {
 	config.Packages = args
 	state, err := pipeline.Extract(config)
diff --git a/message/pipeline/generate.go b/message/pipeline/generate.go
index bb1f85b..f747c37 100644
--- a/message/pipeline/generate.go
+++ b/message/pipeline/generate.go
@@ -8,6 +8,7 @@
 	"fmt"
 	"go/build"
 	"io"
+	"os"
 	"path/filepath"
 	"regexp"
 	"sort"
@@ -52,6 +53,10 @@
 		gopath := filepath.SplitList(build.Default.GOPATH)[0]
 		path = filepath.Join(gopath, "src", filepath.FromSlash(pkgs[0].Pkg.Path()))
 	}
+	if len(s.Config.GenFile) == 0 {
+		cw.WriteGo(os.Stdout, pkg, "")
+		return nil
+	}
 	if filepath.IsAbs(s.Config.GenFile) {
 		path = s.Config.GenFile
 	} else {