api.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
  2. package config
  3. import (
  4. "bufio"
  5. "bytes"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "io/fs"
  10. "os"
  11. "path/filepath"
  12. "regexp"
  13. "strings"
  14. "sync"
  15. "kitty/tools/utils"
  16. )
  17. var _ = fmt.Print
  18. func StringToBool(x string) bool {
  19. x = strings.ToLower(x)
  20. return x == "y" || x == "yes" || x == "true"
  21. }
  22. type ConfigLine struct {
  23. Src_file, Line string
  24. Line_number int
  25. Err error
  26. }
  27. type ConfigParser struct {
  28. LineHandler func(key, val string) error
  29. CommentsHandler func(line string) error
  30. SourceHandler func(text, path string)
  31. bad_lines []ConfigLine
  32. seen_includes map[string]bool
  33. override_env []string
  34. }
  35. type Scanner interface {
  36. Scan() bool
  37. Text() string
  38. Err() error
  39. }
  40. func (self *ConfigParser) BadLines() []ConfigLine {
  41. return self.bad_lines
  42. }
  43. var key_pat = sync.OnceValue(func() *regexp.Regexp {
  44. return regexp.MustCompile(`([a-zA-Z][a-zA-Z0-9_-]*)\s+(.+)$`)
  45. })
  46. func (self *ConfigParser) parse(scanner Scanner, name, base_path_for_includes string, depth int) error {
  47. if self.seen_includes[name] { // avoid include loops
  48. return nil
  49. }
  50. self.seen_includes[name] = true
  51. recurse := func(r io.Reader, nname, base_path_for_includes string) error {
  52. if depth > 32 {
  53. return fmt.Errorf("Too many nested include directives while processing config file: %s", name)
  54. }
  55. escanner := bufio.NewScanner(r)
  56. return self.parse(escanner, nname, base_path_for_includes, depth+1)
  57. }
  58. make_absolute := func(path string) (string, error) {
  59. if path == "" {
  60. return "", fmt.Errorf("Empty include paths not allowed")
  61. }
  62. if !filepath.IsAbs(path) {
  63. path = filepath.Join(base_path_for_includes, path)
  64. }
  65. return path, nil
  66. }
  67. lnum := 0
  68. next_line_num := 0
  69. next_line := ""
  70. var line string
  71. for {
  72. if next_line != "" {
  73. line = next_line
  74. } else {
  75. if scanner.Scan() {
  76. line = strings.TrimLeft(scanner.Text(), " \t")
  77. next_line_num++
  78. } else {
  79. break
  80. }
  81. if line == "" {
  82. continue
  83. }
  84. }
  85. lnum = next_line_num
  86. if scanner.Scan() {
  87. next_line = strings.TrimLeft(scanner.Text(), " \t")
  88. next_line_num++
  89. for strings.HasPrefix(next_line, `\`) {
  90. line += next_line[1:]
  91. if scanner.Scan() {
  92. next_line = strings.TrimLeft(scanner.Text(), " \t")
  93. next_line_num++
  94. } else {
  95. next_line = ""
  96. }
  97. }
  98. } else {
  99. next_line = ""
  100. }
  101. if line[0] == '#' {
  102. if self.CommentsHandler != nil {
  103. err := self.CommentsHandler(line)
  104. if err != nil {
  105. self.bad_lines = append(self.bad_lines, ConfigLine{Src_file: name, Line: line, Line_number: lnum, Err: err})
  106. }
  107. }
  108. continue
  109. }
  110. m := key_pat().FindStringSubmatch(line)
  111. if len(m) < 3 {
  112. self.bad_lines = append(self.bad_lines, ConfigLine{Src_file: name, Line: line, Line_number: lnum, Err: fmt.Errorf("Invalid config line: %#v", line)})
  113. continue
  114. }
  115. key, val := m[1], m[2]
  116. for i, ch := range line {
  117. if ch == ' ' || ch == '\t' {
  118. key = line[:i]
  119. val = strings.TrimSpace(line[i+1:])
  120. break
  121. }
  122. }
  123. switch key {
  124. default:
  125. err := self.LineHandler(key, val)
  126. if err != nil {
  127. self.bad_lines = append(self.bad_lines, ConfigLine{Src_file: name, Line: line, Line_number: lnum, Err: err})
  128. }
  129. case "include", "globinclude", "envinclude":
  130. var includes []string
  131. switch key {
  132. case "include":
  133. aval, err := make_absolute(val)
  134. if err == nil {
  135. includes = []string{aval}
  136. }
  137. case "globinclude":
  138. aval, err := make_absolute(val)
  139. if err == nil {
  140. matches, err := filepath.Glob(aval)
  141. if err == nil {
  142. includes = matches
  143. }
  144. }
  145. case "envinclude":
  146. env := self.override_env
  147. if env == nil {
  148. env = os.Environ()
  149. }
  150. for _, x := range env {
  151. key, eval, _ := strings.Cut(x, "=")
  152. is_match, err := filepath.Match(val, key)
  153. if is_match && err == nil {
  154. err := recurse(strings.NewReader(eval), "<env var: "+key+">", base_path_for_includes)
  155. if err != nil {
  156. return err
  157. }
  158. }
  159. }
  160. }
  161. if len(includes) > 0 {
  162. for _, incpath := range includes {
  163. raw, err := os.ReadFile(incpath)
  164. if err == nil {
  165. err := recurse(bytes.NewReader(raw), incpath, filepath.Dir(incpath))
  166. if err != nil {
  167. return err
  168. }
  169. } else if !errors.Is(err, fs.ErrNotExist) {
  170. return fmt.Errorf("Failed to process include %#v with error: %w", incpath, err)
  171. }
  172. }
  173. }
  174. }
  175. }
  176. return nil
  177. }
  178. func (self *ConfigParser) ParseFiles(paths ...string) error {
  179. for _, path := range paths {
  180. apath, err := filepath.Abs(path)
  181. if err == nil {
  182. path = apath
  183. }
  184. raw, err := os.ReadFile(path)
  185. if err != nil {
  186. return err
  187. }
  188. scanner := utils.NewLineScanner(utils.UnsafeBytesToString(raw))
  189. self.seen_includes = make(map[string]bool)
  190. err = self.parse(scanner, path, filepath.Dir(path), 0)
  191. if err != nil {
  192. return err
  193. }
  194. if self.SourceHandler != nil {
  195. self.SourceHandler(utils.UnsafeBytesToString(raw), path)
  196. }
  197. }
  198. return nil
  199. }
  200. func (self *ConfigParser) LoadConfig(name string, paths []string, overrides []string) (err error) {
  201. const SYSTEM_CONF = "/etc/xdg/kitty"
  202. system_conf := filepath.Join(SYSTEM_CONF, name)
  203. add_if_exists := func(q string) {
  204. err = self.ParseFiles(q)
  205. if err != nil && errors.Is(err, fs.ErrNotExist) {
  206. err = nil
  207. }
  208. }
  209. if add_if_exists(system_conf); err != nil {
  210. return err
  211. }
  212. if len(paths) > 0 {
  213. for _, path := range paths {
  214. if add_if_exists(path); err != nil {
  215. return err
  216. }
  217. }
  218. } else {
  219. if add_if_exists(filepath.Join(utils.ConfigDirForName(name), name)); err != nil {
  220. return err
  221. }
  222. }
  223. if len(overrides) > 0 {
  224. err = self.ParseOverrides(overrides...)
  225. if err != nil {
  226. return err
  227. }
  228. }
  229. return
  230. }
  231. type LinesScanner struct {
  232. lines []string
  233. }
  234. func (self *LinesScanner) Scan() bool {
  235. return len(self.lines) > 0
  236. }
  237. func (self *LinesScanner) Text() string {
  238. ans := self.lines[0]
  239. self.lines = self.lines[1:]
  240. return ans
  241. }
  242. func (self *LinesScanner) Err() error {
  243. return nil
  244. }
  245. func (self *ConfigParser) ParseOverrides(overrides ...string) error {
  246. s := LinesScanner{lines: utils.Map(func(x string) string {
  247. return strings.Replace(x, "=", " ", 1)
  248. }, overrides)}
  249. self.seen_includes = make(map[string]bool)
  250. return self.parse(&s, "<overrides>", utils.ConfigDir(), 0)
  251. }