1// Copyright 2019 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 task provides a registry for tasks to be used by commands. 16package task 17 18import ( 19 "context" 20 "io" 21 "sync" 22 23 "cuelang.org/go/cue" 24 "cuelang.org/go/cue/errors" 25 "cuelang.org/go/cue/token" 26 "cuelang.org/go/internal/value" 27) 28 29// A Context provides context for running a task. 30type Context struct { 31 Context context.Context 32 Stdin io.Reader 33 Stdout io.Writer 34 Stderr io.Writer 35 Obj cue.Value 36 Err errors.Error 37} 38 39func (c *Context) Lookup(field string) cue.Value { 40 f := c.Obj.Lookup(field) 41 if !f.Exists() { 42 c.addErr(f, nil, "could not find field %q", field) 43 return cue.Value{} 44 } 45 if err := f.Err(); err != nil { 46 c.Err = errors.Append(c.Err, errors.Promote(err, "lookup")) 47 } 48 return f 49} 50 51func (c *Context) Int64(field string) int64 { 52 f := c.Obj.Lookup(field) 53 value, err := f.Int64() 54 if err != nil { 55 // TODO: use v for position for now, as f has not yet a 56 // position associated with it. 57 c.addErr(f, err, "invalid integer argument") 58 return 0 59 } 60 return value 61} 62 63func (c *Context) String(field string) string { 64 f := c.Obj.Lookup(field) 65 value, err := f.String() 66 if err != nil { 67 // TODO: use v for position for now, as f has not yet a 68 // position associated with it. 69 c.addErr(f, err, "invalid string argument") 70 return "" 71 } 72 return value 73} 74 75func (c *Context) Bytes(field string) []byte { 76 f := c.Obj.Lookup(field) 77 value, err := f.Bytes() 78 if err != nil { 79 c.addErr(f, err, "invalid bytes argument") 80 return nil 81 } 82 return value 83} 84 85func (c *Context) addErr(v cue.Value, wrap error, format string, args ...interface{}) { 86 87 err := &taskError{ 88 task: c.Obj, 89 v: v, 90 Message: errors.NewMessage(format, args), 91 } 92 c.Err = errors.Append(c.Err, errors.Wrap(err, wrap)) 93} 94 95// taskError wraps some error values to retain position information about the 96// error. 97type taskError struct { 98 task cue.Value 99 v cue.Value 100 errors.Message 101} 102 103var _ errors.Error = &taskError{} 104 105func (t *taskError) Path() (a []string) { 106 for _, x := range t.v.Path().Selectors() { 107 a = append(a, x.String()) 108 } 109 return a 110} 111 112func (t *taskError) Position() token.Pos { 113 return t.task.Pos() 114} 115 116func (t *taskError) InputPositions() (a []token.Pos) { 117 _, nx := value.ToInternal(t.v) 118 119 for _, x := range nx.Conjuncts { 120 if src := x.Source(); src != nil { 121 a = append(a, src.Pos()) 122 } 123 } 124 return a 125} 126 127// A RunnerFunc creates a Runner. 128type RunnerFunc func(v cue.Value) (Runner, error) 129 130// A Runner defines a command type. 131type Runner interface { 132 // Init is called with the original configuration before any task is run. 133 // As a result, the configuration may be incomplete, but allows some 134 // validation before tasks are kicked off. 135 // Init(v cue.Value) 136 137 // Runner runs given the current value and returns a new value which is to 138 // be unified with the original result. 139 Run(ctx *Context) (results interface{}, err error) 140} 141 142// Register registers a task for cue commands. 143func Register(key string, f RunnerFunc) { 144 runners.Store(key, f) 145} 146 147// Lookup returns the RunnerFunc for a key. 148func Lookup(key string) RunnerFunc { 149 v, ok := runners.Load(key) 150 if !ok { 151 return nil 152 } 153 return v.(RunnerFunc) 154} 155 156var runners sync.Map 157