this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

internal/core/runtime: rename Interpreter/Compiler to Injection/Injector

Refactor the extern injection interfaces:

- Interpreter -> Injection, Compiler -> Injector
- NewCompiler -> InjectorForInstance, Compile -> InjectedValue
- SetInterpreter -> SetInjection
- cuelang.org/go/cue/interpreter -> cuelang.org/go/cue/inject

The current names are targetted at wasm but are not appropriate
for more general injection schemes, such as `@embed`.

The new Injector.InjectedValue method receives an *ExternAttr
(containing the parent AST node and parsed attribute) instead of
a pre-extracted name string. This moves name-related logic out of
the generic externDecorator and into the specific injector
implementations that need it (e.g. wasm).

This will also allow injection logic to let the top level
`@extern` attribute parameters influence the behavior
of an extern attribute, because that information is available
inside the `ExternAttr` type.

We leave a forwarding package in cue/interpreter/embed
so that we don't gratuitously break clients that are using embed.New;
technically we'd be entitled to just rename it as the package API
is noted as experimental, but given that embed users had
been required to use `embed.New` explicitly, some soft landing
seems better. The rest of the API is new and its renaming is unlikely to
cause problems. The wasm package does not work with EvalV3 anyway,
so moving it should hopefully not be an issue.

Signed-off-by: Roger Peppe <rogpeppe@gmail.com>
Change-Id: I75035e0b17d9857e38d60e06a8ff454da3889597
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1235341
Reviewed-by: Marcel van Lohuizen <mpvl@gmail.com>
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>

