blob: 24987671ed4a16dfd47f5675dfe2240b8b99d32c [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) {
Marcel van Lohuizenf2a4a422020-10-08 15:21:08 +020075 // Handle common case. This allows certain test cases to be analyzed in
76 // isolation without interference from evaluating these files.
77 if mode == Input &&
78 b.Encoding == build.CUE &&
79 b.Form == build.Schema &&
80 b.Interpretation == "" {
81 return &FileInfo{
82 File: b,
83
84 Definitions: true,
85 Data: true,
86 Optional: true,
87 Constraints: true,
88 References: true,
89 Cycles: true,
90 KeepDefaults: true,
91 Incomplete: true,
92 Imports: true,
93 Stream: true,
94 Docs: true,
95 Attributes: true,
96 }, nil
97 }
98
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +010099 i := cuegenInstance.Value()
100 i = i.Unify(i.Lookup("modes", mode.String()))
101 v := i.LookupDef("FileInfo")
102 v = v.Fill(b)
103
104 if b.Encoding == "" {
105 ext := i.Lookup("extensions", filepath.Ext(b.Filename))
106 if ext.Exists() {
107 v = v.Unify(ext)
108 }
109 }
110
Marcel van Lohuizenf9f8e622020-03-31 17:17:26 +0200111 interpretation, _ := v.Lookup("interpretation").String()
112 if b.Form != "" {
113 v = v.Unify(i.Lookup("forms", string(b.Form)))
114 // may leave some encoding-dependent options open in data mode.
115 } else if interpretation != "" {
116 // always sets schema form.
117 v = v.Unify(i.Lookup("interpretations", interpretation))
118 }
119 if interpretation == "" {
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100120 s, err := v.Lookup("encoding").String()
121 if err != nil {
122 return nil, err
123 }
124 v = v.Unify(i.Lookup("encodings", s))
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100125 }
126
127 fi := &FileInfo{}
128 if err := v.Decode(fi); err != nil {
129 return nil, err
130 }
131 return fi, nil
132}
133
134// ParseArgs converts a sequence of command line arguments representing
135// files into a sequence of build file specifications.
136//
137// The arguments are of the form
138//
139// file* (spec: file+)*
140//
141// where file is a filename and spec is itself of the form
142//
143// tag[=value]('+'tag[=value])*
144//
145// A file type spec applies to all its following files and until a next spec
146// is found.
147//
148// Examples:
149// json: foo.data bar.data json+schema: bar.schema
150//
151func ParseArgs(args []string) (files []*build.File, err error) {
Marcel van Lohuizenf2a4a422020-10-08 15:21:08 +0200152 var inst, v cue.Value
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100153
154 qualifier := ""
155 hasFiles := false
156
157 for i, s := range args {
158 a := strings.Split(s, ":")
159 switch {
Marcel van Lohuizen26e63ec2020-02-28 17:33:47 +0000160 case len(a) == 1 || len(a[0]) == 1: // filename
Marcel van Lohuizenf2a4a422020-10-08 15:21:08 +0200161 if !v.Exists() {
162 if len(a) == 1 && strings.HasSuffix(a[0], ".cue") {
163 // Handle majority case.
164 files = append(files, &build.File{
165 Filename: a[0],
166 Encoding: build.CUE,
167 Form: build.Schema,
168 })
169 hasFiles = true
170 continue
171 }
172 inst, v = parseType("", Input)
173 }
Marcel van Lohuizen9b1dff72020-02-28 14:07:19 +0100174 f, err := toFile(inst, v, s)
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100175 if err != nil {
176 return nil, err
177 }
178 files = append(files, f)
179 hasFiles = true
180
181 case len(a) > 2 || a[0] == "":
182 return nil, errors.Newf(token.NoPos,
183 "unsupported file name %q: may not have ':'", s)
184
185 case a[1] != "":
186 return nil, errors.Newf(token.NoPos, "cannot combine scope with file")
187
188 default: // scope
189 switch {
190 case i == len(args)-1:
191 qualifier = a[0]
192 fallthrough
193 case qualifier != "" && !hasFiles:
194 return nil, errors.Newf(token.NoPos, "scoped qualifier %q without file", qualifier+":")
195 }
Marcel van Lohuizen9b1dff72020-02-28 14:07:19 +0100196 inst, v = parseType(a[0], Input)
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100197 qualifier = a[0]
198 hasFiles = false
199 }
200 }
201
202 return files, nil
203}
204
205// ParseFile parses a single-argument file specifier, such as when a file is
206// passed to a command line argument.
207//
208// Example:
209// cue eval -o yaml:foo.data
210//
211func ParseFile(s string, mode Mode) (*build.File, error) {
212 scope := ""
213 file := s
214
215 if p := strings.LastIndexByte(s, ':'); p >= 0 {
216 scope = s[:p]
217 file = s[p+1:]
218 if scope == "" {
219 return nil, errors.Newf(token.NoPos, "unsupported file name %q: may not have ':", s)
220 }
221 }
222
223 if file == "" {
224 return nil, errors.Newf(token.NoPos, "empty file name in %q", s)
225 }
226
Marcel van Lohuizen9b1dff72020-02-28 14:07:19 +0100227 inst, val := parseType(scope, mode)
228 return toFile(inst, val, file)
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100229}
230
Marcel van Lohuizenf9f8e622020-03-31 17:17:26 +0200231func hasEncoding(v cue.Value) (concrete, hasDefault bool) {
232 enc := v.Lookup("encoding")
233 d, _ := enc.Default()
234 return enc.IsConcrete(), d.IsConcrete()
235}
236
Marcel van Lohuizen9b1dff72020-02-28 14:07:19 +0100237func toFile(i, v cue.Value, filename string) (*build.File, error) {
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100238 v = v.Fill(filename, "filename")
Marcel van Lohuizenf9f8e622020-03-31 17:17:26 +0200239
240 if concrete, hasDefault := hasEncoding(v); !concrete {
241 if filename == "-" {
242 if !hasDefault {
243 v = v.Unify(i.LookupDef("Default"))
Marcel van Lohuizen38362f82020-03-17 14:52:38 +0100244 }
Marcel van Lohuizenf9f8e622020-03-31 17:17:26 +0200245 } else if ext := filepath.Ext(filename); ext != "" {
246 if x := i.Lookup("extensions", ext); x.Exists() || !hasDefault {
247 v = v.Unify(x)
248 }
249 } else if !hasDefault {
250 return nil, errors.Newf(token.NoPos,
251 "no encoding specified for file %q", filename)
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100252 }
253 }
Marcel van Lohuizenf9f8e622020-03-31 17:17:26 +0200254
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100255 f := &build.File{}
256 if err := v.Decode(&f); err != nil {
257 return nil, err
258 }
259 return f, nil
260}
261
Marcel van Lohuizen9b1dff72020-02-28 14:07:19 +0100262func parseType(s string, mode Mode) (inst, val cue.Value) {
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100263 i := cuegenInstance.Value()
Marcel van Lohuizen9b1dff72020-02-28 14:07:19 +0100264 i = i.Unify(i.Lookup("modes", mode.String()))
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100265 v := i.LookupDef("File")
266
267 if s != "" {
268 for _, t := range strings.Split(s, "+") {
269 if p := strings.IndexByte(t, '='); p >= 0 {
270 v = v.Fill(t[p+1:], "tags", t[:p])
271 } else {
272 v = v.Unify(i.Lookup("tags", t))
273 }
274 }
275 }
276
Marcel van Lohuizen9b1dff72020-02-28 14:07:19 +0100277 return i, v
Marcel van Lohuizen429bd0f2020-02-12 19:33:05 +0100278}