A simple monitoring solution written in Go (work in progress)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

157 lines
2.8 KiB

  1. package config
  2. import (
  3. "bufio"
  4. "fmt"
  5. "io"
  6. "os"
  7. "strings"
  8. "unicode"
  9. )
  10. const (
  11. TK_NONE = iota
  12. TK_VAL
  13. TK_QUOTE
  14. TK_COMMENT
  15. )
  16. type Tokenizer struct {
  17. curline int
  18. repline int
  19. file *os.File
  20. reader *bufio.Reader
  21. text string
  22. err error
  23. }
  24. func NewTokenizer(fn string) (*Tokenizer, error) {
  25. var err error
  26. tk := &Tokenizer{curline: 1}
  27. tk.file, err = os.Open(fn)
  28. if err != nil {
  29. return nil, err
  30. }
  31. tk.reader = bufio.NewReader(tk.file)
  32. return tk, nil
  33. }
  34. func (t *Tokenizer) Close() error {
  35. return t.file.Close()
  36. }
  37. func (t *Tokenizer) Scan() bool {
  38. t.repline = t.curline
  39. state := TK_NONE
  40. t.text = ""
  41. var b strings.Builder
  42. var quo rune
  43. for {
  44. var r rune
  45. r, _, t.err = t.reader.ReadRune()
  46. if t.err != nil {
  47. break
  48. }
  49. if r == unicode.ReplacementChar {
  50. t.err = fmt.Errorf("invalid utf-8 encoding on line %s", t.repline)
  51. break
  52. }
  53. switch state {
  54. case TK_NONE:
  55. // When between values, increment both the reported line
  56. // and the current line, since there's not yet anything
  57. // to report
  58. if r == '\n' {
  59. t.repline++
  60. t.curline++
  61. }
  62. // If we're between values and we encounter a space
  63. // or a control character, ignore it
  64. if unicode.IsSpace(r) || unicode.IsControl(r) {
  65. continue
  66. }
  67. // If we're between values and we encounter a #, it's
  68. // the beginning of a comment
  69. if r == '#' {
  70. state = TK_COMMENT
  71. continue
  72. }
  73. // If we're between values and we get a quote character
  74. // treat it as the beginning of a string literal
  75. if r == '"' || r == '\'' || r == '`' {
  76. state = TK_QUOTE
  77. quo = r
  78. continue
  79. }
  80. b.WriteRune(r)
  81. state = TK_VAL
  82. case TK_VAL:
  83. // In values, only increment the current line, so
  84. // if an error is reported, it reports the line
  85. // the value starts on
  86. if r == '\n' {
  87. t.curline++
  88. }
  89. // If we're in a normal value and we encounter a space
  90. // or a control character, end value
  91. if unicode.IsSpace(r) || unicode.IsControl(r) {
  92. goto end
  93. }
  94. b.WriteRune(r)
  95. case TK_QUOTE:
  96. // In quotes, only increment the current line, so
  97. // if an error is reported, it reports the line
  98. // the quoted value starts on
  99. if r == '\n' {
  100. t.curline++
  101. }
  102. // End this quote if it's another quote of the same rune
  103. if r == quo {
  104. goto end
  105. }
  106. b.WriteRune(r)
  107. case TK_COMMENT:
  108. // Comments are ignored, until a new line is encounter
  109. // at which point, increment the current and reported line
  110. if r == '\n' {
  111. t.curline++
  112. t.repline++
  113. state = TK_NONE
  114. }
  115. continue
  116. }
  117. }
  118. end:
  119. if t.err == nil || t.err == io.EOF {
  120. t.text = b.String()
  121. }
  122. return t.err == nil
  123. }
  124. func (t *Tokenizer) Text() string {
  125. return t.text
  126. }
  127. func (t *Tokenizer) Line() int {
  128. return t.repline
  129. }
  130. func (t *Tokenizer) Err() error {
  131. if t.err == io.EOF {
  132. return nil
  133. }
  134. return t.err
  135. }