1
0
Fork 0

Adding upstream version 0.8.9.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-22 10:16:14 +02:00
parent 3b2c48b5e4
commit c0c4addb85
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
285 changed files with 25880 additions and 0 deletions

View file

@ -0,0 +1,219 @@
package basic
import (
"bufio"
"errors"
"fmt"
"os"
"reflect"
"strconv"
"strings"
"github.com/fatih/color"
"github.com/nicholas-fedor/shoutrrr/pkg/format"
"github.com/nicholas-fedor/shoutrrr/pkg/types"
)
// Errors defined as static variables for better error handling.
var (
ErrInvalidConfigType = errors.New("config does not implement types.ServiceConfig")
ErrInvalidConfigField = errors.New("config field is invalid or nil")
ErrRequiredFieldMissing = errors.New("field is required and has no default value")
)
// Generator is the Basic Generator implementation for creating service configurations.
type Generator struct{}
// Generate creates a service configuration by prompting the user for field values or using provided properties.
func (g *Generator) Generate(
service types.Service,
props map[string]string,
_ []string,
) (types.ServiceConfig, error) {
configPtr := reflect.ValueOf(service).Elem().FieldByName("Config")
if !configPtr.IsValid() || configPtr.IsNil() {
return nil, ErrInvalidConfigField
}
scanner := bufio.NewScanner(os.Stdin)
if err := g.promptUserForFields(configPtr, props, scanner); err != nil {
return nil, err
}
if config, ok := configPtr.Interface().(types.ServiceConfig); ok {
return config, nil
}
return nil, ErrInvalidConfigType
}
// promptUserForFields iterates over config fields, prompting the user or using props to set values.
func (g *Generator) promptUserForFields(
configPtr reflect.Value,
props map[string]string,
scanner *bufio.Scanner,
) error {
serviceConfig, ok := configPtr.Interface().(types.ServiceConfig)
if !ok {
return ErrInvalidConfigType
}
configNode := format.GetConfigFormat(serviceConfig)
config := configPtr.Elem() // Dereference for setting fields
for _, item := range configNode.Items {
field := item.Field()
propKey := strings.ToLower(field.Name)
for {
inputValue, err := g.getInputValue(field, propKey, props, scanner)
if err != nil {
return err // Propagate the error immediately
}
if valid, err := g.setFieldValue(config, field, inputValue); valid {
break
} else if err != nil {
g.printError(field.Name, err.Error())
} else {
g.printInvalidType(field.Name, field.Type.Kind().String())
}
}
}
return nil
}
// getInputValue retrieves the value for a field from props or user input.
func (g *Generator) getInputValue(
field *format.FieldInfo,
propKey string,
props map[string]string,
scanner *bufio.Scanner,
) (string, error) {
if propValue, ok := props[propKey]; ok && len(propValue) > 0 {
_, _ = fmt.Fprint(
color.Output,
"Using property ",
color.HiCyanString(propValue),
" for ",
color.HiMagentaString(field.Name),
" field\n",
)
props[propKey] = ""
return propValue, nil
}
prompt := g.formatPrompt(field)
_, _ = fmt.Fprint(color.Output, prompt)
if scanner.Scan() {
input := scanner.Text()
if len(input) == 0 {
if len(field.DefaultValue) > 0 {
return field.DefaultValue, nil
}
if field.Required {
return "", fmt.Errorf("%s: %w", field.Name, ErrRequiredFieldMissing)
}
return "", nil
}
// More specific type validation
if field.Type != nil {
kind := field.Type.Kind()
if kind == reflect.Int || kind == reflect.Int8 || kind == reflect.Int16 ||
kind == reflect.Int32 || kind == reflect.Int64 {
if _, err := strconv.ParseInt(input, 10, field.Type.Bits()); err != nil {
return "", fmt.Errorf("invalid integer value for %s: %w", field.Name, err)
}
}
}
return input, nil
} else if scanErr := scanner.Err(); scanErr != nil {
return "", fmt.Errorf("scanner error: %w", scanErr)
}
return field.DefaultValue, nil
}
// formatPrompt creates a user prompt based on the fields name and default value.
func (g *Generator) formatPrompt(field *format.FieldInfo) string {
if len(field.DefaultValue) > 0 {
return fmt.Sprintf("%s[%s]: ", color.HiWhiteString(field.Name), field.DefaultValue)
}
return color.HiWhiteString(field.Name) + ": "
}
// setFieldValue attempts to set a fields value and handles required field validation.
func (g *Generator) setFieldValue(
config reflect.Value,
field *format.FieldInfo,
inputValue string,
) (bool, error) {
if len(inputValue) == 0 {
if field.Required {
_, _ = fmt.Fprint(
color.Output,
"Field ",
color.HiCyanString(field.Name),
" is required!\n\n",
)
return false, nil
}
if len(field.DefaultValue) == 0 {
return true, nil
}
inputValue = field.DefaultValue
}
valid, err := format.SetConfigField(config, *field, inputValue)
if err != nil {
return false, fmt.Errorf("failed to set field %s: %w", field.Name, err)
}
return valid, nil
}
// printError displays an error message for an invalid field value.
func (g *Generator) printError(fieldName, errorMsg string) {
_, _ = fmt.Fprint(
color.Output,
"Invalid format for field ",
color.HiCyanString(fieldName),
": ",
errorMsg,
"\n\n",
)
}
// printInvalidType displays a type mismatch error for a field.
func (g *Generator) printInvalidType(fieldName, typeName string) {
_, _ = fmt.Fprint(
color.Output,
"Invalid type ",
color.HiYellowString(typeName),
" for field ",
color.HiCyanString(fieldName),
"\n\n",
)
}
// validateAndReturnConfig ensures the config implements ServiceConfig and returns it.
func (g *Generator) validateAndReturnConfig(config reflect.Value) (types.ServiceConfig, error) {
configInterface := config.Interface()
if serviceConfig, ok := configInterface.(types.ServiceConfig); ok {
return serviceConfig, nil
}
return nil, ErrInvalidConfigType
}