blob: 4e291af4a78ece40fb6f94bb930196e6319f54f8 [file] [log] [blame]
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +01001// Copyright 2020 CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//go:generate go run gen.go
16
17package filetypes
18
19import (
20 "path/filepath"
21 "strings"
22
Marcel van Lohuizen845df052020-07-26 13:15:45 +020023 "cuelang.org/go/cue"
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +010024 "cuelang.org/go/cue/build"
25 "cuelang.org/go/cue/errors"
26 "cuelang.org/go/cue/token"
27)
28
29// Mode indicate the base mode of operation and indicates a different set of
30// defaults.
31type Mode int
32
33const (
34 Input Mode = iota // The default
35 Export
36 Def
Marcel van Lohuizen6dfecc32020-03-11 12:53:21 +010037 Eval
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +010038)
39
40func (m Mode) String() string {
41 switch m {
42 default:
43 return "input"
Marcel van Lohuizen6dfecc32020-03-11 12:53:21 +010044 case Eval:
45 return "eval"
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +010046 case Export:
47 return "export"
48 case Def:
49 return "def"
50 }
51}
52
53// FileInfo defines the parsing plan for a file.
54type FileInfo struct {
55 *build.File
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +010056
Marcel van Lohuizen9b1dff72020-02-28 14:07:19 +010057 Definitions bool `json:"definitions"` // include/allow definition fields
58 Data bool `json:"data"` // include/allow regular fields
59 Optional bool `json:"optional"` // include/allow definition fields
60 Constraints bool `json:"constraints"` // include/allow constraints
61 References bool `json:"references"` // don't resolve/allow references
62 Cycles bool `json:"cycles"` // cycles are permitted
63 KeepDefaults bool `json:"keepDefaults"` // select/allow default values
64 Incomplete bool `json:"incomplete"` // permit incomplete values
65 Imports bool `json:"imports"` // don't expand/allow imports
66 Stream bool `json:"stream"` // permit streaming
67 Docs bool `json:"docs"` // show/allow docs
68 Attributes bool `json:"attributes"` // include/allow attributes
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +010069}
70
71// FromFile return detailed file info for a given build file.
72// Encoding must be specified.
Marcel van Lohuizen9b1dff72020-02-28 14:07:19 +010073// TODO: mode should probably not be necessary here.
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +010074func FromFile(b *build.File, mode Mode) (*FileInfo, error) {
75 i := cuegenInstance.Value()
76 i = i.Unify(i.Lookup("modes", mode.String()))
77 v := i.LookupDef("FileInfo")
78 v = v.Fill(b)
79
80 if b.Encoding == "" {
81 ext := i.Lookup("extensions", filepath.Ext(b.Filename))
82 if ext.Exists() {
83 v = v.Unify(ext)
84 }
85 }
86
Marcel van Lohuizenf9f8e622020-03-31 17:17:26 +020087 interpretation, _ := v.Lookup("interpretation").String()
88 if b.Form != "" {
89 v = v.Unify(i.Lookup("forms", string(b.Form)))
90 // may leave some encoding-dependent options open in data mode.
91 } else if interpretation != "" {
92 // always sets schema form.
93 v = v.Unify(i.Lookup("interpretations", interpretation))
94 }
95 if interpretation == "" {
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +010096 s, err := v.Lookup("encoding").String()
97 if err != nil {
98 return nil, err
99 }
100 v = v.Unify(i.Lookup("encodings", s))
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100101 }
102
103 fi := &FileInfo{}
104 if err := v.Decode(fi); err != nil {
105 return nil, err
106 }
107 return fi, nil
108}
109
110// ParseArgs converts a sequence of command line arguments representing
111// files into a sequence of build file specifications.
112//
113// The arguments are of the form
114//
115// file* (spec: file+)*
116//
117// where file is a filename and spec is itself of the form
118//
119// tag[=value]('+'tag[=value])*
120//
121// A file type spec applies to all its following files and until a next spec
122// is found.
123//
124// Examples:
125// json: foo.data bar.data json+schema: bar.schema
126//
127func ParseArgs(args []string) (files []*build.File, err error) {
Marcel van Lohuizen9b1dff72020-02-28 14:07:19 +0100128 inst, v := parseType("", Input)
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100129
130 qualifier := ""
131 hasFiles := false
132
133 for i, s := range args {
134 a := strings.Split(s, ":")
135 switch {
Marcel van Lohuizen26e63ec2020-02-28 17:33:47 +0000136 case len(a) == 1 || len(a[0]) == 1: // filename
Marcel van Lohuizen9b1dff72020-02-28 14:07:19 +0100137 f, err := toFile(inst, v, s)
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100138 if err != nil {
139 return nil, err
140 }
141 files = append(files, f)
142 hasFiles = true
143
144 case len(a) > 2 || a[0] == "":
145 return nil, errors.Newf(token.NoPos,
146 "unsupported file name %q: may not have ':'", s)
147
148 case a[1] != "":
149 return nil, errors.Newf(token.NoPos, "cannot combine scope with file")
150
151 default: // scope
152 switch {
153 case i == len(args)-1:
154 qualifier = a[0]
155 fallthrough
156 case qualifier != "" && !hasFiles:
157 return nil, errors.Newf(token.NoPos, "scoped qualifier %q without file", qualifier+":")
158 }
Marcel van Lohuizen9b1dff72020-02-28 14:07:19 +0100159 inst, v = parseType(a[0], Input)
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100160 qualifier = a[0]
161 hasFiles = false
162 }
163 }
164
165 return files, nil
166}
167
168// ParseFile parses a single-argument file specifier, such as when a file is
169// passed to a command line argument.
170//
171// Example:
172// cue eval -o yaml:foo.data
173//
174func ParseFile(s string, mode Mode) (*build.File, error) {
175 scope := ""
176 file := s
177
178 if p := strings.LastIndexByte(s, ':'); p >= 0 {
179 scope = s[:p]
180 file = s[p+1:]
181 if scope == "" {
182 return nil, errors.Newf(token.NoPos, "unsupported file name %q: may not have ':", s)
183 }
184 }
185
186 if file == "" {
187 return nil, errors.Newf(token.NoPos, "empty file name in %q", s)
188 }
189
Marcel van Lohuizen9b1dff72020-02-28 14:07:19 +0100190 inst, val := parseType(scope, mode)
191 return toFile(inst, val, file)
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100192}
193
Marcel van Lohuizenf9f8e622020-03-31 17:17:26 +0200194func hasEncoding(v cue.Value) (concrete, hasDefault bool) {
195 enc := v.Lookup("encoding")
196 d, _ := enc.Default()
197 return enc.IsConcrete(), d.IsConcrete()
198}
199
Marcel van Lohuizen9b1dff72020-02-28 14:07:19 +0100200func toFile(i, v cue.Value, filename string) (*build.File, error) {
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100201 v = v.Fill(filename, "filename")
Marcel van Lohuizenf9f8e622020-03-31 17:17:26 +0200202
203 if concrete, hasDefault := hasEncoding(v); !concrete {
204 if filename == "-" {
205 if !hasDefault {
206 v = v.Unify(i.LookupDef("Default"))
Marcel van Lohuizen38362f82020-03-17 14:52:38 +0100207 }
Marcel van Lohuizenf9f8e622020-03-31 17:17:26 +0200208 } else if ext := filepath.Ext(filename); ext != "" {
209 if x := i.Lookup("extensions", ext); x.Exists() || !hasDefault {
210 v = v.Unify(x)
211 }
212 } else if !hasDefault {
213 return nil, errors.Newf(token.NoPos,
214 "no encoding specified for file %q", filename)
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100215 }
216 }
Marcel van Lohuizenf9f8e622020-03-31 17:17:26 +0200217
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100218 f := &build.File{}
219 if err := v.Decode(&f); err != nil {
220 return nil, err
221 }
222 return f, nil
223}
224
Marcel van Lohuizen9b1dff72020-02-28 14:07:19 +0100225func parseType(s string, mode Mode) (inst, val cue.Value) {
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100226 i := cuegenInstance.Value()
Marcel van Lohuizen9b1dff72020-02-28 14:07:19 +0100227 i = i.Unify(i.Lookup("modes", mode.String()))
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100228 v := i.LookupDef("File")
229
230 if s != "" {
231 for _, t := range strings.Split(s, "+") {
232 if p := strings.IndexByte(t, '='); p >= 0 {
233 v = v.Fill(t[p+1:], "tags", t[:p])
234 } else {
235 v = v.Unify(i.Lookup("tags", t))
236 }
237 }
238 }
239
Marcel van Lohuizen9b1dff72020-02-28 14:07:19 +0100240 return i, v
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100241}