config.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. package config
  2. import (
  3. "errors"
  4. "fmt"
  5. "github.com/BurntSushi/toml"
  6. "os"
  7. "strings"
  8. "strconv"
  9. "time"
  10. )
  11. type Config struct {
  12. Feeds map[string][]string `toml:"feeds"`
  13. Media struct {
  14. Hook []string `toml:"hook"`
  15. } `toml:"media"`
  16. Style struct {
  17. Colors struct {
  18. Primary string `toml:"primary"`
  19. Error string `toml:"error"`
  20. Highlight string `toml:"highlight"`
  21. Code string `toml:"code_background"`
  22. } `toml:"colors"`
  23. } `toml:"style"`
  24. Network struct {
  25. Context int `toml:"preload_amount"`
  26. Timeout time.Duration `toml:"timeout_seconds"`
  27. CacheSize int `toml:"cache_size"`
  28. } `toml:"network"`
  29. }
  30. var Parsed *Config = nil
  31. /* I use the init function here because everyone who imports config needs
  32. the config to be parsed before starting, and the config should only be parsed once.
  33. It seems like a good use case. It is slightly ugly to have printing/exiting
  34. code this deep in the program, and for it to not be referenced at the top level,
  35. but ultimately it's not a big deal. */
  36. func init() {
  37. var err error
  38. location := location()
  39. if Parsed, err = parse(location); err != nil {
  40. os.Stderr.WriteString(fmt.Errorf("failed to parse %s: %w", location, err).Error() + "\n")
  41. os.Exit(1)
  42. }
  43. if err = postprocess(Parsed); err != nil {
  44. os.Stderr.WriteString(fmt.Errorf("failed to parse %s: %w", location, err).Error() + "\n")
  45. os.Exit(1)
  46. }
  47. }
  48. func parse(location string) (*Config, error) {
  49. /* Default values */
  50. config := &Config{}
  51. config.Feeds = map[string][]string{}
  52. config.Media.Hook = []string{"xdg-open", "%url"}
  53. config.Style.Colors.Primary = "#A4f59b"
  54. config.Style.Colors.Error = "#9c3535"
  55. config.Style.Colors.Highlight = "#0d7d00"
  56. config.Style.Colors.Code = "#4b4b4b"
  57. config.Network.Context = 5
  58. config.Network.Timeout = 10
  59. config.Network.CacheSize = 128
  60. if location == "" {
  61. return config, nil
  62. }
  63. metadata, err := toml.DecodeFile(location, config)
  64. if errors.Is(err, os.ErrNotExist) {
  65. return config, nil
  66. }
  67. if err != nil {
  68. return nil, err
  69. }
  70. if undecoded := metadata.Undecoded(); len(undecoded) != 0 {
  71. return nil, fmt.Errorf("contains unrecognized key(s): %v", undecoded)
  72. }
  73. return config, nil
  74. }
  75. func location() string {
  76. if xdg := os.Getenv("XDG_CONFIG_HOME"); xdg != "" {
  77. return xdg + "/servitor/config.toml"
  78. }
  79. if home := os.Getenv("HOME"); home != "" {
  80. return home + "/.config/servitor/config.toml"
  81. }
  82. return ""
  83. }
  84. func hexToAnsi(text string) (string, error) {
  85. errNotAHexCode := errors.New("must be a hex code of the form '#fcba03'")
  86. if !strings.HasPrefix(text, "#") {
  87. return "", errNotAHexCode
  88. }
  89. if len(text) != 7 {
  90. return "", errNotAHexCode
  91. }
  92. r, err := strconv.ParseUint(text[1:3], 16, 0)
  93. if err != nil {
  94. return "", errNotAHexCode
  95. }
  96. g, err := strconv.ParseUint(text[3:5], 16, 0)
  97. if err != nil {
  98. return "", errNotAHexCode
  99. }
  100. b, err := strconv.ParseUint(text[5:7], 16, 0)
  101. if err != nil {
  102. return "", errNotAHexCode
  103. }
  104. return strconv.Itoa(int(r)) + ";" + strconv.Itoa(int(g)) + ";" + strconv.Itoa(int(b)), nil
  105. }
  106. func postprocess(config *Config) error {
  107. var err error
  108. config.Style.Colors.Primary, err = hexToAnsi(config.Style.Colors.Primary)
  109. if err != nil {
  110. return fmt.Errorf("key style.colors.primary is invalid: %w", err)
  111. }
  112. config.Style.Colors.Error, err = hexToAnsi(config.Style.Colors.Error)
  113. if err != nil {
  114. return fmt.Errorf("key style.colors.error is invalid: %w", err)
  115. }
  116. config.Style.Colors.Highlight, err = hexToAnsi(config.Style.Colors.Highlight)
  117. if err != nil {
  118. return fmt.Errorf("key style.colors.highlight is invalid: %w", err)
  119. }
  120. config.Style.Colors.Code, err = hexToAnsi(config.Style.Colors.Code)
  121. if err != nil {
  122. return fmt.Errorf("key style.colors.code is invalid: %w", err)
  123. }
  124. config.Network.Timeout *= time.Second
  125. return nil
  126. }