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