256 lines
6.9 KiB
Go
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
|
|
}
|