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
15package cli
16
17import (
18 "bufio"
19 "fmt"
20 "io"
21 "strings"
22
23 "cuelang.org/go/cue"
24 "cuelang.org/go/internal/task"
25)
26
27func init() {
28 task.Register("tool/cli.Print", newPrintCmd)
29 task.Register("tool/cli.Ask", newAskCmd)
30
31 // For backwards compatibility.
32 task.Register("print", newPrintCmd)
33}
34
35type printCmd struct{}
36
37func newPrintCmd(v cue.Value) (task.Runner, error) {
38 return &printCmd{}, nil
39}
40
41func (c *printCmd) Run(ctx *task.Context) (res interface{}, err error) {
42 str := ctx.String("text")
43 if ctx.Err != nil {
44 return nil, ctx.Err
45 }
46 fmt.Fprintln(ctx.Stdout, str)
47 return nil, nil
48}
49
50type askCmd struct{}
51
52func newAskCmd(v cue.Value) (task.Runner, error) {
53 return &askCmd{}, nil
54}
55
56type oneByteReader struct {
57 r io.Reader
58}
59
60func (r *oneByteReader) Read(p []byte) (int, error) {
61 if len(p) == 0 {
62 return 0, nil
63 }
64 return r.r.Read(p[:1])
65}
66
67func (c *askCmd) Run(ctx *task.Context) (res interface{}, err error) {
68 str := ctx.String("prompt")
69 if ctx.Err != nil {
70 return nil, ctx.Err
71 }
72 if str != "" {
73 fmt.Fprint(ctx.Stdout, str+" ")
74 }
75
76 // Roger is convinced that bufio.Scanner will only issue as many reads
77 // as it needs, so that limiting it to one-byte reads should be enough
78 // to not read any bytes after a newline.
79 // This behavior is true today but technically not documented,
80 // so Roger will send a CL to properly document it.
81 //
82 // TODO(mvdan): come back to remove this notice once Roger's CL is
83 // approved, or to rewrite the code if it is rejected.
84 scanner := bufio.NewScanner(&oneByteReader{ctx.Stdin})
85 var response string
86 if scanner.Scan() {
87 response = scanner.Text()
88 }
89 if err := scanner.Err(); err != nil {
90 return nil, err
91 }
92
93 update := map[string]interface{}{"response": response}
94
95 switch v := ctx.Lookup("response"); v.IncompleteKind() {
96 case cue.BoolKind:
97 update["response"] = strings.ToLower(response) == "yes"
98 case cue.StringKind:
99 // already set above
100 }
101 return update, nil
102}