diff --git a/cmd/cue/cmd/exp.go b/cmd/cue/cmd/exp.go index ff698878d..aa5a40b38 100644 --- a/cmd/cue/cmd/exp.go +++ b/cmd/cue/cmd/exp.go @@ -22,6 +22,7 @@ import ( "path/filepath" "time" + "cuelang.org/go/cue/ast" "cuelang.org/go/cue/build" "cuelang.org/go/cue/cuecontext" "cuelang.org/go/cue/load" @@ -85,6 +86,8 @@ To ensure that the resulting Go code works, any imported CUE packages or referenced CUE definitions are transitively generated as well. Code is generated in each CUE package directory at cue_types_${pkgname}_gen.go, where the package name is omitted from the filename if it is implied by the import path. +The --outfile flag can be used to write the output for a single CUE package to a specific file, +or to stdout when set to "-". Generated Go type and field names may differ from the original CUE names by default. For instance, an exported definition "#foo" becomes "Foo", @@ -124,13 +127,47 @@ The default is "zero", representing a missing field as the zero value. `[1:], RunE: mkRunE(c, runExpGenGoTypes), } + cmd.Flags().StringP(string(flagOutFile), "o", "", "generate one Go file for a single CUE package") return cmd } func runExpGenGoTypes(cmd *Command, args []string) error { + outFile := flagOutFile.String(cmd) insts := load.Instances(args, &load.Config{}) - return gotypes.Generate(cmd.ctx, insts...) + writeFile := defaultGenGoTypesWrite + if outFile != "" { + if len(insts) != 1 { + return fmt.Errorf("--outfile only allows for one package to be specified") + } + writeFile = func(_ *build.Instance, data []byte) error { + if outFile == "-" { + _, err := os.Stdout.Write(data) + return err + } + return os.WriteFile(outFile, data, 0o666) + } + } + return gotypes.Generate(cmd.ctx, writeFile, insts...) +} + +// defaultGenGoTypesWrite writes each instance's generated Go source to its default filename. +// +// The generated file is named after the CUE package, not the generated Go package, +// as we can have multiple CUE packages in one directory all generating to one Go package. +// To keep the filename short for common cases, if we are generating a CUE package +// whose package name is implied from its import path, omit the package name element. +func defaultGenGoTypesWrite(inst *build.Instance, data []byte) error { + basename := "cue_types_gen.go" + ip := ast.ParseImportPath(inst.ImportPath) + ip1 := ip + ip1.Qualifier = "" + ip1.ExplicitQualifier = false + ip1 = ast.ParseImportPath(ip1.String()) + if ip.Qualifier != ip1.Qualifier { + basename = fmt.Sprintf("cue_types_%s_gen.go", inst.PkgName) + } + return os.WriteFile(filepath.Join(inst.Dir, basename), data, 0o666) } func newExpWritefsCmd(c *Command) *cobra.Command { diff --git a/cmd/cue/cmd/testdata/script/exp_gengotypes_outfile.txtar b/cmd/cue/cmd/testdata/script/exp_gengotypes_outfile.txtar new file mode 100644 index 000000000..00b5e4cbc --- /dev/null +++ b/cmd/cue/cmd/testdata/script/exp_gengotypes_outfile.txtar @@ -0,0 +1,36 @@ +exec cue exp gengotypes --outfile cue_types.go ./pkg1 +cmp cue_types.go cue_types.go.golden +! exists pkg1/cue_types_gen.go + +# -o shorthand works too. +exec cue exp gengotypes -o cue_types2.go ./pkg1 +cmp cue_types2.go cue_types.go.golden + +# stdout works too. +exec cue exp gengotypes --outfile - ./pkg1 +cmp stdout cue_types.go.golden + +# Multiple packages are not allowed with --outfile. +! exec cue exp gengotypes --outfile out.go ./... +stderr 'only allows for one package' + +-- cue.mod/module.cue -- +module: "foo.test/root" +language: version: "v0.13.0" +-- pkg1/pkg1.cue -- +package pkg1 + +#Foo: int +#Bar: string +-- pkg2/pkg2.cue -- +package pkg2 + +#Baz: bool +-- cue_types.go.golden -- +// Code generated by "cue exp gengotypes"; DO NOT EDIT. + +package pkg1 + +type Foo int64 + +type Bar string diff --git a/internal/encoding/gotypes/generate.go b/internal/encoding/gotypes/generate.go index 994a6a178..1cb0a2333 100644 --- a/internal/encoding/gotypes/generate.go +++ b/internal/encoding/gotypes/generate.go @@ -24,7 +24,6 @@ import ( gotoken "go/token" "maps" "os" - "path/filepath" "slices" "strconv" "strings" @@ -38,9 +37,14 @@ import ( "cuelang.org/go/cue/build" ) +// WriteFunc is a function that writes the generated Go source for an instance. +type WriteFunc func(inst *build.Instance, data []byte) error + // Generate produces Go type definitions from exported CUE definitions. // See the help text for `cue help exp gengotypes`. -func Generate(ctx *cue.Context, insts ...*build.Instance) error { +// +// writeFile is called to write the formatted Go source for each instance. +func Generate(ctx *cue.Context, writeFile WriteFunc, insts ...*build.Instance) error { // record which package instances have already been generated instDone := make(map[*build.Instance]bool) @@ -163,21 +167,6 @@ func Generate(ctx *cue.Context, insts ...*build.Instance) error { printf("\n\n") } - // The generated file is named after the CUE package, not the generated Go package, - // as we can have multiple CUE packages in one directory all generating to one Go package. - // To keep the filename short for common cases, if we are generating a CUE package - // whose package name is implied from its import path, omit the package name element. - basename := "cue_types_gen.go" - ip := ast.ParseImportPath(inst.ImportPath) - ip1 := ip - ip1.Qualifier = "" - ip1.ExplicitQualifier = false - ip1 = ast.ParseImportPath(ip1.String()) - if ip.Qualifier != ip1.Qualifier { - basename = fmt.Sprintf("cue_types_%s_gen.go", inst.PkgName) - } - outpath := filepath.Join(inst.Dir, basename) - formatted, err := goformat.Source(buf) if err != nil { // Showing the generated Go code helps debug where the syntax error is. @@ -187,10 +176,10 @@ func Generate(ctx *cue.Context, insts ...*build.Instance) error { for i, line := range lines { withLineNums = fmt.Appendf(withLineNums, "% 4d: %s\n", i+1, line) } - fmt.Fprintf(os.Stderr, "-- %s --\n%s\n--\n", filepath.ToSlash(outpath), withLineNums) + fmt.Fprintf(os.Stderr, "-- %s --\n%s\n--\n", inst.ImportPath, withLineNums) return err } - if err := os.WriteFile(outpath, formatted, 0o666); err != nil { + if err := writeFile(inst, formatted); err != nil { return err } }