Skip to content

Commit 8e5eeab

Browse files
committed
cmd/cue/cmd: parseArgs: split values and schemas early
This will allow schema information to be used for the parsing of values. Issue #5 Change-Id: I7ab2bfb56666ba392b19274bae59da0152e16d9a Reviewed-on: https://cue-review.googlesource.com/c/cue/+/9305 Reviewed-by: Marcel van Lohuizen <[email protected]> Reviewed-by: CUE cueckoo <[email protected]>
1 parent 75d0180 commit 8e5eeab

File tree

2 files changed

+132
-65
lines changed

2 files changed

+132
-65
lines changed

cmd/cue/cmd/common.go

Lines changed: 126 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ type buildPlan struct {
129129
// the instance is also included in insts.
130130
importing bool
131131
mergeData bool // do not merge individual data files.
132-
orphaned []*build.File
132+
orphaned []*decoderInfo
133133
orphanInstance *build.Instance
134134
// imported files are files that were orphaned in the build instance, but
135135
// were placed in the instance by using one the --files, --list or --path
@@ -206,7 +206,7 @@ type streamingIterator struct {
206206
base cue.Value
207207
b *buildPlan
208208
cfg *encoding.Config
209-
a []*build.File
209+
a []*decoderInfo
210210
dec *encoding.Decoder
211211
v cue.Value
212212
f *ast.File
@@ -278,7 +278,7 @@ func (i *streamingIterator) scan() bool {
278278
return false
279279
}
280280

281-
i.dec = encoding.NewDecoder(i.a[0], i.cfg)
281+
i.dec = i.a[0].dec(i.b)
282282
if i.e = i.dec.Err(); i.e != nil {
283283
return false
284284
}
@@ -383,6 +383,87 @@ func newBuildPlan(cmd *Command, args []string, cfg *config) (p *buildPlan, err e
383383
return p, nil
384384
}
385385

386+
type decoderInfo struct {
387+
file *build.File
388+
d *encoding.Decoder // may be nil if delayed
389+
}
390+
391+
func (d *decoderInfo) dec(b *buildPlan) *encoding.Decoder {
392+
if d.d == nil {
393+
d.d = encoding.NewDecoder(d.file, b.encConfig)
394+
}
395+
return d.d
396+
}
397+
398+
func (d *decoderInfo) close() {
399+
if d.d != nil {
400+
d.d.Close()
401+
}
402+
}
403+
404+
// getDecoders takes the orphaned files of the given instance and splits them in
405+
// schemas and values, saving the build.File and encoding.Decoder in the
406+
// returned slices. It is up to the caller to Close any of the decoders that are
407+
// returned.
408+
func (p *buildPlan) getDecoders(b *build.Instance) (schemas, values []*decoderInfo, err error) {
409+
for _, f := range b.OrphanedFiles {
410+
switch f.Encoding {
411+
case build.Protobuf, build.YAML, build.JSON, build.JSONL, build.Text:
412+
default:
413+
return schemas, values, errors.Newf(token.NoPos,
414+
"unsupported encoding %q", f.Encoding)
415+
}
416+
417+
// We add the module root to the path if there is a module defined.
418+
c := *p.encConfig
419+
if b.Module != "" {
420+
c.ProtoPath = append(c.ProtoPath, b.Root)
421+
}
422+
d := encoding.NewDecoder(f, &c)
423+
424+
fi, err := filetypes.FromFile(f, p.cfg.outMode)
425+
if err != nil {
426+
return schemas, values, err
427+
}
428+
switch {
429+
// case !fi.Schema: // TODO: value/schema/auto
430+
// values = append(values, d)
431+
case fi.Form != build.Schema && fi.Form != build.Final:
432+
values = append(values, &decoderInfo{f, d})
433+
434+
case f.Interpretation != build.Auto:
435+
schemas = append(schemas, &decoderInfo{f, d})
436+
437+
case d.Interpretation() == "":
438+
values = append(values, &decoderInfo{f, d})
439+
440+
default:
441+
schemas = append(schemas, &decoderInfo{f, d})
442+
}
443+
}
444+
return schemas, values, nil
445+
}
446+
447+
// importFiles imports orphan files for existing instances. Note that during
448+
// import, both schemas and non-schemas are placed (TODO: should we allow schema
449+
// mode here as well? It seems that the existing package should have enough
450+
// typing to allow for schemas).
451+
//
452+
// It is a separate call to allow closing decoders between processing each
453+
// package.
454+
func (p *buildPlan) importFiles(b *build.Instance) error {
455+
// TODO: assume textproto is imported at top-level or just ignore them.
456+
457+
schemas, values, err := p.getDecoders(b)
458+
for _, d := range append(schemas, values...) {
459+
defer d.close()
460+
}
461+
if err != nil {
462+
return err
463+
}
464+
return p.placeOrphans(b, append(schemas, values...))
465+
}
466+
386467
func parseArgs(cmd *Command, args []string, cfg *config) (p *buildPlan, err error) {
387468
p, err = newBuildPlan(cmd, args, cfg)
388469
if err != nil {
@@ -405,7 +486,7 @@ func parseArgs(cmd *Command, args []string, cfg *config) (p *buildPlan, err erro
405486
switch {
406487
case !b.User:
407488
if p.importing {
408-
if err = p.placeOrphans(b); err != nil {
489+
if err := p.importFiles(b); err != nil {
409490
return nil, err
410491
}
411492
}
@@ -420,50 +501,27 @@ func parseArgs(cmd *Command, args []string, cfg *config) (p *buildPlan, err erro
420501
}
421502
}
422503

423-
switch b := p.orphanInstance; {
424-
case b == nil:
425-
case p.usePlacement() || p.importing:
426-
p.insts = append(p.insts, b)
427-
if err = p.placeOrphans(b); err != nil {
504+
if b := p.orphanInstance; b != nil {
505+
schemas, values, err := p.getDecoders(b)
506+
for _, d := range append(schemas, values...) {
507+
defer d.close()
508+
}
509+
if err != nil {
428510
return nil, err
429511
}
430512

431-
default:
432-
for _, f := range b.OrphanedFiles {
433-
switch f.Encoding {
434-
case build.Protobuf, build.YAML, build.JSON, build.Text:
435-
default:
436-
return nil, errors.Newf(token.NoPos,
437-
"unsupported encoding %q", f.Encoding)
438-
}
513+
// TODO(v0.4.0): what to do:
514+
// - when there is already a single instance
515+
// - when we are importing and there are schemas and values.
516+
517+
if values == nil {
518+
values, schemas = schemas, values
439519
}
440520

441-
// TODO: this processing could probably be delayed, or at least
442-
// simplified. The main reason to do this here is to allow interpreting
443-
// the --schema/-d flag, while allowing to use this for OpenAPI and
444-
// JSON Schema in auto-detect mode.
445-
buildFiles := []*build.File{}
446-
for _, f := range b.OrphanedFiles {
447-
d := encoding.NewDecoder(f, p.encConfig)
521+
for _, di := range schemas {
522+
d := di.dec(p)
448523
for ; !d.Done(); d.Next() {
449-
file := d.File()
450-
sub := &build.File{
451-
Filename: d.Filename(),
452-
Encoding: f.Encoding,
453-
Interpretation: d.Interpretation(),
454-
Form: f.Form,
455-
Tags: f.Tags,
456-
Source: file,
457-
}
458-
if (!p.mergeData || p.schema != nil) && d.Interpretation() == "" {
459-
switch sub.Encoding {
460-
case build.YAML, build.JSON, build.Text:
461-
p.orphaned = append(p.orphaned, sub)
462-
continue
463-
}
464-
}
465-
buildFiles = append(buildFiles, sub)
466-
if err := b.AddSyntax(file); err != nil {
524+
if err := b.AddSyntax(d.File()); err != nil {
467525
return nil, err
468526
}
469527
}
@@ -472,11 +530,34 @@ func parseArgs(cmd *Command, args []string, cfg *config) (p *buildPlan, err erro
472530
}
473531
}
474532

533+
// TODO(v0.4.0): if schema is specified and data format is JSON, YAML or
534+
// Protobuf, reinterpret with schema.
535+
536+
switch {
537+
case p.usePlacement() || p.importing:
538+
if err = p.placeOrphans(b, values); err != nil {
539+
return nil, err
540+
}
541+
542+
case !p.mergeData || p.schema != nil:
543+
p.orphaned = values
544+
545+
default:
546+
for _, di := range values {
547+
d := di.dec(p)
548+
for ; !d.Done(); d.Next() {
549+
if err := b.AddSyntax(d.File()); err != nil {
550+
return nil, err
551+
}
552+
}
553+
if err := d.Err(); err != nil {
554+
return nil, err
555+
}
556+
}
557+
}
558+
475559
if len(b.Files) > 0 {
476560
p.insts = append(p.insts, b)
477-
} else if len(p.orphaned) == 0 {
478-
// Instance with only a single build: just print the file.
479-
p.orphaned = append(p.orphaned, buildFiles...)
480561
}
481562
}
482563

cmd/cue/cmd/orphans.go

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
"cuelang.org/go/cue/parser"
2929
"cuelang.org/go/cue/token"
3030
"cuelang.org/go/internal"
31-
"cuelang.org/go/internal/encoding"
3231
)
3332

3433
// This file contains logic for placing orphan files within a CUE namespace.
@@ -60,8 +59,7 @@ func (b *buildPlan) parsePlacementFlags() error {
6059
return nil
6160
}
6261

63-
func (b *buildPlan) placeOrphans(i *build.Instance) error {
64-
62+
func (b *buildPlan) placeOrphans(i *build.Instance, a []*decoderInfo) error {
6563
pkg := b.encConfig.PkgName
6664
if pkg == "" {
6765
pkg = i.PkgName
@@ -79,19 +77,12 @@ func (b *buildPlan) placeOrphans(i *build.Instance) error {
7977
return err
8078
}
8179

82-
for _, f := range i.OrphanedFiles {
83-
if !i.User && !re.MatchString(filepath.Base(f.Filename)) {
80+
for _, di := range a {
81+
if !i.User && !re.MatchString(filepath.Base(di.file.Filename)) {
8482
continue
8583
}
8684

87-
// We add the module root to the path if there is a module defined.
88-
c := *b.encConfig
89-
if i.Module != "" {
90-
c.ProtoPath = append(c.ProtoPath, i.Root)
91-
}
92-
93-
d := encoding.NewDecoder(f, &c)
94-
defer d.Close()
85+
d := di.dec(b)
9586

9687
var objs []*ast.File
9788

@@ -123,12 +114,12 @@ func (b *buildPlan) placeOrphans(i *build.Instance) error {
123114
if b.importing && len(objs) > 1 && len(b.path) == 0 && !b.useList {
124115
return fmt.Errorf(
125116
"%s, %s, or %s flag needed to handle multiple objects in file %s",
126-
flagPath, flagList, flagFiles, shortFile(i.Root, f))
117+
flagPath, flagList, flagFiles, shortFile(i.Root, di.file))
127118
}
128119

129120
if !b.useList && len(b.path) == 0 && !b.useContext {
130121
for _, f := range objs {
131-
if pkg := c.PkgName; pkg != "" {
122+
if pkg := b.encConfig.PkgName; pkg != "" {
132123
internal.SetPackage(f, pkg, false)
133124
}
134125
files = append(files, f)
@@ -149,11 +140,6 @@ func (b *buildPlan) placeOrphans(i *build.Instance) error {
149140
if err := i.AddSyntax(f); err != nil {
150141
return err
151142
}
152-
i.BuildFiles = append(i.BuildFiles, &build.File{
153-
Filename: f.Filename,
154-
Encoding: build.CUE,
155-
Source: f,
156-
})
157143
}
158144
return nil
159145
}

0 commit comments

Comments
 (0)