package widget
import (
"fmt"
"html/template"
"os"
"regexp"
"strconv"
"time"
"gopkg.in/yaml.v3"
)
var HSLColorPattern = regexp.MustCompile(`^(?:hsla?\()?(\d{1,3})(?: |,)+(\d{1,3})%?(?: |,)+(\d{1,3})%?\)?$`)
var EnvFieldPattern = regexp.MustCompile(`^\${([A-Z_]+)}$`)
const (
HSLHueMax = 360
HSLSaturationMax = 100
HSLLightnessMax = 100
)
type HSLColorField struct {
Hue uint16
Saturation uint8
Lightness uint8
}
func (c *HSLColorField) String() string {
return fmt.Sprintf("hsl(%d, %d%%, %d%%)", c.Hue, c.Saturation, c.Lightness)
}
func (c *HSLColorField) AsCSSValue() template.CSS {
return template.CSS(c.String())
}
func (c *HSLColorField) UnmarshalYAML(node *yaml.Node) error {
var value string
if err := node.Decode(&value); err != nil {
return err
}
matches := HSLColorPattern.FindStringSubmatch(value)
if len(matches) != 4 {
return fmt.Errorf("invalid HSL color format: %s", value)
}
hue, err := strconv.ParseUint(matches[1], 10, 16)
if err != nil {
return err
}
if hue > HSLHueMax {
return fmt.Errorf("HSL hue must be between 0 and %d", HSLHueMax)
}
saturation, err := strconv.ParseUint(matches[2], 10, 8)
if err != nil {
return err
}
if saturation > HSLSaturationMax {
return fmt.Errorf("HSL saturation must be between 0 and %d", HSLSaturationMax)
}
lightness, err := strconv.ParseUint(matches[3], 10, 8)
if err != nil {
return err
}
if lightness > HSLLightnessMax {
return fmt.Errorf("HSL lightness must be between 0 and %d", HSLLightnessMax)
}
c.Hue = uint16(hue)
c.Saturation = uint8(saturation)
c.Lightness = uint8(lightness)
return nil
}
var DurationPattern = regexp.MustCompile(`^(\d+)(s|m|h|d)$`)
type DurationField time.Duration
func (d *DurationField) UnmarshalYAML(node *yaml.Node) error {
var value string
if err := node.Decode(&value); err != nil {
return err
}
matches := DurationPattern.FindStringSubmatch(value)
if len(matches) != 3 {
return fmt.Errorf("invalid duration format: %s", value)
}
duration, err := strconv.Atoi(matches[1])
if err != nil {
return err
}
switch matches[2] {
case "s":
*d = DurationField(time.Duration(duration) * time.Second)
case "m":
*d = DurationField(time.Duration(duration) * time.Minute)
case "h":
*d = DurationField(time.Duration(duration) * time.Hour)
case "d":
*d = DurationField(time.Duration(duration) * 24 * time.Hour)
}
return nil
}
type OptionalEnvString string
func (f *OptionalEnvString) UnmarshalYAML(node *yaml.Node) error {
var value string
err := node.Decode(&value)
if err != nil {
return err
}
matches := EnvFieldPattern.FindStringSubmatch(value)
if len(matches) != 2 {
*f = OptionalEnvString(value)
return nil
}
value, found := os.LookupEnv(matches[1])
if !found {
return fmt.Errorf("environment variable %s not found", matches[1])
}
*f = OptionalEnvString(value)
return nil
}