package bootstrap import ( "fmt" "strings" ) // What the installer asks a person, and how an unattended run answers. // // **The installer goes as far as it can, and where a human must choose, it asks** — a packet // filter is required, so the question is not whether but which. A run with a terminal is asked; // a run without one (the lab, an unattended machine) answers with flags, and a required choice // with no flag, no terminal and no lone option is a refusal rather than a guess. // Choice is one question the installer needs answered. type Choice struct { // Name is the flag that answers it unattended: --packet-filter, --private-network. Name string // Question is what a person is asked, ending with what the options are. Question string // Options are the acceptable answers. Empty means free-form (an address, a list). Options []string // Default is used when nothing and nobody answered. Empty means the choice is required. Default string } // decide resolves one choice, in the order a person would expect: // // an explicit answer (the flag) wins; a lone option answers itself, said aloud; a terminal is // asked; a default fills in; and a required choice nothing answered is refused naming its flag. func decide(c Choice, answered string, prompt func(Choice) (string, error), say func(string)) (string, error) { if got := strings.TrimSpace(answered); got != "" { return validated(c, got) } if len(c.Options) == 1 { // The only answer there is. Said rather than silent, because "it chose for me" and "there // was nothing to choose" read identically afterwards unless one of them says so. say(fmt.Sprintf(" %-17s %s — the only option there is", c.Name, c.Options[0])) return c.Options[0], nil } if prompt != nil { got, err := prompt(c) if err != nil { return "", err } if strings.TrimSpace(got) == "" && c.Default != "" { say(fmt.Sprintf(" %-17s %s (default)", c.Name, c.Default)) return c.Default, nil } return validated(c, strings.TrimSpace(got)) } if c.Default != "" { say(fmt.Sprintf(" %-17s %s (default)", c.Name, c.Default)) return c.Default, nil } return "", fmt.Errorf( "%s must be chosen and nothing chose it: no --%s, no terminal to ask on%s", c.Name, c.Name, orOptions(c)) } func validated(c Choice, got string) (string, error) { if len(c.Options) == 0 { return got, nil } for _, option := range c.Options { if got == option { return got, nil } } return "", fmt.Errorf("%q is not a %s this mesh offers. It has %s", got, c.Name, strings.Join(c.Options, ", ")) } func orOptions(c Choice) string { if len(c.Options) == 0 { return "" } return ". It offers " + strings.Join(c.Options, ", ") }