@@ -37,6 +37,7 @@ import (
3737
3838 cueast "cuelang.org/go/cue/ast"
3939 "cuelang.org/go/cue/ast/astutil"
40+ "cuelang.org/go/cue/errors"
4041 "cuelang.org/go/cue/format"
4142 "cuelang.org/go/cue/literal"
4243 "cuelang.org/go/cue/load"
@@ -218,6 +219,8 @@ restrictive enum interpretation of #Switch remains.
218219
219220 cmd .Flags ().StringP (string (flagPackage ), "p" , "" , "package name for generated CUE files" )
220221
222+ cmd .Flags ().String (string (flagOutFile ), "" , "write generated CUE schema to the given file or stdout (only allows for one package)" )
223+
221224 return cmd
222225}
223226
@@ -247,8 +250,9 @@ func (e *extractor) filter(name string) bool {
247250type extractor struct {
248251 cmd * Command
249252
250- allPkgs map [string ]* packages.Package
251- done map [string ]bool
253+ allPkgs map [string ]* packages.Package
254+ rootPkgs map [string ]bool
255+ done map [string ]bool
252256
253257 // per package
254258 pkg * packages.Package
@@ -357,6 +361,10 @@ var toString = []*types.Interface{
357361// - consider not including types with any dropped fields.
358362
359363func extract (cmd * Command , args []string ) error {
364+ if flagLocal .IsSet (cmd ) && flagOutFile .IsSet (cmd ) {
365+ return errors .New ("--local and --outfile are mutually exclusive" )
366+ }
367+
360368 // TODO the CUE load using "." (below) assumes that a CUE module and a Go
361369 // module will exist within the same directory (more precisely a Go module
362370 // could be nested within a CUE module), such that the module path in any
@@ -387,9 +395,10 @@ func extract(cmd *Command, args []string) error {
387395 }
388396
389397 e := extractor {
390- cmd : cmd ,
391- allPkgs : map [string ]* packages.Package {},
392- orig : map [types.Type ]* ast.StructType {},
398+ cmd : cmd ,
399+ allPkgs : map [string ]* packages.Package {},
400+ rootPkgs : map [string ]bool {},
401+ orig : map [types.Type ]* ast.StructType {},
393402 }
394403
395404 e .initExclusions (flagExclude .String (cmd ))
@@ -398,9 +407,14 @@ func extract(cmd *Command, args []string) error {
398407
399408 for _ , p := range pkgs {
400409 e .done [p .PkgPath ] = true
410+ e .rootPkgs [p .PkgPath ] = true
401411 e .addPackage (p )
402412 }
403413
414+ if flagOutFile .IsSet (cmd ) && len (e .rootPkgs ) > 1 {
415+ return errors .New ("--outfile only allows for one package to be specified" )
416+ }
417+
404418 for _ , p := range pkgs {
405419 if err := e .extractPkg (root , p ); err != nil {
406420 return err
@@ -439,6 +453,10 @@ func (e *extractor) extractPkg(root string, p *packages.Package) error {
439453
440454 e .recordTypeInfo (p )
441455
456+ if ! e .rootPkgs [p .PkgPath ] && flagOutFile .IsSet (e .cmd ) {
457+ return nil
458+ }
459+
442460 e .consts = map [string ][]string {}
443461
444462 for _ , f := range p .Syntax {
@@ -462,8 +480,10 @@ func (e *extractor) extractPkg(root string, p *packages.Package) error {
462480 }
463481 }
464482
465- if err := os .MkdirAll (dir , 0777 ); err != nil {
466- return err
483+ if ! flagOutFile .IsSet (e .cmd ) {
484+ if err := os .MkdirAll (dir , 0777 ); err != nil {
485+ return err
486+ }
467487 }
468488
469489 e .usedPkgs = map [string ]bool {}
@@ -473,7 +493,21 @@ func (e *extractor) extractPkg(root string, p *packages.Package) error {
473493 args += " --exclude=" + e .exclude
474494 }
475495
496+ pName := flagPackage .String (e .cmd )
497+ if pName == "" {
498+ pName = p .Name
499+ }
500+
501+ var (
502+ decls []cueast.Decl
503+ cuePkg * cueast.Package
504+ )
476505 for i , f := range p .Syntax {
506+ if cuePkg == nil || ! flagOutFile .IsSet (e .cmd ) {
507+ cuePkg = & cueast.Package {Name : e .ident (pName , false )}
508+ decls = nil
509+ }
510+
477511 e .cmap = ast .NewCommentMap (p .Fset , f , f .Comments )
478512
479513 e .pkgNames = map [string ]pkgInfo {}
@@ -494,25 +528,24 @@ func (e *extractor) extractPkg(root string, p *packages.Package) error {
494528 e .pkgNames [pkgPath ] = info
495529 }
496530
497- decls := []cueast.Decl {}
531+ var fileDecls []cueast.Decl
498532 for _ , d := range f .Decls {
499533 switch d := d .(type ) {
500534 case * ast.GenDecl :
501- decls = append (decls , e .reportDecl (d )... )
535+ fileDecls = append (fileDecls , e .reportDecl (d )... )
502536 }
503537 }
504538
505- if len (decls ) == 0 && f .Doc == nil {
539+ if len (fileDecls ) == 0 && f .Doc == nil && ! flagOutFile . IsSet ( e . cmd ) {
506540 continue
507541 }
542+ decls = append (decls , fileDecls ... )
508543
509- pName := flagPackage .String (e .cmd )
510- if pName == "" {
511- pName = p .Name
512- }
544+ addDoc (f .Doc , cuePkg )
513545
514- pkg := & cueast.Package {Name : e .ident (pName , false )}
515- addDoc (f .Doc , pkg )
546+ if flagOutFile .IsSet (e .cmd ) && i != len (p .Syntax )- 1 {
547+ continue
548+ }
516549
517550 f := & cueast.File {Decls : []cueast.Decl {
518551 & cueast.CommentGroup {List : []* cueast.Comment {
@@ -521,7 +554,7 @@ func (e *extractor) extractPkg(root string, p *packages.Package) error {
521554 & cueast.CommentGroup {List : []* cueast.Comment {
522555 {Text : "//cue:generate cue get go " + args },
523556 }},
524- pkg ,
557+ cuePkg ,
525558 }}
526559 f .Decls = append (f .Decls , decls ... )
527560
@@ -530,16 +563,29 @@ func (e *extractor) extractPkg(root string, p *packages.Package) error {
530563 }
531564
532565 file := filepath .Base (p .CompiledGoFiles [i ])
533-
534566 file = strings .Replace (file , ".go" , "_go" , 1 )
535567 file += "_gen.cue"
568+ filePath := filepath .Join (dir , file )
569+
536570 b , err := format .Node (f , format .Simplify ())
537571 if err != nil {
538572 return err
539573 }
540- err = os .WriteFile (filepath .Join (dir , file ), b , 0666 )
541- if err != nil {
542- return err
574+
575+ if flagOutFile .IsSet (e .cmd ) {
576+ filePath = flagOutFile .String (e .cmd )
577+ }
578+
579+ if filePath == "-" {
580+ _ , err = os .Stdout .Write (b )
581+ if err != nil {
582+ return err
583+ }
584+ } else {
585+ err = os .WriteFile (filePath , b , 0666 )
586+ if err != nil {
587+ return err
588+ }
543589 }
544590 }
545591
@@ -549,10 +595,10 @@ func (e *extractor) extractPkg(root string, p *packages.Package) error {
549595 }
550596 }
551597
552- for path := range e .usedPkgs {
553- if ! e .done [path ] {
554- e .done [path ] = true
555- if err := e .extractPkg (root , e .allPkgs [path ]); err != nil {
598+ for pkgPath := range e .usedPkgs {
599+ if ! e .done [pkgPath ] {
600+ e .done [pkgPath ] = true
601+ if err := e .extractPkg (root , e .allPkgs [pkgPath ]); err != nil {
556602 return err
557603 }
558604 }
@@ -883,7 +929,7 @@ func makeDoc(g *ast.CommentGroup, isDoc bool) *cueast.CommentGroup {
883929 // The parser has given us exactly the comment text.
884930 switch c [1 ] {
885931 case '/' :
886- //-style comment (no newline at the end)
932+ // -style comment (no newline at the end)
887933 a = append (a , & cueast.Comment {Text : c })
888934
889935 case '*' :
0 commit comments