github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/interact/query.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package interact 5 6 import ( 7 "bufio" 8 "fmt" 9 "io" 10 "strings" 11 ) 12 13 // QueryVerify writes a question to w and waits for an answer to be read from 14 // scanner. It will pass the answer into the verify function. Verify, if 15 // non-nil, should check the answer for validity, returning an error that will 16 // be written out to errOut, or nil if answer is valid. 17 // 18 // This function takes a scanner rather than an io.Reader to avoid the case 19 // where the scanner reads past the delimiter and thus might lose data. It is 20 // expected that this method will be used repeatedly with the same scanner if 21 // multiple queries are required. 22 func QueryVerify(question string, scanner *bufio.Scanner, out, errOut io.Writer, verify VerifyFunc) (answer string, err error) { 23 defer fmt.Fprint(out, "\n") 24 for { 25 if _, err = out.Write([]byte(question)); err != nil { 26 return "", err 27 } 28 29 done := !scanner.Scan() 30 31 if done { 32 if err := scanner.Err(); err != nil { 33 return "", err 34 } 35 } 36 answer = scanner.Text() 37 if done && answer == "" { 38 // EOF 39 return "", io.EOF 40 } 41 if verify == nil { 42 return answer, nil 43 } 44 ok, msg, err := verify(answer) 45 if err != nil { 46 return "", err 47 } 48 // valid answer, return it! 49 if ok { 50 return answer, nil 51 } 52 53 // invalid answer, inform user of problem and retry. 54 if msg != "" { 55 _, err := fmt.Fprint(errOut, msg+"\n") 56 if err != nil { 57 return "", err 58 } 59 } 60 _, err = errOut.Write([]byte{'\n'}) 61 if err != nil { 62 return "", err 63 } 64 65 if done { 66 // can't query any more, nothing we can do. 67 return "", io.EOF 68 } 69 } 70 } 71 72 // MatchOptions returns a function that performs a case insensitive comparison 73 // against the given list of options. To make a verification function that 74 // accepts an empty default, include an empty string in the list. 75 func MatchOptions(options []string, errmsg string) VerifyFunc { 76 return func(s string) (ok bool, msg string, err error) { 77 for _, opt := range options { 78 if strings.ToLower(opt) == strings.ToLower(s) { 79 return true, "", nil 80 } 81 } 82 return false, errmsg, nil 83 } 84 } 85 86 // FindMatch does a case-insensitive search of the given options and returns the 87 // matching option. Found reports whether s was found in the options. 88 func FindMatch(s string, options []string) (match string, found bool) { 89 for _, opt := range options { 90 if strings.ToLower(opt) == strings.ToLower(s) { 91 return opt, true 92 } 93 } 94 return "", false 95 }