diff --git a/cmd/cue/cmd/common.go b/cmd/cue/cmd/common.go index 6a53e5dca..b0195f48e 100644 --- a/cmd/cue/cmd/common.go +++ b/cmd/cue/cmd/common.go @@ -129,7 +129,7 @@ type buildPlan struct { // the instance is also included in insts. importing bool mergeData bool // do not merge individual data files. - orphaned []*build.File + orphaned []decoderInfo orphanInstance *build.Instance // imported files are files that were orphaned in the build instance, but // were placed in the instance by using one the --files, --list or --path @@ -206,7 +206,7 @@ type streamingIterator struct { base cue.Value b *buildPlan cfg *encoding.Config - a []*build.File + a []decoderInfo dec *encoding.Decoder v cue.Value f *ast.File @@ -278,7 +278,7 @@ func (i *streamingIterator) scan() bool { return false } - i.dec = encoding.NewDecoder(i.a[0], i.cfg) + i.dec = i.a[0].dec if i.e = i.dec.Err(); i.e != nil { return false } @@ -383,6 +383,72 @@ func newBuildPlan(cmd *Command, args []string, cfg *config) (p *buildPlan, err e return p, nil } +type decoderInfo struct { + file *build.File + dec *encoding.Decoder +} + +// getDecoders takes the orphaned files of the given instance and splits them in +// schemas and values, saving the build.File and encoding.Decoder in the +// returned slices. It is up to the caller to Close any of the decoders that are +// returned. +func (p *buildPlan) getDecoders(b *build.Instance) (schemas, values []decoderInfo, err error) { + for _, f := range b.OrphanedFiles { + switch f.Encoding { + case build.Protobuf, build.YAML, build.JSON, build.JSONL, build.Text: + default: + return schemas, values, errors.Newf(token.NoPos, + "unsupported encoding %q", f.Encoding) + } + + // We add the module root to the path if there is a module defined. + c := *p.encConfig + if b.Module != "" { + c.ProtoPath = append(c.ProtoPath, b.Root) + } + d := encoding.NewDecoder(f, &c) + + fi, err := filetypes.FromFile(f, p.cfg.outMode) + if err != nil { + return schemas, values, err + } + switch { + // case !fi.Schema: // TODO: value/schema/auto + // values = append(values, d) + case fi.Form != build.Schema && fi.Form != build.Final: + values = append(values, decoderInfo{f, d}) + + case f.Interpretation != build.Auto: + schemas = append(schemas, decoderInfo{f, d}) + + case d.Interpretation() == "": + values = append(values, decoderInfo{f, d}) + + default: + schemas = append(schemas, decoderInfo{f, d}) + } + } + return schemas, values, nil +} + +// importFiles imports orphan files for existing instances. Note that during +// import, both schemas and non-schemas are placed (TODO: should we allow schema +// mode here as well? It seems that the existing package should have enough +// typing to allow for schemas). +// +// It is a separate call to allow closing decoders between processing each +// package. +func (p *buildPlan) importFiles(b *build.Instance) error { + schemas, values, err := p.getDecoders(b) + for _, d := range append(schemas, values...) { + defer d.dec.Close() + } + if err != nil { + return err + } + return p.placeOrphans(b, append(schemas, values...)) +} + func parseArgs(cmd *Command, args []string, cfg *config) (p *buildPlan, err error) { p, err = newBuildPlan(cmd, args, cfg) if err != nil { @@ -405,7 +471,7 @@ func parseArgs(cmd *Command, args []string, cfg *config) (p *buildPlan, err erro switch { case !b.User: if p.importing { - if err = p.placeOrphans(b); err != nil { + if err := p.importFiles(b); err != nil { return nil, err } } @@ -420,50 +486,27 @@ func parseArgs(cmd *Command, args []string, cfg *config) (p *buildPlan, err erro } } - switch b := p.orphanInstance; { - case b == nil: - case p.usePlacement() || p.importing: - p.insts = append(p.insts, b) - if err = p.placeOrphans(b); err != nil { + if b := p.orphanInstance; b != nil { + schemas, values, err := p.getDecoders(b) + for _, d := range append(schemas, values...) { + defer d.dec.Close() + } + if err != nil { return nil, err } - default: - for _, f := range b.OrphanedFiles { - switch f.Encoding { - case build.Protobuf, build.YAML, build.JSON, build.Text: - default: - return nil, errors.Newf(token.NoPos, - "unsupported encoding %q", f.Encoding) - } + // TODO(v0.4.0): what to do: + // - when there is already a single instance + // - when we are importing and there are schemas and values. + + if values == nil { + values, schemas = schemas, values } - // TODO: this processing could probably be delayed, or at least - // simplified. The main reason to do this here is to allow interpreting - // the --schema/-d flag, while allowing to use this for OpenAPI and - // JSON Schema in auto-detect mode. - buildFiles := []*build.File{} - for _, f := range b.OrphanedFiles { - d := encoding.NewDecoder(f, p.encConfig) + for _, di := range schemas { + d := di.dec for ; !d.Done(); d.Next() { - file := d.File() - sub := &build.File{ - Filename: d.Filename(), - Encoding: f.Encoding, - Interpretation: d.Interpretation(), - Form: f.Form, - Tags: f.Tags, - Source: file, - } - if (!p.mergeData || p.schema != nil) && d.Interpretation() == "" { - switch sub.Encoding { - case build.YAML, build.JSON, build.Text: - p.orphaned = append(p.orphaned, sub) - continue - } - } - buildFiles = append(buildFiles, sub) - if err := b.AddSyntax(file); err != nil { + if err := b.AddSyntax(d.File()); err != nil { return nil, err } } @@ -472,11 +515,34 @@ func parseArgs(cmd *Command, args []string, cfg *config) (p *buildPlan, err erro } } + // TODO(v0.4.0): if schema is specified and data format is JSON, YAML or + // Protobuf, reinterpret with schema. + + switch { + case p.usePlacement() || p.importing: + if err = p.placeOrphans(b, values); err != nil { + return nil, err + } + + case !p.mergeData || p.schema != nil: + p.orphaned = values + + default: + for _, di := range values { + d := di.dec + for ; !d.Done(); d.Next() { + if err := b.AddSyntax(d.File()); err != nil { + return nil, err + } + } + if err := d.Err(); err != nil { + return nil, err + } + } + } + if len(b.Files) > 0 { p.insts = append(p.insts, b) - } else if len(p.orphaned) == 0 { - // Instance with only a single build: just print the file. - p.orphaned = append(p.orphaned, buildFiles...) } } diff --git a/cmd/cue/cmd/orphans.go b/cmd/cue/cmd/orphans.go index 818cb166a..b41f224f6 100644 --- a/cmd/cue/cmd/orphans.go +++ b/cmd/cue/cmd/orphans.go @@ -28,7 +28,6 @@ import ( "cuelang.org/go/cue/parser" "cuelang.org/go/cue/token" "cuelang.org/go/internal" - "cuelang.org/go/internal/encoding" ) // This file contains logic for placing orphan files within a CUE namespace. @@ -60,8 +59,7 @@ func (b *buildPlan) parsePlacementFlags() error { return nil } -func (b *buildPlan) placeOrphans(i *build.Instance) error { - +func (b *buildPlan) placeOrphans(i *build.Instance, a []decoderInfo) error { pkg := b.encConfig.PkgName if pkg == "" { pkg = i.PkgName @@ -79,19 +77,12 @@ func (b *buildPlan) placeOrphans(i *build.Instance) error { return err } - for _, f := range i.OrphanedFiles { - if !i.User && !re.MatchString(filepath.Base(f.Filename)) { + for _, di := range a { + if !i.User && !re.MatchString(filepath.Base(di.file.Filename)) { continue } - // We add the module root to the path if there is a module defined. - c := *b.encConfig - if i.Module != "" { - c.ProtoPath = append(c.ProtoPath, i.Root) - } - - d := encoding.NewDecoder(f, &c) - defer d.Close() + d := di.dec var objs []*ast.File @@ -123,12 +114,12 @@ func (b *buildPlan) placeOrphans(i *build.Instance) error { if b.importing && len(objs) > 1 && len(b.path) == 0 && !b.useList { return fmt.Errorf( "%s, %s, or %s flag needed to handle multiple objects in file %s", - flagPath, flagList, flagFiles, shortFile(i.Root, f)) + flagPath, flagList, flagFiles, shortFile(i.Root, di.file)) } if !b.useList && len(b.path) == 0 && !b.useContext { for _, f := range objs { - if pkg := c.PkgName; pkg != "" { + if pkg := b.encConfig.PkgName; pkg != "" { internal.SetPackage(f, pkg, false) } files = append(files, f) @@ -149,11 +140,6 @@ func (b *buildPlan) placeOrphans(i *build.Instance) error { if err := i.AddSyntax(f); err != nil { return err } - i.BuildFiles = append(i.BuildFiles, &build.File{ - Filename: f.Filename, - Encoding: build.CUE, - Source: f, - }) } return nil }