+743 -689
+1 -1
.github/workflows/trybot.yaml
··· 117 117 GOARCH: "386" 118 118 run: go test -short ./... 119 119 - name: Test with -tags=cuewasm 120 - run: go test -tags cuewasm ./cmd/cue/cmd ./cue/interpreter/wasm 120 + run: go test -tags cuewasm ./cmd/cue/cmd ./cue/inject/wasm 121 121 - id: auth 122 122 if: |- 123 123 github.repository == 'cue-lang/cue' && (((github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/release-branch.')) && (! (contains(github.event.head_commit.message, '
+1 -1
cmd/cue/cmd/common.go
··· 734 734 } 735 735 736 736 func buildToolInstances(ctx *cue.Context, binst []*build.Instance) ([]*cue.Instance, error) { 737 - // Reuse the same context, if there is one, so that the @embed interpreter can be used. 737 + // Reuse the same context, if there is one, so that the @embed injection can be used. 738 738 // Note that ctx may be nil when we do `cue help cmd`. 739 739 r := new(cue.Runtime) 740 740 if ctx != nil {
+3 -3
cmd/cue/cmd/root.go
··· 54 54 55 55 type runFunction func(cmd *Command, args []string) error 56 56 57 - // wasmInterp is set when the cuewasm build tag is enbabled. 58 - var wasmInterp cuecontext.ExternInterpreter 57 + // wasmInterp is set when the cuewasm build tag is enabled. 58 + var wasmInterp cuecontext.Injection 59 59 60 60 func statsEncoder(cmd *Command) (*encoding.Encoder, error) { 61 61 file := os.Getenv("CUE_STATS_FILE") ··· 181 181 } 182 182 var opts []cuecontext.Option 183 183 if wasmInterp != nil { 184 - opts = append(opts, cuecontext.Interpreter(wasmInterp)) 184 + opts = append(opts, cuecontext.WithInjection(wasmInterp)) 185 185 } 186 186 c.ctx = cuecontext.New(opts...) 187 187 // Some init work, such as in internal/filetypes, evaluates CUE by design.
+2 -2
cmd/cue/cmd/root_cuewasm.go
··· 17 17 package cmd 18 18 19 19 import ( 20 - "cuelang.org/go/cue/interpreter/wasm" 20 + "cuelang.org/go/cue/inject/wasm" 21 21 ) 22 22 23 23 func init() { 24 - // The wasm interpreter can be enabled by default once we are ready to ship the feature. 24 + // The wasm injection can be enabled by default once we are ready to ship the feature. 25 25 // For now, it's not ready, and makes cue binaries heavier by over 2MiB. 26 26 wasmInterp = wasm.New() 27 27 }
+15 -8
cue/cuecontext/cuecontext.go
··· 21 21 "fmt" 22 22 23 23 "cuelang.org/go/cue" 24 - "cuelang.org/go/cue/interpreter/embed" 24 + "cuelang.org/go/cue/inject/embed" 25 25 "cuelang.org/go/internal" 26 26 "cuelang.org/go/internal/core/runtime" 27 27 "cuelang.org/go/internal/cuedebug" ··· 45 45 func New(options ...Option) *cue.Context { 46 46 r := runtime.New() 47 47 // Embedding is always available. 48 - r.SetInterpreter(embed.New()) 48 + r.SetInjection(embed.New()) 49 49 for _, o := range options { 50 50 o.apply(r) 51 51 } 52 52 return (*cue.Context)(r) 53 53 } 54 54 55 - // An ExternInterpreter creates a compiler that can produce implementations of 56 - // functions written in a language other than CUE. It is currently for internal 57 - // use only. 58 - type ExternInterpreter = runtime.Interpreter 55 + // Deprecated: use [Injection] instead. 56 + type ExternInterpreter = runtime.Injection 57 + 58 + // An Injection provides a way to inject runtime values 59 + // into imported CUE code. 60 + type Injection = runtime.Injection 59 61 60 - // Interpreter associates an interpreter for external code with this context. 62 + // Deprecated: use [WithInjection] instead. 61 63 func Interpreter(i ExternInterpreter) Option { 64 + return WithInjection(i) 65 + } 66 + 67 + // WithInjection associates an injection for external code with this context. 68 + func WithInjection(i Injection) Option { 62 69 return Option{func(r *runtime.Runtime) { 63 - r.SetInterpreter(i) 70 + r.SetInjection(i) 64 71 }} 65 72 } 66 73
+592
cue/inject/embed/embed.go
··· 1 + // Copyright 2024 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 + // Package embed provides capabilities to CUE to embed any file that resides 16 + // within a CUE module into CUE either verbatim or decoded. 17 + // 18 + // This package is EXPERIMENTAL and subject to change. 19 + // 20 + // # Overview 21 + // 22 + // To enable file embedding, a file must include the file-level @extern(embed) 23 + // attribute. This allows a quick glance to see if a file embeds any files at 24 + // all. This allows the @embed attribute to be used to load a file within a CUE 25 + // module into a field. 26 + // 27 + // References to files are always relative to the directory in which the 28 + // referring file resides. Only files in the same module containing the CUE 29 + // file can be embedded, and parent directory references are not allowed. 30 + // 31 + // # The @embed attribute 32 + // 33 + // There are two main ways to embed files which are distinguished by the file 34 + // and glob arguments. The @embed attribute supports the following arguments: 35 + // 36 + // file=$filename 37 + // 38 + // The use of the file argument tells embed to load a single file into the 39 + // field. This argument many not be used in conjunction with the glob argument. 40 + // 41 + // glob=$pattern 42 + // 43 + // The use of the glob argument tells embed to load multiple files into the 44 + // field as a map of file paths to the decoded values. The paths are normalized 45 + // to use forward slashes. This argument may not be used in conjunction with the 46 + // file argument. 47 + // 48 + // type=$type 49 + // 50 + // By default, the file type is interpreted based on the file extension. This 51 + // behavior can be overridden by the type argument. See cue help filetypes for 52 + // the list of supported types. This field is required if a file extension is 53 + // unknown, or if a wildcard is used for the file extension in the glob pattern. 54 + // 55 + // allowEmptyGlob 56 + // 57 + // By default, a glob pattern that matches no files results in an error. When 58 + // allowEmptyGlob is present, a glob pattern with no matches will return an 59 + // empty struct instead of an error. This option is only supported with glob patterns. 60 + // 61 + // # Limitations 62 + // 63 + // The embed injection currently does not support: 64 + // - stream values, such as .ndjson or YAML streams. 65 + // - schema-based decoding, such as needed for textproto 66 + // 67 + // # Example 68 + // 69 + // @extern(embed) 70 + // 71 + // package foo 72 + // 73 + // // interpreted as JSON 74 + // a: _ @embed(file="file1.json") // the quotes are optional here 75 + // 76 + // // interpreted the same file as JSON schema 77 + // #A: _ @embed(file=file1.json, type=jsonschema) 78 + // 79 + // // interpret a proprietary extension as OpenAPI represented as YAML 80 + // b: _ @embed(file="file2.crd", type=openapi+yaml) 81 + // 82 + // // include all YAML files in the x directory interpreted as YAML 83 + // // The result is a map of file paths to the decoded YAML values. 84 + // files: _ @embed(glob=x/*.yaml) 85 + // 86 + // // include all files in the y directory as a map of file paths to binary 87 + // // data. The entries are unified into the same map as above. 88 + // files: _ @embed(glob=y/*.*, type=binary) 89 + // 90 + // // include all YAML files in the z directory, but allow empty result 91 + // // if no files match (returns empty struct instead of error) 92 + // optionalFiles: _ @embed(glob=z/*.yaml, allowEmptyGlob) 93 + package embed 94 + 95 + import ( 96 + iofs "io/fs" 97 + "os" 98 + "path" 99 + "path/filepath" 100 + "strings" 101 + 102 + "cuelang.org/go/cue" 103 + "cuelang.org/go/cue/ast" 104 + "cuelang.org/go/cue/build" 105 + "cuelang.org/go/cue/errors" 106 + "cuelang.org/go/cue/token" 107 + "cuelang.org/go/internal" 108 + "cuelang.org/go/internal/core/adt" 109 + "cuelang.org/go/internal/core/runtime" 110 + "cuelang.org/go/internal/encoding" 111 + "cuelang.org/go/internal/filetypes" 112 + "cuelang.org/go/internal/value" 113 + pkgpath "cuelang.org/go/pkg/path" 114 + ) 115 + 116 + // TODO: record files in build.Instance 117 + 118 + // injection is a [runtime.Injection] for embedded files. 119 + type injection struct{} 120 + 121 + // New returns a new injection for embedded files as a 122 + // [runtime.Injection] suitable for passing to 123 + // [cuelang.org/go/cue/cuecontext.WithInjection]. 124 + func New() runtime.Injection { 125 + return injection{} 126 + } 127 + 128 + func (i injection) Kind() string { 129 + return EmbedKind 130 + } 131 + 132 + const EmbedKind = "embed" 133 + 134 + // InjectorForInstance returns an injector that can decode and embed files 135 + // that exist within a CUE module. 136 + func (i injection) InjectorForInstance(b *build.Instance, r *runtime.Runtime) (runtime.Injector, errors.Error) { 137 + if b.Module == "" { 138 + return nil, errors.Newf(token.Pos{}, "cannot embed files when not in a module") 139 + } 140 + if b.Root == "" { 141 + return nil, errors.Newf(token.Pos{}, "cannot embed files: no module root found") 142 + } 143 + return &injector{ 144 + b: b, 145 + runtime: (*cue.Context)(r), 146 + }, nil 147 + } 148 + 149 + // An injector is a [runtime.Injector] that allows embedding files into CUE 150 + // values. 151 + type injector struct { 152 + b *build.Instance 153 + runtime *cue.Context 154 + opCtx *adt.OpContext 155 + 156 + // file system cache 157 + dir string 158 + fs iofs.StatFS 159 + pos token.Pos 160 + } 161 + 162 + // validateAttr performs logical validation of the attr. It does not 163 + // perform any checks against a filesystem. 164 + func validateAttr(a *internal.Attr) (file, glob, typ string, allowEmptyGlob bool, errs errors.Error) { 165 + if a.Err != nil { 166 + return "", "", "", false, a.Err 167 + } 168 + 169 + pos := a.Pos 170 + 171 + file, _, err := a.Lookup(0, "file") 172 + if err != nil { 173 + return "", "", "", false, errors.Promote(err, "invalid attribute") 174 + } 175 + 176 + glob, _, err = a.Lookup(0, "glob") 177 + if err != nil { 178 + return "", "", "", false, errors.Promote(err, "invalid attribute") 179 + } 180 + 181 + typ, _, err = a.Lookup(0, "type") 182 + if err != nil { 183 + return "", "", "", false, errors.Promote(err, "invalid type argument") 184 + } 185 + 186 + allowEmptyGlob, err = a.Flag(0, "allowEmptyGlob") 187 + if err != nil { 188 + return "", "", "", false, errors.Promote(err, "invalid allowEmptyGlob argument") 189 + } 190 + 191 + switch { 192 + case file == "" && glob == "": 193 + return "", "", "", false, errors.Newf(pos, "attribute must have file or glob field") 194 + 195 + case file != "" && glob != "": 196 + return "", "", "", false, errors.Newf(pos, "attribute cannot have both file and glob field") 197 + case allowEmptyGlob && glob == "": 198 + return "", "", "", false, errors.Newf(pos, "allowEmptyGlob must be specified with a glob field") 199 + 200 + case file != "": 201 + file, err := clean(pos, file) 202 + if err != nil { 203 + return "", "", "", false, err 204 + } 205 + return file, "", typ, allowEmptyGlob, nil 206 + 207 + default: 208 + glob, err := clean(pos, glob) 209 + if err != nil { 210 + return "", "", "", false, err 211 + } 212 + 213 + // Validate that the glob pattern is valid per [pkgpath.Match]. 214 + // Note that we use Unix match semantics because all embed paths are Unix-like. 215 + if _, err := pkgpath.Match(glob, "", pkgpath.Unix); err != nil { 216 + return "", "", "", false, errors.Wrapf(err, pos, "invalid glob pattern %q", glob) 217 + } 218 + 219 + // If we do not have a type, ensure the extension of the base is fully 220 + // specified, i.e. does not contain any meta characters as specified by 221 + // path.Match. 222 + if typ == "" { 223 + ext := path.Ext(path.Base(glob)) 224 + if ext == "" || strings.ContainsAny(ext, "*?[\\") { 225 + return "", "", "", false, errors.Newf(pos, "extension not fully specified; type argument required") 226 + } 227 + } 228 + return "", glob, typ, allowEmptyGlob, nil 229 + } 230 + } 231 + 232 + // InjectedValue interprets an embed attribute to either load a file 233 + // (@embed(file=...)) or a glob of files (@embed(glob=...)) 234 + // and decodes the given files. 235 + func (c *injector) InjectedValue(attr *runtime.ExternAttr, scope *adt.Vertex) (adt.Expr, errors.Error) { 236 + a := attr.Attr 237 + c.opCtx = adt.NewContext((*runtime.Runtime)(c.runtime), nil) 238 + 239 + pos := a.Pos 240 + c.pos = pos 241 + 242 + // Jump through some hoops to get file operations to behave the same for 243 + // Windows and Unix. 244 + // TODO: obtain an iofs.FS from load or something similar. 245 + dir := filepath.Dir(pos.File().Name()) 246 + if c.dir != dir { 247 + c.fs = os.DirFS(dir).(iofs.StatFS) // Documented as implementing iofs.StatFS 248 + c.dir = dir 249 + } 250 + 251 + file, glob, typ, allowEmptyGlob, err := validateAttr(a) 252 + if err != nil { 253 + return nil, err 254 + } 255 + 256 + if file != "" { 257 + for dir := path.Dir(file); dir != "."; dir = path.Dir(dir) { 258 + if _, err := c.fs.Stat(path.Join(dir, "cue.mod")); err == nil { 259 + return nil, errors.Newf(pos, "cannot embed file %q: in different module", file) 260 + } 261 + } 262 + return c.decodeFile(file, typ) 263 + } 264 + return c.processGlob(glob, typ, allowEmptyGlob) 265 + } 266 + 267 + func (c *injector) processGlob(glob, scope string, allowEmptyGlob bool) (adt.Expr, errors.Error) { 268 + m := &adt.StructLit{} 269 + 270 + matches, err := fsGlob(c.fs, glob) 271 + if err != nil { 272 + return nil, errors.Promote(err, "failed to match glob") 273 + } 274 + if len(matches) == 0 && !allowEmptyGlob { 275 + return nil, errors.Newf(c.pos, "no matches for glob pattern %q", glob) 276 + } 277 + 278 + dirs := make(map[string]string) 279 + for _, f := range matches { 280 + // TODO: lots of stat calls happening in this MVP so another won't hurt. 281 + // We don't support '**' initially, and '*' only matches files, so skip 282 + // any directories. 283 + if fi, err := c.fs.Stat(f); err != nil { 284 + return nil, errors.Newf(c.pos, "failed to stat %s: %v", f, err) 285 + } else if fi.IsDir() { 286 + continue 287 + } 288 + // Add all parents of the embedded file that 289 + // aren't the current directory (if there's a cue.mod 290 + // in the current directory, that's the current module 291 + // not nested). 292 + for dir := path.Dir(f); dir != "."; dir = path.Dir(dir) { 293 + dirs[dir] = f 294 + } 295 + 296 + expr, err := c.decodeFile(f, scope) 297 + if err != nil { 298 + return nil, err 299 + } 300 + 301 + m.Decls = append(m.Decls, &adt.Field{ 302 + Label: c.opCtx.StringLabel(f), 303 + Value: expr, 304 + }) 305 + } 306 + // Check that none of the matches were in a nested module 307 + // directory. 308 + for dir, f := range dirs { 309 + if _, err := c.fs.Stat(path.Join(dir, "cue.mod")); err == nil { 310 + return nil, errors.Newf(c.pos, "cannot embed file %q: in different module", f) 311 + } 312 + } 313 + return m, nil 314 + } 315 + 316 + func clean(pos token.Pos, s string) (string, errors.Error) { 317 + file := path.Clean(s) 318 + if file != s { 319 + return file, errors.Newf(pos, "path not normalized, use %q instead", file) 320 + } 321 + if path.IsAbs(file) { 322 + return "", errors.Newf(pos, "only relative files are allowed") 323 + } 324 + if file == ".." || strings.HasPrefix(file, "../") { 325 + return "", errors.Newf(pos, "cannot refer to parent directory") 326 + } 327 + return file, nil 328 + } 329 + 330 + // fsGlob is like [iofs.Glob] but only includes dot-prefixed files 331 + // when the dot is explictly present in an element. 332 + // TODO: add option for including dot files? 333 + func fsGlob(fsys iofs.FS, pattern string) ([]string, error) { 334 + pattern = path.Clean(pattern) 335 + matches, err := iofs.Glob(fsys, pattern) 336 + if err != nil { 337 + return nil, err 338 + } 339 + return filterFsGlobResults(pattern, matches...), nil 340 + } 341 + 342 + // filterFsGlobResults applies additional filtering on the given 343 + // matches to only include dot-prefixed files when the dot is 344 + // explictly present in the corresponding pattern element. 345 + func filterFsGlobResults(pattern string, matches ...string) []string { 346 + patElems := strings.Split(pattern, "/") 347 + included := func(m string) bool { 348 + for i, elem := range strings.Split(m, "/") { 349 + // Technically there should never be more elements in m than 350 + // there are in patElems, but be defensive and check bounds just in case. 351 + if strings.HasPrefix(elem, ".") && (i >= len(patElems) || !strings.HasPrefix(patElems[i], ".")) { 352 + return false 353 + } 354 + } 355 + return true 356 + } 357 + 358 + i := 0 359 + for _, m := range matches { 360 + if included(m) { 361 + matches[i] = m 362 + i++ 363 + } 364 + } 365 + return matches[:i] 366 + } 367 + 368 + func (c *injector) decodeFile(file, scope string) (adt.Expr, errors.Error) { 369 + // Do not use the most obvious filetypes.Input in order to disable "auto" 370 + // mode. 371 + f, err := filetypes.ParseFileAndType(file, scope, filetypes.Def) 372 + if err != nil { 373 + return nil, errors.Promote(err, "invalid file type") 374 + } 375 + 376 + // Open and pre-load the file system using iofs.FS. 377 + r, err := c.fs.Open(file) 378 + if err != nil { 379 + return nil, errors.Newf(c.pos, "open %v: no such file or directory", file) 380 + } 381 + defer r.Close() 382 + 383 + info, err := r.Stat() 384 + if err != nil { 385 + return nil, errors.Promote(err, "failed to decode file") 386 + } 387 + if info.IsDir() { 388 + return nil, errors.Newf(c.pos, "cannot embed directories") 389 + } 390 + f.Source = r 391 + 392 + // TODO: this really should be done at the start of the build process. 393 + // c.b.ExternFiles = append(c.b.ExternFiles, f) 394 + 395 + config := &encoding.Config{ 396 + // TODO: schema is currently the wrong schema, which is a bug in 397 + // internal/core/runtime. There is also an outstanding design choice: 398 + // do we imply the schema from the schema of the current field, or do 399 + // we explicitly enable schema-based encoding with a "schema" argument. 400 + // In the case of YAML it seems to be better to be explicit. In the case 401 + // of textproto it seems to be more convenient to do it implicitly. 402 + // Schema: value.Make(c.opCtx, schema), 403 + } 404 + 405 + d := encoding.NewDecoder(c.runtime, f, config) 406 + if err := d.Err(); err != nil { 407 + return nil, errors.Promote(err, "failed to decode file") 408 + } 409 + 410 + defer d.Close() 411 + 412 + n := d.File() 413 + 414 + if d.Next(); !d.Done() { 415 + // TODO: support streaming values 416 + return nil, errors.Newf(c.pos, "streaming not implemented: found more than one value in file") 417 + } 418 + 419 + // TODO: each of these encodings should probably be supported in the future 420 + switch f.Encoding { 421 + case build.CUE: 422 + return nil, errors.Newf(c.pos, "encoding %q not (yet) supported", f.Encoding) 423 + case build.JSONL: 424 + return nil, errors.Newf(c.pos, "encoding %q not (yet) supported: requires support for streaming", f.Encoding) 425 + case build.BinaryProto, build.TextProto: 426 + return nil, errors.Newf(c.pos, "encoding %q not (yet) supported: requires support for schema-guided decoding", f.Encoding) 427 + } 428 + 429 + val := c.runtime.BuildFile(n) 430 + if err := val.Err(); err != nil { 431 + return nil, errors.Promote(err, "failed to build file") 432 + } 433 + 434 + _, v := value.ToInternal(val) 435 + return v, nil 436 + } 437 + 438 + // EmbeddedPaths walks all the embed attributes in the given file, 439 + // returning a slice of [Embed] structs for attributes 440 + // that were successfully validated, and errors for those which were 441 + // not. The filepath should be the filepath of the file from which 442 + // these attributes were extracted, and relative to whatever root is 443 + // going to be used in calls to [Embed.Matches] and [Embed.FindAll]. 444 + func EmbeddedPaths(filepath string, syntax *ast.File) ([]*Embed, errors.Error) { 445 + extAttrs, err := runtime.ExternAttrsForFile(syntax) 446 + if err != nil { 447 + return nil, err 448 + } 449 + if extAttrs.TopLevel[EmbedKind] == nil { 450 + return nil, nil 451 + } 452 + var errs errors.Error 453 + var embeds []*Embed 454 + for attr := range extAttrs.Body { 455 + if attr.Attr.Name != EmbedKind { 456 + continue 457 + } 458 + if err := attr.Attr.Err; err != nil { 459 + errs = errors.Append(errs, err) 460 + continue 461 + } 462 + file, glob, typ, allowEmptyGlob, err := validateAttr(attr.Attr) 463 + if err != nil { 464 + errs = errors.Append(errs, err) 465 + continue 466 + } 467 + embed := &Embed{ 468 + Node: attr.Parent, 469 + Attribute: attr.Attr, 470 + FilePath: filepath, 471 + Type: typ, 472 + } 473 + if file != "" { 474 + embed.interpreter = &embeddedFile{ 475 + filepath: file, 476 + } 477 + embeds = append(embeds, embed) 478 + } else if glob != "" { 479 + embed.interpreter = &embeddedGlob{ 480 + glob: glob, 481 + allowEmptyGlob: allowEmptyGlob, 482 + } 483 + embeds = append(embeds, embed) 484 + } 485 + } 486 + return embeds, errs 487 + } 488 + 489 + type Embed struct { 490 + Node ast.Node 491 + Attribute *internal.Attr 492 + FilePath string 493 + Type string 494 + interpreter embedInterpreter 495 + } 496 + 497 + // Matches reports whether the provided filepath is matched by this 498 + // [Embed] attribute. The filepath should be relative to the same root 499 + // as the filepath provided to [EmbeddedPaths]. E.g. if in 500 + // `/wibble/foo/bar.cue` you have `@embed(filename=a/b.json)`, and 501 + // `foo/bar.cue` is the filepath passed to [EmbeddedPaths], then 502 + // [Embed.Matches] will return true if called with `foo/a/b.json`. 503 + func (e *Embed) Matches(filepath string) bool { 504 + return e.interpreter.matches(e, filepath) 505 + } 506 + 507 + // FindAll uses the provided fs to report all the filepaths that 508 + // match this [Embed] attribute. The fs must be relative to the same 509 + // root as the filepath provided to [EmbeddedPaths]. I.e. for the 510 + // filepath provided to [EmbeddedPaths], iofs.Stat(fs, filepath) 511 + // should be accessing the same file which contained this [Embed] 512 + // attribute. 513 + func (e *Embed) FindAll(fs iofs.FS) ([]string, error) { 514 + return e.interpreter.findAll(e, fs) 515 + } 516 + 517 + // IsGlob reports whether this [Embed] attribute represents a glob 518 + // embedding. 519 + func (e *Embed) IsGlob() bool { 520 + _, isGlob := e.interpreter.(*embeddedGlob) 521 + return isGlob 522 + } 523 + 524 + type embedInterpreter interface { 525 + // NB: All filepaths (including any within the Embed) are 526 + // considered relative to the same root. 527 + 528 + matches(e *Embed, filepath string) bool 529 + findAll(e *Embed, fs iofs.FS) ([]string, error) 530 + } 531 + 532 + type embeddedFile struct { 533 + filepath string 534 + } 535 + 536 + func (ef *embeddedFile) matches(e *Embed, filepath string) bool { 537 + dir := path.Dir(e.FilePath) 538 + return filepath == path.Join(dir, ef.filepath) 539 + } 540 + 541 + func (ef *embeddedFile) findAll(e *Embed, fs iofs.FS) ([]string, error) { 542 + dir := path.Dir(e.FilePath) 543 + filepath := path.Join(dir, ef.filepath) 544 + info, err := iofs.Stat(fs, filepath) 545 + if err != nil { 546 + return nil, errors.Wrapf(err, e.Attribute.Pos, "failed to stat %s: %v", filepath, err) 547 + } 548 + if info.IsDir() { 549 + return nil, errors.Newf(e.Attribute.Pos, "%v is a directory", filepath) 550 + } 551 + return []string{filepath}, nil 552 + } 553 + 554 + type embeddedGlob struct { 555 + glob string 556 + allowEmptyGlob bool 557 + } 558 + 559 + func (eg *embeddedGlob) matches(e *Embed, filepath string) bool { 560 + dir := path.Dir(e.FilePath) 561 + if dir != "." { 562 + wasCut := false 563 + filepath, wasCut = strings.CutPrefix(filepath, dir+"/") 564 + if !wasCut { 565 + return false 566 + } 567 + } 568 + result, err := pkgpath.Match(eg.glob, filepath, pkgpath.Unix) 569 + if !result || err != nil { 570 + return false 571 + } 572 + return len(filterFsGlobResults(eg.glob, filepath)) == 1 573 + } 574 + 575 + func (eg *embeddedGlob) findAll(e *Embed, fs iofs.FS) ([]string, error) { 576 + dir := path.Dir(e.FilePath) 577 + fs, err := iofs.Sub(fs, dir) 578 + if err != nil { 579 + return nil, errors.Wrapf(err, e.Attribute.Pos, "%v", err) 580 + } 581 + filepaths, err := fsGlob(fs, eg.glob) 582 + if err != nil { 583 + return nil, errors.Wrapf(err, e.Attribute.Pos, "%v", err) 584 + } 585 + if !eg.allowEmptyGlob && len(filepaths) == 0 { 586 + return nil, errors.Newf(e.Attribute.Pos, "no matches for glob pattern %q", eg.glob) 587 + } 588 + for i, filepath := range filepaths { 589 + filepaths[i] = path.Join(dir, filepath) 590 + } 591 + return filepaths, nil 592 + }
+1 -1
cue/instance.go
··· 202 202 cfg := &compile.Config{Scope: valueScope(Value{idx: r, v: inst.root})} 203 203 v, err := compile.Instance(cfg, r, p) 204 204 205 - // Just like [runtime.Runtime.Build], ensure that the @embed compiler is run as needed. 205 + // Just like [runtime.Runtime.Build], ensure that the @embed injector is run as needed. 206 206 err = errors.Append(err, r.InjectImplementations(p, v)) 207 207 208 208 v.AddConjunct(adt.MakeRootConjunct(nil, inst.root))
+7 -575
cue/interpreter/embed/embed.go
··· 1 - // Copyright 2024 CUE Authors 1 + // Copyright 2026 CUE Authors 2 2 // 3 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 4 // you may not use this file except in compliance with the License. ··· 12 12 // See the License for the specific language governing permissions and 13 13 // limitations under the License. 14 14 15 - // Package embed provides capabilities to CUE to embed any file that resides 16 - // within a CUE module into CUE either verbatim or decoded. 17 - // 18 - // This package is EXPERIMENTAL and subject to change. 19 - // 20 - // # Overview 21 - // 22 - // To enable file embedding, a file must include the file-level @extern(embed) 23 - // attribute. This allows a quick glance to see if a file embeds any files at 24 - // all. This allows the @embed attribute to be used to load a file within a CUE 25 - // module into a field. 26 - // 27 - // References to files are always relative to the directory in which the 28 - // referring file resides. Only files in the same module containing the CUE 29 - // file can be embedded, and parent directory references are not allowed. 30 - // 31 - // # The @embed attribute 32 - // 33 - // There are two main ways to embed files which are distinguished by the file 34 - // and glob arguments. The @embed attribute supports the following arguments: 35 - // 36 - // file=$filename 37 - // 38 - // The use of the file argument tells embed to load a single file into the 39 - // field. This argument many not be used in conjunction with the glob argument. 40 - // 41 - // glob=$pattern 42 - // 43 - // The use of the glob argument tells embed to load multiple files into the 44 - // field as a map of file paths to the decoded values. The paths are normalized 45 - // to use forward slashes. This argument may not be used in conjunction with the 46 - // file argument. 47 - // 48 - // type=$type 49 - // 50 - // By default, the file type is interpreted based on the file extension. This 51 - // behavior can be overridden by the type argument. See cue help filetypes for 52 - // the list of supported types. This field is required if a file extension is 53 - // unknown, or if a wildcard is used for the file extension in the glob pattern. 54 - // 55 - // allowEmptyGlob 56 - // 57 - // By default, a glob pattern that matches no files results in an error. When 58 - // allowEmptyGlob is present, a glob pattern with no matches will return an 59 - // empty struct instead of an error. This option is only supported with glob patterns. 60 - // 61 - // # Limitations 62 - // 63 - // The embed interpreter currently does not support: 64 - // - stream values, such as .ndjson or YAML streams. 65 - // - schema-based decoding, such as needed for textproto 66 - // 67 - // # Example 68 - // 69 - // @extern(embed) 70 - // 71 - // package foo 72 - // 73 - // // interpreted as JSON 74 - // a: _ @embed(file="file1.json") // the quotes are optional here 75 - // 76 - // // interpreted the same file as JSON schema 77 - // #A: _ @embed(file=file1.json, type=jsonschema) 78 - // 79 - // // interpret a proprietary extension as OpenAPI represented as YAML 80 - // b: _ @embed(file="file2.crd", type=openapi+yaml) 81 - // 82 - // // include all YAML files in the x directory interpreted as YAML 83 - // // The result is a map of file paths to the decoded YAML values. 84 - // files: _ @embed(glob=x/*.yaml) 85 - // 86 - // // include all files in the y directory as a map of file paths to binary 87 - // // data. The entries are unified into the same map as above. 88 - // files: _ @embed(glob=y/*.*, type=binary) 89 - // 90 - // // include all YAML files in the z directory, but allow empty result 91 - // // if no files match (returns empty struct instead of error) 92 - // optionalFiles: _ @embed(glob=z/*.yaml, allowEmptyGlob) 93 15 package embed 94 16 95 17 import ( 96 - iofs "io/fs" 97 - "os" 98 - "path" 99 - "path/filepath" 100 - "strings" 101 - 102 - "cuelang.org/go/cue" 103 - "cuelang.org/go/cue/ast" 104 - "cuelang.org/go/cue/build" 105 - "cuelang.org/go/cue/errors" 106 - "cuelang.org/go/cue/token" 107 - "cuelang.org/go/internal" 108 - "cuelang.org/go/internal/core/adt" 18 + "cuelang.org/go/cue/inject/embed" 109 19 "cuelang.org/go/internal/core/runtime" 110 - "cuelang.org/go/internal/encoding" 111 - "cuelang.org/go/internal/filetypes" 112 - "cuelang.org/go/internal/value" 113 - pkgpath "cuelang.org/go/pkg/path" 114 20 ) 115 21 116 - // TODO: record files in build.Instance 117 - 118 - // interpreter is a [cuecontext.ExternInterpreter] for embedded files. 119 - type interpreter struct{} 120 - 121 - // Note that [cuecontext.ExternInterpreter] is just an alias for [runtime.Interpreter] 122 - // but because the [cuelang.org/go/cue/cuecontext] package depends on embedding 123 - // we cannot refer to that type directly. 124 - 125 - // New returns a new interpreter for embedded files as a 126 - // [cuelang.org/go/cue/cuecontext.ExternInterpreter] suitable for 127 - // passing to [cuelang.org/go/cue/cuecontext.New]. 128 - func New() runtime.Interpreter { 129 - return interpreter{} 130 - } 131 - 132 - func (i interpreter) Kind() string { 133 - return EmbedKind 134 - } 135 - 136 - const EmbedKind = "embed" 137 - 138 - // NewCompiler returns a compiler that can decode and embed files that exist 139 - // within a CUE module. 140 - func (i interpreter) NewCompiler(b *build.Instance, r *runtime.Runtime) (runtime.Compiler, errors.Error) { 141 - if b.Module == "" { 142 - return nil, errors.Newf(token.Pos{}, "cannot embed files when not in a module") 143 - } 144 - if b.Root == "" { 145 - return nil, errors.Newf(token.Pos{}, "cannot embed files: no module root found") 146 - } 147 - return &compiler{ 148 - b: b, 149 - runtime: (*cue.Context)(r), 150 - }, nil 151 - } 152 - 153 - // A compiler is a [runtime.Compiler] that allows embedding files into CUE 154 - // values. 155 - type compiler struct { 156 - b *build.Instance 157 - runtime *cue.Context 158 - opCtx *adt.OpContext 159 - 160 - // file system cache 161 - dir string 162 - fs iofs.StatFS 163 - pos token.Pos 164 - } 165 - 166 - // validateAttr performs logical validation of the attr. It does not 167 - // perform any checks against a filesystem. 168 - func validateAttr(a *internal.Attr) (file, glob, typ string, allowEmptyGlob bool, errs errors.Error) { 169 - if a.Err != nil { 170 - return "", "", "", false, a.Err 171 - } 172 - 173 - pos := a.Pos 174 - 175 - file, _, err := a.Lookup(0, "file") 176 - if err != nil { 177 - return "", "", "", false, errors.Promote(err, "invalid attribute") 178 - } 179 - 180 - glob, _, err = a.Lookup(0, "glob") 181 - if err != nil { 182 - return "", "", "", false, errors.Promote(err, "invalid attribute") 183 - } 184 - 185 - typ, _, err = a.Lookup(0, "type") 186 - if err != nil { 187 - return "", "", "", false, errors.Promote(err, "invalid type argument") 188 - } 189 - 190 - allowEmptyGlob, err = a.Flag(0, "allowEmptyGlob") 191 - if err != nil { 192 - return "", "", "", false, errors.Promote(err, "invalid allowEmptyGlob argument") 193 - } 194 - 195 - switch { 196 - case file == "" && glob == "": 197 - return "", "", "", false, errors.Newf(pos, "attribute must have file or glob field") 198 - 199 - case file != "" && glob != "": 200 - return "", "", "", false, errors.Newf(pos, "attribute cannot have both file and glob field") 201 - case allowEmptyGlob && glob == "": 202 - return "", "", "", false, errors.Newf(pos, "allowEmptyGlob must be specified with a glob field") 203 - 204 - case file != "": 205 - file, err := clean(pos, file) 206 - if err != nil { 207 - return "", "", "", false, err 208 - } 209 - return file, "", typ, allowEmptyGlob, nil 210 - 211 - default: 212 - glob, err := clean(pos, glob) 213 - if err != nil { 214 - return "", "", "", false, err 215 - } 216 - 217 - // Validate that the glob pattern is valid per [pkgpath.Match]. 218 - // Note that we use Unix match semantics because all embed paths are Unix-like. 219 - if _, err := pkgpath.Match(glob, "", pkgpath.Unix); err != nil { 220 - return "", "", "", false, errors.Wrapf(err, pos, "invalid glob pattern %q", glob) 221 - } 222 - 223 - // If we do not have a type, ensure the extension of the base is fully 224 - // specified, i.e. does not contain any meta characters as specified by 225 - // path.Match. 226 - if typ == "" { 227 - ext := path.Ext(path.Base(glob)) 228 - if ext == "" || strings.ContainsAny(ext, "*?[\\") { 229 - return "", "", "", false, errors.Newf(pos, "extension not fully specified; type argument required") 230 - } 231 - } 232 - return "", glob, typ, allowEmptyGlob, nil 233 - } 234 - } 235 - 236 - // Compile interprets an embed attribute to either load a file 237 - // (@embed(file=...)) or a glob of files (@embed(glob=...)). 238 - // and decodes the given files. 239 - func (c *compiler) Compile(funcName string, scope adt.Value, a *internal.Attr) (adt.Expr, errors.Error) { 240 - c.opCtx = adt.NewContext((*runtime.Runtime)(c.runtime), nil) 241 - 242 - pos := a.Pos 243 - c.pos = pos 244 - 245 - // Jump through some hoops to get file operations to behave the same for 246 - // Windows and Unix. 247 - // TODO: obtain an iofs.FS from load or something similar. 248 - dir := filepath.Dir(pos.File().Name()) 249 - if c.dir != dir { 250 - c.fs = os.DirFS(dir).(iofs.StatFS) // Documented as implementing iofs.StatFS 251 - c.dir = dir 252 - } 253 - 254 - file, glob, typ, allowEmptyGlob, err := validateAttr(a) 255 - if err != nil { 256 - return nil, err 257 - } 258 - 259 - if file != "" { 260 - for dir := path.Dir(file); dir != "."; dir = path.Dir(dir) { 261 - if _, err := c.fs.Stat(path.Join(dir, "cue.mod")); err == nil { 262 - return nil, errors.Newf(pos, "cannot embed file %q: in different module", file) 263 - } 264 - } 265 - return c.decodeFile(file, typ) 266 - } 267 - return c.processGlob(glob, typ, allowEmptyGlob) 268 - } 269 - 270 - func (c *compiler) processGlob(glob, scope string, allowEmptyGlob bool) (adt.Expr, errors.Error) { 271 - m := &adt.StructLit{} 272 - 273 - matches, err := fsGlob(c.fs, glob) 274 - if err != nil { 275 - return nil, errors.Promote(err, "failed to match glob") 276 - } 277 - if len(matches) == 0 && !allowEmptyGlob { 278 - return nil, errors.Newf(c.pos, "no matches for glob pattern %q", glob) 279 - } 280 - 281 - dirs := make(map[string]string) 282 - for _, f := range matches { 283 - // TODO: lots of stat calls happening in this MVP so another won't hurt. 284 - // We don't support '**' initially, and '*' only matches files, so skip 285 - // any directories. 286 - if fi, err := c.fs.Stat(f); err != nil { 287 - return nil, errors.Newf(c.pos, "failed to stat %s: %v", f, err) 288 - } else if fi.IsDir() { 289 - continue 290 - } 291 - // Add all parents of the embedded file that 292 - // aren't the current directory (if there's a cue.mod 293 - // in the current directory, that's the current module 294 - // not nested). 295 - for dir := path.Dir(f); dir != "."; dir = path.Dir(dir) { 296 - dirs[dir] = f 297 - } 298 - 299 - expr, err := c.decodeFile(f, scope) 300 - if err != nil { 301 - return nil, err 302 - } 303 - 304 - m.Decls = append(m.Decls, &adt.Field{ 305 - Label: c.opCtx.StringLabel(f), 306 - Value: expr, 307 - }) 308 - } 309 - // Check that none of the matches were in a nested module 310 - // directory. 311 - for dir, f := range dirs { 312 - if _, err := c.fs.Stat(path.Join(dir, "cue.mod")); err == nil { 313 - return nil, errors.Newf(c.pos, "cannot embed file %q: in different module", f) 314 - } 315 - } 316 - return m, nil 317 - } 318 - 319 - func clean(pos token.Pos, s string) (string, errors.Error) { 320 - file := path.Clean(s) 321 - if file != s { 322 - return file, errors.Newf(pos, "path not normalized, use %q instead", file) 323 - } 324 - if path.IsAbs(file) { 325 - return "", errors.Newf(pos, "only relative files are allowed") 326 - } 327 - if file == ".." || strings.HasPrefix(file, "../") { 328 - return "", errors.Newf(pos, "cannot refer to parent directory") 329 - } 330 - return file, nil 331 - } 332 - 333 - // fsGlob is like [iofs.Glob] but only includes dot-prefixed files 334 - // when the dot is explictly present in an element. 335 - // TODO: add option for including dot files? 336 - func fsGlob(fsys iofs.FS, pattern string) ([]string, error) { 337 - pattern = path.Clean(pattern) 338 - matches, err := iofs.Glob(fsys, pattern) 339 - if err != nil { 340 - return nil, err 341 - } 342 - return filterFsGlobResults(pattern, matches...), nil 343 - } 344 - 345 - // filterFsGlobResults applies additional filtering on the given 346 - // matches to only include dot-prefixed files when the dot is 347 - // explictly present in the corresponding pattern element. 348 - func filterFsGlobResults(pattern string, matches ...string) []string { 349 - patElems := strings.Split(pattern, "/") 350 - included := func(m string) bool { 351 - for i, elem := range strings.Split(m, "/") { 352 - // Technically there should never be more elements in m than 353 - // there are in patElems, but be defensive and check bounds just in case. 354 - if strings.HasPrefix(elem, ".") && (i >= len(patElems) || !strings.HasPrefix(patElems[i], ".")) { 355 - return false 356 - } 357 - } 358 - return true 359 - } 360 - 361 - i := 0 362 - for _, m := range matches { 363 - if included(m) { 364 - matches[i] = m 365 - i++ 366 - } 367 - } 368 - return matches[:i] 369 - } 370 - 371 - func (c *compiler) decodeFile(file, scope string) (adt.Expr, errors.Error) { 372 - // Do not use the most obvious filetypes.Input in order to disable "auto" 373 - // mode. 374 - f, err := filetypes.ParseFileAndType(file, scope, filetypes.Def) 375 - if err != nil { 376 - return nil, errors.Promote(err, "invalid file type") 377 - } 378 - 379 - // Open and pre-load the file system using iofs.FS. 380 - r, err := c.fs.Open(file) 381 - if err != nil { 382 - return nil, errors.Newf(c.pos, "open %v: no such file or directory", file) 383 - } 384 - defer r.Close() 385 - 386 - info, err := r.Stat() 387 - if err != nil { 388 - return nil, errors.Promote(err, "failed to decode file") 389 - } 390 - if info.IsDir() { 391 - return nil, errors.Newf(c.pos, "cannot embed directories") 392 - } 393 - f.Source = r 394 - 395 - // TODO: this really should be done at the start of the build process. 396 - // c.b.ExternFiles = append(c.b.ExternFiles, f) 397 - 398 - config := &encoding.Config{ 399 - // TODO: schema is currently the wrong schema, which is a bug in 400 - // internal/core/runtime. There is also an outstanding design choice: 401 - // do we imply the schema from the schema of the current field, or do 402 - // we explicitly enable schema-based encoding with a "schema" argument. 403 - // In the case of YAML it seems to be better to be explicit. In the case 404 - // of textproto it seems to be more convenient to do it implicitly. 405 - // Schema: value.Make(c.opCtx, schema), 406 - } 407 - 408 - d := encoding.NewDecoder(c.runtime, f, config) 409 - if err := d.Err(); err != nil { 410 - return nil, errors.Promote(err, "failed to decode file") 411 - } 412 - 413 - defer d.Close() 414 - 415 - n := d.File() 416 - 417 - if d.Next(); !d.Done() { 418 - // TODO: support streaming values 419 - return nil, errors.Newf(c.pos, "streaming not implemented: found more than one value in file") 420 - } 421 - 422 - // TODO: each of these encodings should probably be supported in the future 423 - switch f.Encoding { 424 - case build.CUE: 425 - return nil, errors.Newf(c.pos, "encoding %q not (yet) supported", f.Encoding) 426 - case build.JSONL: 427 - return nil, errors.Newf(c.pos, "encoding %q not (yet) supported: requires support for streaming", f.Encoding) 428 - case build.BinaryProto, build.TextProto: 429 - return nil, errors.Newf(c.pos, "encoding %q not (yet) supported: requires support for schema-guided decoding", f.Encoding) 430 - } 431 - 432 - val := c.runtime.BuildFile(n) 433 - if err := val.Err(); err != nil { 434 - return nil, errors.Promote(err, "failed to build file") 435 - } 436 - 437 - _, v := value.ToInternal(val) 438 - return v, nil 439 - } 440 - 441 - // EmbeddedPaths walks all the embed attributes in the given file, 442 - // returning a slice of [Embed] structs for attributes 443 - // that were successfully validated, and errors for those which were 444 - // not. The filepath should be the filepath of the file from which 445 - // these attributes were extracted, and relative to whatever root is 446 - // going to be used in calls to [Embed.Matches] and [Embed.FindAll]. 447 - func EmbeddedPaths(filepath string, syntax *ast.File) ([]*Embed, errors.Error) { 448 - extAttrs, err := runtime.ExternAttrsForFile(syntax) 449 - if err != nil { 450 - return nil, err 451 - } 452 - if extAttrs.TopLevel[EmbedKind] == nil { 453 - return nil, nil 454 - } 455 - var errs errors.Error 456 - var embeds []*Embed 457 - for attr := range extAttrs.Body { 458 - if attr.Attr.Name != EmbedKind { 459 - continue 460 - } 461 - if err := attr.Attr.Err; err != nil { 462 - errs = errors.Append(errs, err) 463 - continue 464 - } 465 - file, glob, typ, allowEmptyGlob, err := validateAttr(attr.Attr) 466 - if err != nil { 467 - errs = errors.Append(errs, err) 468 - continue 469 - } 470 - embed := &Embed{ 471 - Node: attr.Parent, 472 - Attribute: attr.Attr, 473 - FilePath: filepath, 474 - Type: typ, 475 - } 476 - if file != "" { 477 - embed.interpreter = &embeddedFile{ 478 - filepath: file, 479 - } 480 - embeds = append(embeds, embed) 481 - } else if glob != "" { 482 - embed.interpreter = &embeddedGlob{ 483 - glob: glob, 484 - allowEmptyGlob: allowEmptyGlob, 485 - } 486 - embeds = append(embeds, embed) 487 - } 488 - } 489 - return embeds, errs 490 - } 491 - 492 - type Embed struct { 493 - Node ast.Node 494 - Attribute *internal.Attr 495 - FilePath string 496 - Type string 497 - interpreter embedInterpreter 498 - } 499 - 500 - // Matches reports whether the provided filepath is matched by this 501 - // [Embed] attribute. The filepath should be relative to the same root 502 - // as the filepath provided to [EmbeddedPaths]. E.g. if in 503 - // `/wibble/foo/bar.cue` you have `@embed(filename=a/b.json)`, and 504 - // `foo/bar.cue` is the filepath passed to [EmbeddedPaths], then 505 - // [Embed.Matches] will return true if called with `foo/a/b.json`. 506 - func (e *Embed) Matches(filepath string) bool { 507 - return e.interpreter.matches(e, filepath) 508 - } 509 - 510 - // FindAll uses the provided fs to report all the filepaths that 511 - // match this [Embed] attribute. The fs must be relative to the same 512 - // root as the filepath provided to [EmbeddedPaths]. I.e. for the 513 - // filepath provided to [EmbeddedPaths], iofs.Stat(fs, filepath) 514 - // should be accessing the same file which contained this [Embed] 515 - // attribute. 516 - func (e *Embed) FindAll(fs iofs.FS) ([]string, error) { 517 - return e.interpreter.findAll(e, fs) 518 - } 519 - 520 - // IsGlob reports whether this [Embed] attribute represents a glob 521 - // embedding. 522 - func (e *Embed) IsGlob() bool { 523 - _, isGlob := e.interpreter.(*embeddedGlob) 524 - return isGlob 525 - } 526 - 527 - type embedInterpreter interface { 528 - // NB: All filepaths (including any within the Embed) are 529 - // considered relative to the same root. 530 - 531 - matches(e *Embed, filepath string) bool 532 - findAll(e *Embed, fs iofs.FS) ([]string, error) 533 - } 534 - 535 - type embeddedFile struct { 536 - filepath string 537 - } 538 - 539 - func (ef *embeddedFile) matches(e *Embed, filepath string) bool { 540 - dir := path.Dir(e.FilePath) 541 - return filepath == path.Join(dir, ef.filepath) 542 - } 543 - 544 - func (ef *embeddedFile) findAll(e *Embed, fs iofs.FS) ([]string, error) { 545 - dir := path.Dir(e.FilePath) 546 - filepath := path.Join(dir, ef.filepath) 547 - info, err := iofs.Stat(fs, filepath) 548 - if err != nil { 549 - return nil, errors.Wrapf(err, e.Attribute.Pos, "failed to stat %s: %v", filepath, err) 550 - } 551 - if info.IsDir() { 552 - return nil, errors.Newf(e.Attribute.Pos, "%v is a directory", filepath) 553 - } 554 - return []string{filepath}, nil 555 - } 556 - 557 - type embeddedGlob struct { 558 - glob string 559 - allowEmptyGlob bool 560 - } 561 - 562 - func (eg *embeddedGlob) matches(e *Embed, filepath string) bool { 563 - dir := path.Dir(e.FilePath) 564 - if dir != "." { 565 - wasCut := false 566 - filepath, wasCut = strings.CutPrefix(filepath, dir+"/") 567 - if !wasCut { 568 - return false 569 - } 570 - } 571 - result, err := pkgpath.Match(eg.glob, filepath, pkgpath.Unix) 572 - if !result || err != nil { 573 - return false 574 - } 575 - return len(filterFsGlobResults(eg.glob, filepath)) == 1 576 - } 577 - 578 - func (eg *embeddedGlob) findAll(e *Embed, fs iofs.FS) ([]string, error) { 579 - dir := path.Dir(e.FilePath) 580 - fs, err := iofs.Sub(fs, dir) 581 - if err != nil { 582 - return nil, errors.Wrapf(err, e.Attribute.Pos, "%v", err) 583 - } 584 - filepaths, err := fsGlob(fs, eg.glob) 585 - if err != nil { 586 - return nil, errors.Wrapf(err, e.Attribute.Pos, "%v", err) 587 - } 588 - if !eg.allowEmptyGlob && len(filepaths) == 0 { 589 - return nil, errors.Newf(e.Attribute.Pos, "no matches for glob pattern %q", eg.glob) 590 - } 591 - for i, filepath := range filepaths { 592 - filepaths[i] = path.Join(dir, filepath) 593 - } 594 - return filepaths, nil 22 + // Deprecated: use [embed.New]. 23 + // 24 + //go:fix inline 25 + func New() runtime.Injection { 26 + return embed.New() 595 27 }
cue/interpreter/embed/embed_test.go cue/inject/embed/embed_test.go
cue/interpreter/wasm/builtin.go cue/inject/wasm/builtin.go
cue/interpreter/wasm/call.go cue/inject/wasm/call.go
cue/interpreter/wasm/doc.go cue/inject/wasm/doc.go
+1 -1
cue/interpreter/wasm/exe_test.go cue/inject/wasm/exe_test.go
··· 32 32 "cuelang.org/go/cue/ast" 33 33 "cuelang.org/go/cue/build" 34 34 "cuelang.org/go/cue/cuecontext" 35 - "cuelang.org/go/cue/interpreter/wasm" 35 + "cuelang.org/go/cue/inject/wasm" 36 36 "cuelang.org/go/cue/parser" 37 37 "cuelang.org/go/internal" 38 38 "cuelang.org/go/internal/core/runtime"
cue/interpreter/wasm/extern.go cue/inject/wasm/extern.go
cue/interpreter/wasm/layout.go cue/inject/wasm/layout.go
cue/interpreter/wasm/runtime.go cue/inject/wasm/runtime.go
cue/interpreter/wasm/testdata/cue/basic.txtar cue/inject/wasm/testdata/cue/basic.txtar
cue/interpreter/wasm/testdata/cue/basic.wasm cue/inject/wasm/testdata/cue/basic.wasm
cue/interpreter/wasm/testdata/cue/basic1.wasm cue/inject/wasm/testdata/cue/basic1.wasm
cue/interpreter/wasm/testdata/cue/complex.txtar cue/inject/wasm/testdata/cue/complex.txtar
cue/interpreter/wasm/testdata/cue/def.txtar cue/inject/wasm/testdata/cue/def.txtar
cue/interpreter/wasm/testdata/cue/default.txtar cue/inject/wasm/testdata/cue/default.txtar
cue/interpreter/wasm/testdata/cue/empty.wasm cue/inject/wasm/testdata/cue/empty.wasm
cue/interpreter/wasm/testdata/cue/error.txtar cue/inject/wasm/testdata/cue/error.txtar
cue/interpreter/wasm/testdata/cue/missing.txtar cue/inject/wasm/testdata/cue/missing.txtar
cue/interpreter/wasm/testdata/cue/multiple.txtar cue/inject/wasm/testdata/cue/multiple.txtar
cue/interpreter/wasm/testdata/cue/nested.txtar cue/inject/wasm/testdata/cue/nested.txtar
cue/interpreter/wasm/testdata/cue/noload.txtar cue/inject/wasm/testdata/cue/noload.txtar
cue/interpreter/wasm/testdata/cue/nopackage.txtar cue/inject/wasm/testdata/cue/nopackage.txtar
cue/interpreter/wasm/testdata/cue/struct.txtar cue/inject/wasm/testdata/cue/struct.txtar
cue/interpreter/wasm/testdata/cue/struct.wasm cue/inject/wasm/testdata/cue/struct.wasm
cue/interpreter/wasm/testdata/cue/unused.txtar cue/inject/wasm/testdata/cue/unused.txtar
cue/interpreter/wasm/testdata/gen.go cue/inject/wasm/testdata/gen.go
cue/interpreter/wasm/testdata/rust/Cargo.lock cue/inject/wasm/testdata/rust/Cargo.lock
cue/interpreter/wasm/testdata/rust/Cargo.toml cue/inject/wasm/testdata/rust/Cargo.toml
cue/interpreter/wasm/testdata/rust/basic/Cargo.toml cue/inject/wasm/testdata/rust/basic/Cargo.toml
cue/interpreter/wasm/testdata/rust/basic/src/lib.rs cue/inject/wasm/testdata/rust/basic/src/lib.rs
cue/interpreter/wasm/testdata/rust/basic1/Cargo.toml cue/inject/wasm/testdata/rust/basic1/Cargo.toml
cue/interpreter/wasm/testdata/rust/basic1/src/lib.rs cue/inject/wasm/testdata/rust/basic1/src/lib.rs
cue/interpreter/wasm/testdata/rust/struct/Cargo.toml cue/inject/wasm/testdata/rust/struct/Cargo.toml
cue/interpreter/wasm/testdata/rust/struct/src/lib.rs cue/inject/wasm/testdata/rust/struct/src/lib.rs
cue/interpreter/wasm/testdata/rust/struct/src/mem.rs cue/inject/wasm/testdata/rust/struct/src/mem.rs
+28 -16
cue/interpreter/wasm/wasm.go cue/inject/wasm/wasm.go
··· 19 19 "strings" 20 20 "sync" 21 21 22 + "cuelang.org/go/cue/ast" 22 23 "cuelang.org/go/cue/build" 23 24 "cuelang.org/go/cue/cuecontext" 24 25 "cuelang.org/go/cue/errors" ··· 28 29 coreruntime "cuelang.org/go/internal/core/runtime" 29 30 ) 30 31 31 - // interpreter is a [cuecontext.ExternInterpreter] for Wasm files. 32 - type interpreter struct{} 32 + // injection is a [cuecontext.Injection] for Wasm files. 33 + type injection struct{} 33 34 34 - // New returns a new Wasm interpreter as a [cuecontext.ExternInterpreter] 35 - // suitable for passing to [cuecontext.New]. 35 + // New returns a new Wasm injection as a [cuecontext.Injection] 36 + // suitable for passing to [cuecontext.WithInjection]. 36 37 func New() cuecontext.ExternInterpreter { 37 - return &interpreter{} 38 + return &injection{} 38 39 } 39 40 40 - func (i *interpreter) Kind() string { 41 + func (i *injection) Kind() string { 41 42 return "wasm" 42 43 } 43 44 44 - // NewCompiler returns a Wasm compiler that services the specified 45 + // InjectorForInstance returns a Wasm injector that services the specified 45 46 // build.Instance. 46 - func (i *interpreter) NewCompiler(b *build.Instance, r *coreruntime.Runtime) (coreruntime.Compiler, errors.Error) { 47 - return &compiler{ 47 + func (i *injection) InjectorForInstance(b *build.Instance, r *coreruntime.Runtime) (coreruntime.Injector, errors.Error) { 48 + return &wasmInjector{ 48 49 b: b, 49 50 runtime: r, 50 51 wasmRuntime: newRuntime(), ··· 52 53 }, nil 53 54 } 54 55 55 - // A compiler is a [coreruntime.Compiler] 56 + // A wasmInjector is a [coreruntime.Injector] 56 57 // that provides Wasm functionality to the runtime. 57 - type compiler struct { 58 + type wasmInjector struct { 58 59 b *build.Instance 59 60 runtime *coreruntime.Runtime 60 61 wasmRuntime runtime ··· 67 68 instances map[string]*instance 68 69 } 69 70 70 - // Compile searches for a Wasm function described by the given `@extern` 71 - // attribute and returns it as an [adt.Builtin] with the given function 72 - // name. 73 - func (c *compiler) Compile(funcName string, scope adt.Value, a *internal.Attr) (adt.Expr, errors.Error) { 71 + // InjectedValue searches for a Wasm function described by the given 72 + // extern attribute and returns it as an [adt.Builtin]. 73 + func (c *wasmInjector) InjectedValue(attr *coreruntime.ExternAttr, scope *adt.Vertex) (adt.Expr, errors.Error) { 74 + a := attr.Attr 75 + 76 + // Determine the function name from the parent field label, 77 + // with an explicit name= attribute taking precedence. 78 + var funcName string 79 + if f, ok := attr.Parent.(*ast.Field); ok { 80 + funcName, _, _ = ast.LabelName(f.Label) 81 + } 82 + if name, ok, _ := a.Lookup(1, "name"); ok { 83 + funcName = name 84 + } 85 + 74 86 baseFile, err := fileName(a) 75 87 if err != nil { 76 88 return nil, errors.Promote(err, "invalid attribute") ··· 104 116 105 117 // instance returns the instance corresponding to filename, compiling 106 118 // and loading it if necessary. 107 - func (c *compiler) instance(filename string) (inst *instance, err error) { 119 + func (c *wasmInjector) instance(filename string) (inst *instance, err error) { 108 120 c.mu.Lock() 109 121 defer c.mu.Unlock() 110 122 inst, ok := c.instances[filename]
+1 -1
cue/interpreter/wasm/wasm_test.go cue/inject/wasm/wasm_test.go
··· 24 24 "cuelang.org/go/cue/cuecontext" 25 25 "cuelang.org/go/cue/errors" 26 26 "cuelang.org/go/cue/format" 27 - "cuelang.org/go/cue/interpreter/wasm" 27 + "cuelang.org/go/cue/inject/wasm" 28 28 "cuelang.org/go/internal" 29 29 "cuelang.org/go/internal/cuetxtar" 30 30 )
+1 -1
internal/ci/github/trybot.cue
··· 215 215 name: "Test with -tags=cuewasm" 216 216 // The wasm interpreter is only bundled into cmd/cue with the cuewasm build tag. 217 217 // Test the related packages with the build tag enabled as well. 218 - run: "go test -tags cuewasm ./cmd/cue/cmd ./cue/interpreter/wasm" 218 + run: "go test -tags cuewasm ./cmd/cue/cmd ./cue/inject/wasm" 219 219 } 220 220 }
+2 -2
internal/core/compile/compile.go
··· 1121 1121 case *ast.Func: 1122 1122 // We don't yet support function types natively in 1123 1123 // CUE. ast.Func exists only to support external 1124 - // interpreters. Function values (really, adt.Builtin) 1124 + // injections. Function values (really, adt.Builtin) 1125 1125 // are only created by the runtime, or injected by 1126 - // external interpreters. 1126 + // external injections. 1127 1127 // 1128 1128 // TODO: revise this when we add function types. 1129 1129 return c.resolve(ast.NewIdent("_"))
+57 -58
internal/core/runtime/extern.go
··· 26 26 "cuelang.org/go/internal/core/walk" 27 27 ) 28 28 29 - // SetInterpreter sets the interpreter for interpretation of files marked with 30 - // @extern(kind). 31 - func (r *Runtime) SetInterpreter(i Interpreter) { 32 - if r.interpreters == nil { 33 - r.interpreters = map[string]Interpreter{} 29 + // SetInjection sets the injection value to be used for injection 30 + // of values with an @extern(kind) attribute where kind is i.Kind(). 31 + func (r *Runtime) SetInjection(i Injection) { 32 + if r.injections == nil { 33 + r.injections = map[string]Injection{} 34 34 } 35 - r.interpreters[i.Kind()] = i 35 + r.injections[i.Kind()] = i 36 36 } 37 37 38 - // TODO: consider also passing the top-level attribute to NewCompiler to allow 39 - // passing default values. 40 - 41 - // Interpreter defines an entrypoint for creating per-package interpreters. 42 - type Interpreter interface { 43 - // NewCompiler creates a compiler for b and reports any errors. 44 - NewCompiler(b *build.Instance, r *Runtime) (Compiler, errors.Error) 38 + // Injection defines an entrypoint for creating per-instance injectors. 39 + type Injection interface { 40 + // InjectorForInstance returns a new injector for the 41 + // given build instance. 42 + InjectorForInstance(b *build.Instance, r *Runtime) (Injector, errors.Error) 45 43 46 - // Kind returns the string to be used in the file-level @extern attribute. 44 + // Kind returns the @extern kind for this injection, 45 + // for example "embed" for @extern(embed). 46 + // A given Injection instance should always return 47 + // the same value. 47 48 Kind() string 48 49 } 49 50 50 - // A Compiler fills in an adt.Expr for fields marked with `@extern(kind)`. 51 - type Compiler interface { 52 - // Compile creates an adt.Expr (usually a builtin) for the 53 - // given external named resource (usually a function). name 54 - // is the name of the resource to compile, taken from altName 55 - // in `@extern(name=altName)`, or from the field name if that's 56 - // not defined. Scope is the struct that contains the field. 57 - // Other than "name", the fields in a are implementation 58 - // specific. 59 - Compile(name string, scope adt.Value, a *internal.Attr) (adt.Expr, errors.Error) 51 + // An Injector fills in an adt.Expr for fields marked with `@extern(kind)`. 52 + type Injector interface { 53 + // InjectedValue returns a value to be unified at the position of 54 + // the given external attribute. The scope argument 55 + // holds the value of the instance before any injections 56 + // have been unified into it. 57 + InjectedValue(attr *ExternAttr, scope *adt.Vertex) (adt.Expr, errors.Error) 60 58 } 61 59 62 60 // InjectImplementations modifies v to include implementations of functions ··· 82 80 return d.errs 83 81 } 84 82 85 - // externDecorator locates extern attributes and calls the relevant interpreters 86 - // to inject builtins. 83 + // externDecorator locates extern attributes and calls the relevant injectors 84 + // to inject values. 87 85 type externDecorator struct { 88 86 runtime *Runtime 89 87 pkg *build.Instance 90 88 91 - compilers map[string]Compiler 89 + injectors map[string]Injector 92 90 93 - // fileKinds maps each AST file to the set of extern kinds declared in it. 94 - fileKinds map[*token.File]map[string]bool 91 + // fileKinds maps each AST file to the extern kinds declared in it, 92 + // along with their file-level @extern attribute. 93 + fileKinds map[*token.File]map[string]*internal.Attr 95 94 96 95 errs errors.Error 97 96 } 98 97 99 98 // addFile finds injection points in the given ast.File for external 100 - // implementations of Builtins. 99 + // implementations. 101 100 func (d *externDecorator) addFile(f *ast.File) (errs errors.Error) { 102 101 kinds, _, err := findExternFileAttrs(f) 103 102 if err != nil { ··· 108 107 } 109 108 110 109 if d.fileKinds == nil { 111 - d.fileKinds = map[*token.File]map[string]bool{} 110 + d.fileKinds = map[*token.File]map[string]*internal.Attr{} 112 111 } 113 - km := make(map[string]bool) 114 - for kind := range kinds { 115 - km[kind] = true 116 - } 117 - d.fileKinds[f.Pos().File()] = km 112 + d.fileKinds[f.Pos().File()] = kinds 118 113 119 114 for kind, attr := range kinds { 120 - if err := d.initCompiler(kind, attr.Pos); err != nil { 115 + if err := d.initInjector(kind, attr.Pos); err != nil { 121 116 errs = errors.Append(errs, err) 122 117 } 123 118 } ··· 163 158 164 159 if k == "" { 165 160 err = errors.Append(err, errors.Newf(attr.Pos, 166 - "interpreter name must be non-empty")) 161 + "injection name must be non-empty")) 167 162 continue 168 163 } 169 164 ··· 208 203 return kinds, f.Decls[p:], err 209 204 } 210 205 211 - // initCompiler initializes the runtime for kind, if applicable. The pos 206 + // initInjector initializes the injector for kind, if applicable. The pos 212 207 // argument represents the position of the file-level @extern attribute. 213 - func (d *externDecorator) initCompiler(kind string, pos token.Pos) errors.Error { 214 - if _, ok := d.compilers[kind]; ok { 208 + func (d *externDecorator) initInjector(kind string, pos token.Pos) errors.Error { 209 + if _, ok := d.injectors[kind]; ok { 215 210 return nil 216 211 } 217 - // initialize the compiler. 218 - if d.compilers == nil { 219 - d.compilers = map[string]Compiler{} 212 + if d.injectors == nil { 213 + d.injectors = map[string]Injector{} 220 214 } 221 - x := d.runtime.interpreters[kind] 215 + x := d.runtime.injections[kind] 222 216 if x == nil { 223 - return errors.Newf(pos, "no interpreter defined for %q", kind) 217 + return errors.Newf(pos, "no injection defined for %q", kind) 224 218 } 225 - c, err := x.NewCompiler(d.pkg, d.runtime) 219 + inj, err := x.InjectorForInstance(d.pkg, d.runtime) 226 220 if err != nil { 227 221 return err 228 222 } 229 - d.compilers[kind] = c 223 + d.injectors[kind] = inj 230 224 return nil 231 225 } 232 226 ··· 351 345 continue 352 346 } 353 347 srcField := decl.Source().(*ast.Field) // We know all the above types come from ast.Field. 354 - name, _, _ := ast.LabelName(srcField.Label) 355 348 for _, attr := range srcField.Attrs { 356 - if expr := d.externValue(attr, name, kinds, scope); expr != nil { 349 + if expr := d.injectedValue(attr, srcField, kinds, scope); expr != nil { 357 350 *valuePtr = &adt.BinaryExpr{ 358 351 Op: adt.AndOp, 359 352 X: *valuePtr, ··· 364 357 } 365 358 366 359 // Process embedded attributes. 360 + var srcParent ast.Node 367 361 var srcDecls []ast.Decl 368 362 switch src := s.Src.(type) { 369 363 case *ast.File: 364 + srcParent = src 370 365 srcDecls = src.Decls 371 366 case *ast.StructLit: 367 + srcParent = src 372 368 srcDecls = src.Elts 373 369 default: 374 370 panic("unexpected type in adt.StructLit.Src") 375 371 } 376 372 for _, decl := range srcDecls { 377 373 if attr, ok := decl.(*ast.Attribute); ok { 378 - if expr := d.externValue(attr, "", kinds, scope); expr != nil { 374 + if expr := d.injectedValue(attr, srcParent, kinds, scope); expr != nil { 379 375 s.Decls = append(s.Decls, expr) 380 376 } 381 377 } 382 378 } 383 379 } 384 380 385 - func (d *externDecorator) externValue(astAttr *ast.Attribute, name string, kinds map[string]bool, scope *adt.Vertex) adt.Expr { 386 - if !kinds[astAttr.Name()] { 381 + func (d *externDecorator) injectedValue(astAttr *ast.Attribute, parent ast.Node, kinds map[string]*internal.Attr, scope *adt.Vertex) adt.Expr { 382 + topLevel := kinds[astAttr.Name()] 383 + if topLevel == nil { 387 384 return nil 388 385 } 389 386 attr := internal.ParseAttr(astAttr) ··· 391 388 d.errs = errors.Append(d.errs, attr.Err) 392 389 return nil 393 390 } 394 - c := d.compilers[attr.Name] 395 - if c == nil { 391 + inj := d.injectors[attr.Name] 392 + if inj == nil { 396 393 return nil 397 394 } 398 - if a, ok, _ := attr.Lookup(1, "name"); ok { 399 - name = a 395 + ea := &ExternAttr{ 396 + TopLevel: topLevel, 397 + Parent: parent, 398 + Attr: attr, 400 399 } 401 - b, err := c.Compile(name, scope, attr) 400 + b, err := inj.InjectedValue(ea, scope) 402 401 if err != nil { 403 402 d.errs = errors.Append(d.errs, errors.Wrap(errors.Newf(attr.Pos, "@%s", attr.Name), err)) 404 403 return nil
+24 -12
internal/core/runtime/extern_test.go
··· 20 20 "testing" 21 21 22 22 "cuelang.org/go/cue" 23 + "cuelang.org/go/cue/ast" 23 24 "cuelang.org/go/cue/build" 24 25 "cuelang.org/go/cue/cuecontext" 25 26 "cuelang.org/go/cue/errors" 26 27 "cuelang.org/go/cue/token" 27 - "cuelang.org/go/internal" 28 28 "cuelang.org/go/internal/core/adt" 29 29 "cuelang.org/go/internal/core/runtime" 30 30 "cuelang.org/go/internal/cuetxtar" ··· 38 38 } 39 39 40 40 test.Run(t, func(t *cuetxtar.Test) { 41 - interpreter := &interpreterFake{files: map[string]int{}} 42 - ctx := cuecontext.New(cuecontext.Interpreter(interpreter)) 41 + inj := &injectionFake{files: map[string]int{}} 42 + ctx := cuecontext.New(cuecontext.WithInjection(inj)) 43 43 44 44 b := t.Instance() 45 45 v := ctx.BuildInstance(b) ··· 52 52 }) 53 53 } 54 54 55 - type interpreterFake struct { 55 + type injectionFake struct { 56 56 files map[string]int 57 57 } 58 58 59 - func (i *interpreterFake) Kind() string { return "testfn" } 59 + func (i *injectionFake) Kind() string { return "testfn" } 60 60 61 - func (i *interpreterFake) NewCompiler(b *build.Instance, r *runtime.Runtime) (runtime.Compiler, errors.Error) { 61 + func (i *injectionFake) InjectorForInstance(b *build.Instance, r *runtime.Runtime) (runtime.Injector, errors.Error) { 62 62 switch b.PkgName { 63 63 case "failinit": 64 64 return nil, errors.Newf(token.NoPos, "TEST: fail initialization") 65 65 case "nullinit": 66 66 return nil, nil 67 67 case "scopetest": 68 - return newCompilerFake(b, r) 68 + return newInjectorFake(b, r) 69 69 } 70 70 return i, nil 71 71 } 72 72 73 - func (i *interpreterFake) Compile(funcName string, _ adt.Value, a *internal.Attr) (adt.Expr, errors.Error) { 73 + func (i *injectionFake) InjectedValue(attr *runtime.ExternAttr, _ *adt.Vertex) (adt.Expr, errors.Error) { 74 + a := attr.Attr 74 75 if ok, _ := a.Flag(1, "fail"); ok { 75 76 return nil, errors.Newf(token.NoPos, "TEST: fail compilation") 76 77 } ··· 92 93 i.files[str] = len(i.files) + 1 93 94 } 94 95 96 + // Derive the function name from the parent field label, 97 + // with an explicit name= attribute taking precedence. 98 + var funcName string 99 + if f, ok := attr.Parent.(*ast.Field); ok { 100 + funcName, _, _ = ast.LabelName(f.Label) 101 + } 102 + if name, ok, _ := a.Lookup(1, "name"); ok { 103 + funcName = name 104 + } 105 + 95 106 return &adt.Builtin{ 96 107 Name: "impl" + funcName + strconv.Itoa(i.files[str]), 97 108 Params: []adt.Param{{Value: &adt.BasicType{K: adt.IntKind}}}, ··· 99 110 }, nil 100 111 } 101 112 102 - type compilerFake struct { 113 + type injectorFake struct { 103 114 runtime *runtime.Runtime 104 115 b *build.Instance 105 116 } 106 117 107 - func newCompilerFake(b *build.Instance, r *runtime.Runtime) (runtime.Compiler, errors.Error) { 108 - return &compilerFake{ 118 + func newInjectorFake(b *build.Instance, r *runtime.Runtime) (runtime.Injector, errors.Error) { 119 + return &injectorFake{ 109 120 runtime: r, 110 121 b: b, 111 122 }, nil 112 123 } 113 124 114 - func (c *compilerFake) Compile(name string, scope adt.Value, a *internal.Attr) (adt.Expr, errors.Error) { 125 + func (c *injectorFake) InjectedValue(attr *runtime.ExternAttr, scope *adt.Vertex) (adt.Expr, errors.Error) { 126 + a := attr.Attr 115 127 typStr, err := a.String(0) 116 128 if err != nil { 117 129 return nil, errors.Promote(err, "test")
+2 -2
internal/core/runtime/runtime.go
··· 28 28 29 29 loaded map[*build.Instance]interface{} 30 30 31 - // interpreters implement extern functionality. The map key corresponds to 31 + // injections implement extern functionality. The map key corresponds to 32 32 // the kind in a file-level @extern(kind) attribute. 33 - interpreters map[string]Interpreter 33 + injections map[string]Injection 34 34 35 35 version internal.EvaluatorVersion 36 36
+3 -3
internal/core/runtime/testdata/errors.txtar
··· 93 93 ./double_extern_b.cue:2:1 94 94 duplicate @extern attribute for kind "testfn": 95 95 ./double_extern_b.cue:3:1 96 - interpreter name must be non-empty: 96 + injection name must be non-empty: 97 97 ./empty_extern.cue:1:1 98 - no interpreter defined for "\"testfn\" foo": 98 + no injection defined for "\"testfn\" foo": 99 99 ./invalid_file_attr.cue:1:1 100 100 extern attribute must appear before package clause: 101 101 ./late_extern.cue:3:1 102 - no interpreter defined for "wazem": 102 + no injection defined for "wazem": 103 103 ./unknown_interpreter.cue:1:1
+1 -1
internal/lsp/cache/package.go
··· 21 21 "strings" 22 22 23 23 "cuelang.org/go/cue/ast" 24 - "cuelang.org/go/cue/interpreter/embed" 24 + "cuelang.org/go/cue/inject/embed" 25 25 "cuelang.org/go/cue/token" 26 26 "cuelang.org/go/internal/golangorgx/gopls/protocol" 27 27 "cuelang.org/go/internal/lsp/eval"
+1 -1
internal/lsp/eval/eval.go
··· 339 339 "strings" 340 340 341 341 "cuelang.org/go/cue/ast" 342 - "cuelang.org/go/cue/interpreter/embed" 342 + "cuelang.org/go/cue/inject/embed" 343 343 "cuelang.org/go/cue/token" 344 344 "cuelang.org/go/internal/golangorgx/gopls/protocol" 345 345 "cuelang.org/go/internal/lsp/fscache"