fields.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. package widget
  2. import (
  3. "fmt"
  4. "html/template"
  5. "os"
  6. "regexp"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "gopkg.in/yaml.v3"
  11. )
  12. var HSLColorPattern = regexp.MustCompile(`^(?:hsla?\()?(\d{1,3})(?: |,)+(\d{1,3})%?(?: |,)+(\d{1,3})%?\)?$`)
  13. var EnvFieldPattern = regexp.MustCompile(`^\${([A-Z_]+)}$`)
  14. const (
  15. HSLHueMax = 360
  16. HSLSaturationMax = 100
  17. HSLLightnessMax = 100
  18. )
  19. type HSLColorField struct {
  20. Hue uint16
  21. Saturation uint8
  22. Lightness uint8
  23. }
  24. func (c *HSLColorField) String() string {
  25. return fmt.Sprintf("hsl(%d, %d%%, %d%%)", c.Hue, c.Saturation, c.Lightness)
  26. }
  27. func (c *HSLColorField) AsCSSValue() template.CSS {
  28. return template.CSS(c.String())
  29. }
  30. func (c *HSLColorField) UnmarshalYAML(node *yaml.Node) error {
  31. var value string
  32. if err := node.Decode(&value); err != nil {
  33. return err
  34. }
  35. matches := HSLColorPattern.FindStringSubmatch(value)
  36. if len(matches) != 4 {
  37. return fmt.Errorf("invalid HSL color format: %s", value)
  38. }
  39. hue, err := strconv.ParseUint(matches[1], 10, 16)
  40. if err != nil {
  41. return err
  42. }
  43. if hue > HSLHueMax {
  44. return fmt.Errorf("HSL hue must be between 0 and %d", HSLHueMax)
  45. }
  46. saturation, err := strconv.ParseUint(matches[2], 10, 8)
  47. if err != nil {
  48. return err
  49. }
  50. if saturation > HSLSaturationMax {
  51. return fmt.Errorf("HSL saturation must be between 0 and %d", HSLSaturationMax)
  52. }
  53. lightness, err := strconv.ParseUint(matches[3], 10, 8)
  54. if err != nil {
  55. return err
  56. }
  57. if lightness > HSLLightnessMax {
  58. return fmt.Errorf("HSL lightness must be between 0 and %d", HSLLightnessMax)
  59. }
  60. c.Hue = uint16(hue)
  61. c.Saturation = uint8(saturation)
  62. c.Lightness = uint8(lightness)
  63. return nil
  64. }
  65. var DurationPattern = regexp.MustCompile(`^(\d+)(s|m|h|d)$`)
  66. type DurationField time.Duration
  67. func (d *DurationField) UnmarshalYAML(node *yaml.Node) error {
  68. var value string
  69. if err := node.Decode(&value); err != nil {
  70. return err
  71. }
  72. matches := DurationPattern.FindStringSubmatch(value)
  73. if len(matches) != 3 {
  74. return fmt.Errorf("invalid duration format: %s", value)
  75. }
  76. duration, err := strconv.Atoi(matches[1])
  77. if err != nil {
  78. return err
  79. }
  80. switch matches[2] {
  81. case "s":
  82. *d = DurationField(time.Duration(duration) * time.Second)
  83. case "m":
  84. *d = DurationField(time.Duration(duration) * time.Minute)
  85. case "h":
  86. *d = DurationField(time.Duration(duration) * time.Hour)
  87. case "d":
  88. *d = DurationField(time.Duration(duration) * 24 * time.Hour)
  89. }
  90. return nil
  91. }
  92. type OptionalEnvString string
  93. func (f *OptionalEnvString) UnmarshalYAML(node *yaml.Node) error {
  94. var value string
  95. err := node.Decode(&value)
  96. if err != nil {
  97. return err
  98. }
  99. matches := EnvFieldPattern.FindStringSubmatch(value)
  100. if len(matches) != 2 {
  101. *f = OptionalEnvString(value)
  102. return nil
  103. }
  104. value, found := os.LookupEnv(matches[1])
  105. if !found {
  106. return fmt.Errorf("environment variable %s not found", matches[1])
  107. }
  108. *f = OptionalEnvString(value)
  109. return nil
  110. }
  111. func (f *OptionalEnvString) String() string {
  112. return string(*f)
  113. }
  114. func toSimpleIconIfPrefixed(icon string) (string, bool) {
  115. if !strings.HasPrefix(icon, "si:") {
  116. return icon, false
  117. }
  118. icon = strings.TrimPrefix(icon, "si:")
  119. icon = "https://cdnjs.cloudflare.com/ajax/libs/simple-icons/11.14.0/" + icon + ".svg"
  120. return icon, true
  121. }