github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/juju/interact/pollster.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  	"bytes"
     9  	"fmt"
    10  	"html/template"
    11  	"io"
    12  	"net/url"
    13  	"os"
    14  	"sort"
    15  	"strconv"
    16  	"strings"
    17  
    18  	"github.com/juju/collections/set"
    19  	"github.com/juju/errors"
    20  	"github.com/juju/jsonschema"
    21  	"golang.org/x/crypto/ssh/terminal"
    22  )
    23  
    24  // Non standard json Format
    25  const FormatCertFilename jsonschema.Format = "cert-filename"
    26  
    27  // Pollster is used to ask multiple questions of the user using a standard
    28  // formatting.
    29  type Pollster struct {
    30  	VerifyURLs     VerifyFunc
    31  	VerifyCertFile VerifyFunc
    32  	scanner        *bufio.Scanner
    33  	out            io.Writer
    34  	errOut         io.Writer
    35  	in             io.Reader
    36  }
    37  
    38  // New returns a Pollster that wraps the given reader and writer.
    39  func New(in io.Reader, out, errOut io.Writer) *Pollster {
    40  	return &Pollster{
    41  		scanner: bufio.NewScanner(byteAtATimeReader{in}),
    42  		out:     out,
    43  		errOut:  errOut,
    44  		in:      in,
    45  	}
    46  }
    47  
    48  // List contains the information necessary to ask the user to select one item
    49  // from a list of options.
    50  type List struct {
    51  	Singular string
    52  	Plural   string
    53  	Options  []string
    54  	Default  string
    55  }
    56  
    57  // MultiList contains the information necessary to ask the user to select from a
    58  // list of options.
    59  type MultiList struct {
    60  	Singular string
    61  	Plural   string
    62  	Options  []string
    63  	Default  []string
    64  }
    65  
    66  var listTmpl = template.Must(template.New("").Funcs(map[string]interface{}{"title": strings.Title}).Parse(`
    67  {{title .Plural}}
    68  {{range .Options}}  {{.}}
    69  {{end}}
    70  `[1:]))
    71  
    72  var selectTmpl = template.Must(template.New("").Parse(`
    73  Select {{.Singular}}{{if .Default}} [{{.Default}}]{{end}}: `[1:]))
    74  
    75  // Select queries the user to select from the given list of options.
    76  func (p *Pollster) Select(l List) (string, error) {
    77  	return p.SelectVerify(l, VerifyOptions(l.Singular, l.Options, l.Default != ""))
    78  }
    79  
    80  // SelectVerify queries the user to select from the given list of options,
    81  // verifying the choice by passing responses through verify.
    82  func (p *Pollster) SelectVerify(l List, verify VerifyFunc) (string, error) {
    83  	if err := listTmpl.Execute(p.out, l); err != nil {
    84  		return "", err
    85  	}
    86  
    87  	question, err := sprint(selectTmpl, l)
    88  	if err != nil {
    89  		return "", errors.Trace(err)
    90  	}
    91  	val, err := QueryVerify(question, p.scanner, p.out, p.errOut, verify)
    92  	if err != nil {
    93  		return "", errors.Trace(err)
    94  	}
    95  	if val == "" {
    96  		return l.Default, nil
    97  	}
    98  	return val, nil
    99  }
   100  
   101  var multiSelectTmpl = template.Must(template.New("").Funcs(
   102  	map[string]interface{}{"join": strings.Join}).Parse(`
   103  Select one or more {{.Plural}} separated by commas{{if .Default}} [{{join .Default ", "}}]{{end}}: `[1:]))
   104  
   105  // MultiSelect queries the user to select one more answers from the given list of
   106  // options by entering values delimited by commas (and thus options must not
   107  // contain commas).
   108  func (p *Pollster) MultiSelect(l MultiList) ([]string, error) {
   109  	var bad []string
   110  	for _, s := range l.Options {
   111  		if strings.Contains(s, ",") {
   112  			bad = append(bad, s)
   113  		}
   114  	}
   115  	if len(bad) > 0 {
   116  		return nil, errors.Errorf("options may not contain commas: %q", bad)
   117  	}
   118  	if err := listTmpl.Execute(p.out, l); err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	// If there is only ever one option and that option also equals the default
   123  	// option, then just echo out what that option is (above), then return that
   124  	// option back.
   125  	if len(l.Default) == 1 && len(l.Options) == 1 && l.Options[0] == l.Default[0] {
   126  		return l.Default, nil
   127  	}
   128  
   129  	question, err := sprint(multiSelectTmpl, l)
   130  	if err != nil {
   131  		return nil, errors.Trace(err)
   132  	}
   133  	verify := multiVerify(l.Singular, l.Plural, l.Options, l.Default != nil)
   134  	a, err := QueryVerify(question, p.scanner, p.out, p.errOut, verify)
   135  	if err != nil {
   136  		return nil, errors.Trace(err)
   137  	}
   138  	if a == "" {
   139  		return l.Default, nil
   140  	}
   141  	return multiSplit(a), nil
   142  }
   143  
   144  // Enter requests that the user enter a value.  Any value except an empty string
   145  // is accepted.
   146  func (p *Pollster) Enter(valueName string) (string, error) {
   147  	return p.EnterVerify(valueName, func(s string) (ok bool, msg string, err error) {
   148  		return s != "", "", nil
   149  	})
   150  }
   151  
   152  // EnterPassword works like Enter except that if the pollster's input wraps a
   153  // terminal, the user's input will be read without local echo.
   154  func (p *Pollster) EnterPassword(valueName string) (string, error) {
   155  	if f, ok := p.in.(*os.File); ok && terminal.IsTerminal(int(f.Fd())) {
   156  		defer fmt.Fprint(p.out, "\n\n")
   157  		if _, err := fmt.Fprintf(p.out, "Enter "+valueName+": "); err != nil {
   158  			return "", errors.Trace(err)
   159  		}
   160  		value, err := terminal.ReadPassword(int(f.Fd()))
   161  		if err != nil {
   162  			return "", errors.Trace(err)
   163  		}
   164  		return string(value), nil
   165  	}
   166  	return p.Enter(valueName)
   167  }
   168  
   169  // EnterDefault requests that the user enter a value.  Any value is accepted.
   170  // An empty string is treated as defVal.
   171  func (p *Pollster) EnterDefault(valueName, defVal string) (string, error) {
   172  	return p.EnterVerifyDefault(valueName, nil, defVal)
   173  }
   174  
   175  // VerifyFunc is a type that determines whether a value entered by the user is
   176  // acceptable or not.  If it returns an error, the calling func will return an
   177  // error, and the other return values are ignored.  If ok is true, the value is
   178  // acceptable, and that value will be returned by the calling function.  If ok
   179  // is false, the user will be asked to enter a new value for query.  If ok is
   180  // false. if errmsg is not empty, it will be printed out as an error to te the
   181  // user.
   182  type VerifyFunc func(s string) (ok bool, errmsg string, err error)
   183  
   184  // EnterVerify requests that the user enter a value.  Values failing to verify
   185  // will be rejected with the error message returned by verify.  A nil verify
   186  // function will accept any value (even an empty string).
   187  func (p *Pollster) EnterVerify(valueName string, verify VerifyFunc) (string, error) {
   188  	return QueryVerify("Enter "+valueName+": ", p.scanner, p.out, p.errOut, verify)
   189  }
   190  
   191  // EnterOptional requests that the user enter a value.  It accepts any value,
   192  // even an empty string.
   193  func (p *Pollster) EnterOptional(valueName string) (string, error) {
   194  	return QueryVerify("Enter "+valueName+" (optional): ", p.scanner, p.out, p.errOut, nil)
   195  }
   196  
   197  // EnterVerifyDefault requests that the user enter a value.  Values failing to
   198  // verify will be rejected with the error message returned by verify.  An empty
   199  // string will be accepted as the default value even if it would fail
   200  // verification.
   201  func (p *Pollster) EnterVerifyDefault(valueName string, verify VerifyFunc, defVal string) (string, error) {
   202  	var verifyDefault VerifyFunc
   203  	if verify != nil {
   204  		verifyDefault = func(s string) (ok bool, errmsg string, err error) {
   205  			if s == "" {
   206  				return true, "", nil
   207  			}
   208  			return verify(s)
   209  		}
   210  	}
   211  	s, err := QueryVerify("Enter "+valueName+" ["+defVal+"]: ", p.scanner, p.out, p.errOut, verifyDefault)
   212  	if err != nil {
   213  		return "", errors.Trace(err)
   214  	}
   215  	if s == "" {
   216  		return defVal, nil
   217  	}
   218  	return s, nil
   219  }
   220  
   221  // YN queries the user with a yes no question q (which should not include a
   222  // question mark at the end).  It uses defVal as the default answer.
   223  func (p *Pollster) YN(q string, defVal bool) (bool, error) {
   224  	defaultStr := "(y/N)"
   225  	if defVal {
   226  		defaultStr = "(Y/n)"
   227  	}
   228  	verify := func(s string) (ok bool, errmsg string, err error) {
   229  		_, err = yesNoConvert(s, defVal)
   230  		if err != nil {
   231  			return false, err.Error(), nil
   232  		}
   233  		return true, "", nil
   234  	}
   235  	a, err := QueryVerify(q+"? "+defaultStr+": ", p.scanner, p.out, p.errOut, verify)
   236  	if err != nil {
   237  		return false, errors.Trace(err)
   238  	}
   239  	return yesNoConvert(a, defVal)
   240  }
   241  
   242  func yesNoConvert(s string, defVal bool) (bool, error) {
   243  	if s == "" {
   244  		return defVal, nil
   245  	}
   246  	switch strings.ToLower(s) {
   247  	case "y", "yes":
   248  		return true, nil
   249  	case "n", "no":
   250  		return false, nil
   251  	default:
   252  		return false, errors.Errorf("Invalid entry: %q, please choose y or n.", s)
   253  	}
   254  }
   255  
   256  // VerifyOptions is the verifier used by pollster.Select.
   257  func VerifyOptions(singular string, options []string, hasDefault bool) VerifyFunc {
   258  	return func(s string) (ok bool, errmsg string, err error) {
   259  		if s == "" {
   260  			return hasDefault, "", nil
   261  		}
   262  		for _, opt := range options {
   263  			if strings.ToLower(opt) == strings.ToLower(s) {
   264  				return true, "", nil
   265  			}
   266  		}
   267  		return false, fmt.Sprintf("Invalid %s: %q", singular, s), nil
   268  	}
   269  }
   270  
   271  func multiVerify(singular, plural string, options []string, hasDefault bool) VerifyFunc {
   272  	return func(s string) (ok bool, errmsg string, err error) {
   273  		if s == "" {
   274  			return hasDefault, "", nil
   275  		}
   276  		vals := set.NewStrings(multiSplit(s)...)
   277  		opts := set.NewStrings(options...)
   278  		unknowns := vals.Difference(opts)
   279  		if len(unknowns) > 1 {
   280  			list := `"` + strings.Join(unknowns.SortedValues(), `", "`) + `"`
   281  			return false, fmt.Sprintf("Invalid %s: %s", plural, list), nil
   282  		}
   283  		if len(unknowns) > 0 {
   284  			return false, fmt.Sprintf("Invalid %s: %q", singular, unknowns.Values()[0]), nil
   285  		}
   286  		return true, "", nil
   287  	}
   288  }
   289  
   290  func multiSplit(s string) []string {
   291  	chosen := strings.Split(s, ",")
   292  	for i := range chosen {
   293  		chosen[i] = strings.TrimSpace(chosen[i])
   294  	}
   295  	return chosen
   296  }
   297  
   298  func sprint(t *template.Template, data interface{}) (string, error) {
   299  	b := &bytes.Buffer{}
   300  	if err := t.Execute(b, data); err != nil {
   301  		return "", err
   302  	}
   303  	return b.String(), nil
   304  }
   305  
   306  // QuerySchema takes a jsonschema and queries the user to input value(s) for the
   307  // schema.  It returns an object as defined by the schema (generally a
   308  // map[string]interface{} for objects, etc).
   309  func (p *Pollster) QuerySchema(schema *jsonschema.Schema) (interface{}, error) {
   310  	if len(schema.Type) == 0 {
   311  		return nil, errors.Errorf("invalid schema, no type specified")
   312  	}
   313  	if len(schema.Type) > 1 {
   314  		return nil, errors.Errorf("don't know how to query for a value with multiple types")
   315  	}
   316  	var v interface{}
   317  	var err error
   318  	if schema.Type[0] == jsonschema.ObjectType {
   319  		v, err = p.queryObjectSchema(schema)
   320  	} else {
   321  		v, err = p.queryOneSchema(schema)
   322  	}
   323  	if err != nil {
   324  		return nil, errors.Trace(err)
   325  	}
   326  	return v, nil
   327  }
   328  
   329  func (p *Pollster) queryObjectSchema(schema *jsonschema.Schema) (map[string]interface{}, error) {
   330  	// TODO(natefinch): support for optional values.
   331  	vals := map[string]interface{}{}
   332  	if len(schema.Order) != 0 {
   333  		m, err := p.queryOrder(schema)
   334  		if err != nil {
   335  			return nil, errors.Trace(err)
   336  		}
   337  		vals = m
   338  	} else {
   339  		// traverse alphabetically
   340  		for _, name := range names(schema.Properties) {
   341  			v, err := p.queryProp(schema.Properties[name])
   342  			if err != nil {
   343  				return nil, errors.Trace(err)
   344  			}
   345  			vals[name] = v
   346  		}
   347  	}
   348  
   349  	if schema.AdditionalProperties != nil {
   350  		if err := p.queryAdditionalProps(vals, schema); err != nil {
   351  			return nil, errors.Trace(err)
   352  		}
   353  	}
   354  
   355  	return vals, nil
   356  }
   357  
   358  // names returns the list of names of schema in alphabetical order.
   359  func names(m map[string]*jsonschema.Schema) []string {
   360  	ret := make([]string, 0, len(m))
   361  	for n := range m {
   362  		ret = append(ret, n)
   363  	}
   364  	sort.Strings(ret)
   365  	return ret
   366  }
   367  
   368  func (p *Pollster) queryOrder(schema *jsonschema.Schema) (map[string]interface{}, error) {
   369  	vals := map[string]interface{}{}
   370  	for _, name := range schema.Order {
   371  		prop, ok := schema.Properties[name]
   372  		if !ok {
   373  			return nil, errors.Errorf("property %q from Order not in schema", name)
   374  		}
   375  		v, err := p.queryProp(prop)
   376  		if err != nil {
   377  			return nil, errors.Trace(err)
   378  		}
   379  		vals[name] = v
   380  	}
   381  	return vals, nil
   382  }
   383  
   384  func (p *Pollster) queryProp(prop *jsonschema.Schema) (interface{}, error) {
   385  	if isObject(prop) {
   386  		return p.queryObjectSchema(prop)
   387  	}
   388  	return p.queryOneSchema(prop)
   389  }
   390  
   391  func (p *Pollster) queryAdditionalProps(vals map[string]interface{}, schema *jsonschema.Schema) error {
   392  	if schema.AdditionalProperties.Type[0] != jsonschema.ObjectType {
   393  		return errors.Errorf("don't know how to query for additional properties of type %q", schema.AdditionalProperties.Type[0])
   394  	}
   395  
   396  	verifyName := func(s string) (ok bool, errmsg string, err error) {
   397  		if s == "" {
   398  			return false, "", nil
   399  		}
   400  		if _, ok := vals[s]; ok {
   401  			return false, fmt.Sprintf("%s %q already exists", strings.Title(schema.Singular), s), nil
   402  		}
   403  		return true, "", nil
   404  	}
   405  
   406  	localEnvVars := func(envVars []string) string {
   407  		for _, envVar := range envVars {
   408  			if value, ok := os.LookupEnv(envVar); ok && value != "" {
   409  				return value
   410  			}
   411  		}
   412  		return ""
   413  	}
   414  
   415  	// Currently we assume we always prompt for at least one value for
   416  	// additional properties, but we may want to change this to ask if they want
   417  	// to enter any at all.
   418  	for {
   419  		// We assume that the name of the schema is the name of the object the
   420  		// schema describes, and for additional properties the property name
   421  		// (i.e. map key) is the "name" of the thing.
   422  		var name string
   423  		var err error
   424  
   425  		// Note: here we check that schema.Default is empty as well.
   426  		defFromEnvVar := localEnvVars(schema.EnvVars)
   427  		if (schema.Default == nil || schema.Default == "") && defFromEnvVar == "" {
   428  			name, err = p.EnterVerify(schema.Singular+" name", verifyName)
   429  			if err != nil {
   430  				return errors.Trace(err)
   431  			}
   432  		} else {
   433  			// If we set a prompt default, that'll get returned as the value,
   434  			// but it's not the actual value that is the default, so fix that,
   435  			// if an environment variable wasn't used.
   436  			var def string
   437  			if schema.PromptDefault != nil {
   438  				def = fmt.Sprintf("%v", schema.PromptDefault)
   439  			}
   440  			if defFromEnvVar != "" {
   441  				def = defFromEnvVar
   442  			}
   443  			if def == "" {
   444  				def = fmt.Sprintf("%v", schema.Default)
   445  			}
   446  
   447  			name, err = p.EnterVerifyDefault(schema.Singular, verifyName, def)
   448  			if err != nil {
   449  				return errors.Trace(err)
   450  			}
   451  
   452  			if name == def && schema.PromptDefault != nil && name != defFromEnvVar {
   453  				name = fmt.Sprintf("%v", schema.Default)
   454  			}
   455  		}
   456  
   457  		v, err := p.queryObjectSchema(schema.AdditionalProperties)
   458  		if err != nil {
   459  			return errors.Trace(err)
   460  		}
   461  		vals[name] = v
   462  		more, err := p.YN("Enter another "+schema.Singular, false)
   463  		if err != nil {
   464  			return errors.Trace(err)
   465  		}
   466  		if !more {
   467  			break
   468  		}
   469  	}
   470  
   471  	return nil
   472  }
   473  
   474  func (p *Pollster) queryOneSchema(schema *jsonschema.Schema) (interface{}, error) {
   475  	if len(schema.Type) == 0 {
   476  		return nil, errors.Errorf("invalid schema, no type specified")
   477  	}
   478  	if len(schema.Type) > 1 {
   479  		return nil, errors.Errorf("don't know how to query for a value with multiple types")
   480  	}
   481  	if len(schema.Enum) == 1 {
   482  		// if there's only one possible value, don't bother prompting, just
   483  		// return that value.
   484  		return schema.Enum[0], nil
   485  	}
   486  	if schema.Type[0] == jsonschema.ArrayType {
   487  		return p.queryArray(schema)
   488  	}
   489  	if len(schema.Enum) > 1 {
   490  		return p.selectOne(schema)
   491  	}
   492  	var verify VerifyFunc
   493  	switch schema.Format {
   494  	case "":
   495  		// verify stays nil
   496  	case jsonschema.FormatURI:
   497  		if p.VerifyURLs != nil {
   498  			verify = p.VerifyURLs
   499  		} else {
   500  			verify = uriVerify
   501  		}
   502  	case FormatCertFilename:
   503  		if p.VerifyCertFile != nil {
   504  			verify = p.VerifyCertFile
   505  		}
   506  	default:
   507  		// TODO(natefinch): support more formats
   508  		return nil, errors.Errorf("unsupported format type: %q", schema.Format)
   509  	}
   510  
   511  	if schema.Default == nil {
   512  		a, err := p.EnterVerify(schema.Singular, verify)
   513  		if err != nil {
   514  			return nil, errors.Trace(err)
   515  		}
   516  		return convert(a, schema.Type[0])
   517  	}
   518  
   519  	var def string
   520  	if schema.PromptDefault != nil {
   521  		def = fmt.Sprintf("%v", schema.PromptDefault)
   522  	}
   523  	var defFromEnvVar string
   524  	if len(schema.EnvVars) > 0 {
   525  		for _, envVar := range schema.EnvVars {
   526  			value := os.Getenv(envVar)
   527  			if value != "" {
   528  				defFromEnvVar = value
   529  				def = defFromEnvVar
   530  				break
   531  			}
   532  		}
   533  	}
   534  	if def == "" {
   535  		def = fmt.Sprintf("%v", schema.Default)
   536  	}
   537  
   538  	a, err := p.EnterVerifyDefault(schema.Singular, verify, def)
   539  	if err != nil {
   540  		return nil, errors.Trace(err)
   541  	}
   542  
   543  	// If we set a prompt default, that'll get returned as the value,
   544  	// but it's not the actual value that is the default, so fix that,
   545  	// if an environment variable wasn't used.
   546  	if a == def && schema.PromptDefault != nil && a != defFromEnvVar {
   547  		a = fmt.Sprintf("%v", schema.Default)
   548  	}
   549  
   550  	return convert(a, schema.Type[0])
   551  }
   552  
   553  func (p *Pollster) queryArray(schema *jsonschema.Schema) (interface{}, error) {
   554  	if !supportedArraySchema(schema) {
   555  		b, err := schema.MarshalJSON()
   556  		if err != nil {
   557  			// shouldn't ever happen
   558  			return nil, errors.Errorf("unsupported schema for an array")
   559  		}
   560  		return nil, errors.Errorf("unsupported schema for an array: %s", b)
   561  	}
   562  	var def string
   563  	if schema.Default != nil {
   564  		def = schema.Default.(string)
   565  	}
   566  	if schema.PromptDefault != nil {
   567  		def = schema.PromptDefault.(string)
   568  	}
   569  	var array []string
   570  	if def != "" {
   571  		array = []string{def}
   572  	}
   573  	return p.MultiSelect(MultiList{
   574  		Singular: schema.Singular,
   575  		Plural:   schema.Plural,
   576  		Options:  optFromEnum(schema.Items.Schemas[0]),
   577  		Default:  array,
   578  	})
   579  }
   580  
   581  func uriVerify(s string) (ok bool, errMsg string, err error) {
   582  	if s == "" {
   583  		return false, "", nil
   584  	}
   585  	_, err = url.Parse(s)
   586  	if err != nil {
   587  		return false, fmt.Sprintf("Invalid URI: %q", s), nil
   588  	}
   589  	return true, "", nil
   590  }
   591  
   592  func supportedArraySchema(schema *jsonschema.Schema) bool {
   593  	// TODO(natefinch): support arrays without schemas.
   594  	// TODO(natefinch): support arrays with multiple schemas.
   595  	// TODO(natefinch): support arrays without Enums.
   596  	if schema.Items == nil ||
   597  		len(schema.Items.Schemas) != 1 ||
   598  		len(schema.Items.Schemas[0].Enum) == 0 ||
   599  		len(schema.Items.Schemas[0].Type) != 1 {
   600  		return false
   601  	}
   602  	switch schema.Items.Schemas[0].Type[0] {
   603  	case jsonschema.IntegerType,
   604  		jsonschema.StringType,
   605  		jsonschema.BooleanType,
   606  		jsonschema.NumberType:
   607  		return true
   608  	default:
   609  		return false
   610  	}
   611  }
   612  
   613  func optFromEnum(schema *jsonschema.Schema) []string {
   614  	ret := make([]string, len(schema.Enum))
   615  	for i := range schema.Enum {
   616  		ret[i] = fmt.Sprint(schema.Enum[i])
   617  	}
   618  	return ret
   619  }
   620  
   621  func (p *Pollster) selectOne(schema *jsonschema.Schema) (interface{}, error) {
   622  	options := make([]string, len(schema.Enum))
   623  	for i := range schema.Enum {
   624  		options[i] = fmt.Sprint(schema.Enum[i])
   625  	}
   626  	def := ""
   627  	if schema.Default != nil {
   628  		def = fmt.Sprint(schema.Default)
   629  	}
   630  	a, err := p.Select(List{
   631  		Singular: schema.Singular,
   632  		Plural:   schema.Plural,
   633  		Options:  options,
   634  		Default:  def,
   635  	})
   636  	if err != nil {
   637  		return nil, errors.Trace(err)
   638  	}
   639  	if schema.Default != nil && a == "" {
   640  		return schema.Default, nil
   641  	}
   642  	return convert(a, schema.Type[0])
   643  }
   644  
   645  func isObject(schema *jsonschema.Schema) bool {
   646  	for _, t := range schema.Type {
   647  		if t == jsonschema.ObjectType {
   648  			return true
   649  		}
   650  	}
   651  	return false
   652  }
   653  
   654  // convert converts the given string to a specific value based on the schema
   655  // type that validated it.
   656  func convert(s string, t jsonschema.Type) (interface{}, error) {
   657  	switch t {
   658  	case jsonschema.IntegerType:
   659  		return strconv.Atoi(s)
   660  	case jsonschema.NumberType:
   661  		return strconv.ParseFloat(s, 64)
   662  	case jsonschema.StringType:
   663  		return s, nil
   664  	case jsonschema.BooleanType:
   665  		switch strings.ToLower(s) {
   666  		case "y", "yes", "true", "t":
   667  			return true, nil
   668  		case "n", "no", "false", "f":
   669  			return false, nil
   670  		default:
   671  			return nil, errors.Errorf("unknown value for boolean type: %q", s)
   672  		}
   673  	default:
   674  		return nil, errors.Errorf("don't know how to convert value %q of type %q", s, t)
   675  	}
   676  }
   677  
   678  // byteAtATimeReader causes all reads to return a single byte.  This prevents
   679  // things line bufio.scanner from reading past the end of a line, which can
   680  // cause problems when we do wacky things like reading directly from the
   681  // terminal for password style prompts.
   682  type byteAtATimeReader struct {
   683  	io.Reader
   684  }
   685  
   686  func (r byteAtATimeReader) Read(out []byte) (int, error) {
   687  	return r.Reader.Read(out[:1])
   688  }