evcc-io/cmd/configure/survey.go
2023-08-17 21:41:44 +02:00

256 lines
6.9 KiB
Go

package configure
import (
"errors"
"fmt"
"os"
"slices"
"strconv"
"strings"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2/terminal"
"github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/util/templates"
stripmd "github.com/writeas/go-strip-markdown/v2"
)
// surveyAskOne asks the user for input
func (c *CmdConfigure) surveyAskOne(p survey.Prompt, response interface{}, opts ...survey.AskOpt) error {
opts = append(opts, survey.WithIcons(func(icons *survey.IconSet) {
icons.Question.Text = ""
}))
err := survey.AskOne(p, response, opts...)
if err != nil {
if err == terminal.InterruptErr {
fmt.Println(c.localizedString("Cancel"))
os.Exit(0)
}
fmt.Printf("%s %s\n", c.localizedString("InputError"), err)
}
return err
}
// askConfigFailureNextStep asks the user if he/she wants to select another device because the current does not work, or continue
func (c *CmdConfigure) askConfigFailureNextStep() bool {
fmt.Println()
return c.askYesNo(c.localizedString("TestingDevice_RepeatStep"))
}
// select item from list
func (c *CmdConfigure) askSelection(message string, items []string) (string, int, error) {
prompt := &survey.Select{
Message: message,
Options: items,
}
var selection string
err := c.surveyAskOne(prompt, &selection)
return selection, slices.Index(items, selection), err
}
// selectItem selects item from list
func (c *CmdConfigure) selectItem(deviceCategory DeviceCategory) templates.Template {
var emptyItem templates.Template
emptyItem.SetTitle(c.localizedString("ItemNotPresent"))
elements := c.fetchElements(deviceCategory)
elements = append(elements, emptyItem)
var items []string
for _, item := range elements {
if item.Title() != "" {
items = append(items, item.Title())
}
}
text := fmt.Sprintf("%s %s %s:", c.localizedString("Choose"), DeviceCategories[deviceCategory].article, DeviceCategories[deviceCategory].title)
_, selected, err := c.askSelection(text, items)
if err != nil {
c.log.FATAL.Fatal(err)
}
return elements[selected]
}
// askChoice selects item from list
func (c *CmdConfigure) askChoice(label string, choices []string) (int, string) {
selection, index, err := c.askSelection(label, choices)
if err != nil {
c.log.FATAL.Fatal(err)
}
return index, selection
}
// askYesNo asks yes/no question, return true if yes is selected
func (c *CmdConfigure) askYesNo(label string) bool {
prompt := &survey.Confirm{
Message: label,
}
var confirmation bool
if err := c.surveyAskOne(prompt, &confirmation); err != nil {
c.log.FATAL.Fatal(err)
}
return confirmation
}
type question struct {
label, help string
defaultValue, exampleValue string
invalidValues []string
validValues []string
valueType templates.ParamType
minNumberValue, maxNumberValue int64
mask, required bool
excludeNone bool
}
// askBoolValue asks for a boolean value selection for a given question
func (c *CmdConfigure) askBoolValue(label string) string {
choices := []string{c.localizedString("Config_No"), c.localizedString("Config_Yes")}
values := []string{"false", "true"}
index, _ := c.askChoice(label, choices)
return values[index]
}
func (c *CmdConfigure) askParam(p templates.Param) string {
var mask, required bool
if p.Mask != nil {
mask = *p.Mask
}
if p.Required != nil {
required = *p.Required
}
return c.askValue(question{
label: p.Description.String(c.lang),
valueType: p.Type,
validValues: p.Choice, // TODO proper choice handling
mask: mask,
required: required,
})
}
// askValue asks for value input for a given question (template param)
func (c *CmdConfigure) askValue(q question) string {
if q.valueType == templates.TypeBool {
label := q.label
if q.help != "" {
helpDescription := stripmd.Strip(q.help)
fmt.Println("-------------------------------------------------")
fmt.Println(c.localizedString("Value_Help"))
fmt.Println(helpDescription)
fmt.Println("-------------------------------------------------")
}
return c.askBoolValue(label)
}
if q.valueType == templates.TypeChoice {
label := strings.TrimSpace(strings.Join([]string{q.label, c.localizedString("Value_Choice")}, " "))
idx, _ := c.askChoice(label, q.validValues)
return q.validValues[idx]
}
if q.valueType == templates.TypeChargeModes {
chargingModes := []string{string(api.ModeOff), string(api.ModeNow), string(api.ModeMinPV), string(api.ModePV)}
chargeModes := []string{
c.localizedString("ChargeModeOff"),
c.localizedString("ChargeModeNow"),
c.localizedString("ChargeModeMinPV"),
c.localizedString("ChargeModePV"),
}
if !q.excludeNone {
chargingModes = append(chargingModes, "")
chargeModes = append(chargeModes, c.localizedString("ChargeModeNone"))
}
modeChoice, _ := c.askChoice(c.localizedString("ChargeMode_Question"), chargeModes)
return chargingModes[modeChoice]
}
validate := func(val interface{}) error {
value := val.(string)
if q.invalidValues != nil && slices.Contains(q.invalidValues, value) {
return errors.New(c.localizedString("ValueError_Used"))
}
if q.validValues != nil && !slices.Contains(q.validValues, value) {
return errors.New(c.localizedString("ValueError_Invalid"))
}
if q.required && len(value) == 0 {
return errors.New(c.localizedString("ValueError_Empty"))
}
if !q.required && len(value) == 0 {
return nil
}
if q.valueType == templates.TypeFloat {
_, err := strconv.ParseFloat(value, 64)
if err != nil {
return errors.New(c.localizedString("ValueError_Float"))
}
}
if q.valueType == templates.TypeNumber {
intValue, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return errors.New(c.localizedString("ValueError_Number"))
}
if q.minNumberValue != 0 && intValue < q.minNumberValue {
return errors.New(c.localizedString("ValueError_NumberLowerThanMin", localizeMap{"Min": q.minNumberValue}))
}
if q.maxNumberValue != 0 && intValue > q.maxNumberValue {
return errors.New(c.localizedString("ValueError_NumberBiggerThanMax", localizeMap{"Max": q.maxNumberValue}))
}
}
if q.valueType == templates.TypeDuration {
_, err := time.ParseDuration(value)
if err != nil {
return errors.New(c.localizedString("ValueError_Duration"))
}
}
return nil
}
help := q.help
if q.required {
help += " (" + c.localizedString("Value_Required") + ")"
} else {
help += " (" + c.localizedString("Value_Optional") + ")"
}
if q.exampleValue != "" {
help += fmt.Sprintf(" ("+c.localizedString("Value_Sample")+": %s)", q.exampleValue)
}
var prompt survey.Prompt
if q.mask {
prompt = &survey.Password{
Message: q.label,
Help: help,
}
} else {
prompt = &survey.Input{
Message: q.label,
Default: q.defaultValue,
Help: help,
}
}
var input string
if err := c.surveyAskOne(prompt, &input, survey.WithValidator(validate)); err != nil {
c.log.FATAL.Fatal(err)
}
return input
}