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.

273 lines
6.8 KiB

  1. package gcfg
  2. import (
  3. "fmt"
  4. "io"
  5. "io/ioutil"
  6. "os"
  7. "strings"
  8. "github.com/src-d/gcfg/scanner"
  9. "github.com/src-d/gcfg/token"
  10. "gopkg.in/warnings.v0"
  11. )
  12. var unescape = map[rune]rune{'\\': '\\', '"': '"', 'n': '\n', 't': '\t', 'b': '\b'}
  13. // no error: invalid literals should be caught by scanner
  14. func unquote(s string) string {
  15. u, q, esc := make([]rune, 0, len(s)), false, false
  16. for _, c := range s {
  17. if esc {
  18. uc, ok := unescape[c]
  19. switch {
  20. case ok:
  21. u = append(u, uc)
  22. fallthrough
  23. case !q && c == '\n':
  24. esc = false
  25. continue
  26. }
  27. panic("invalid escape sequence")
  28. }
  29. switch c {
  30. case '"':
  31. q = !q
  32. case '\\':
  33. esc = true
  34. default:
  35. u = append(u, c)
  36. }
  37. }
  38. if q {
  39. panic("missing end quote")
  40. }
  41. if esc {
  42. panic("invalid escape sequence")
  43. }
  44. return string(u)
  45. }
  46. func read(c *warnings.Collector, callback func(string, string, string, string, bool) error,
  47. fset *token.FileSet, file *token.File, src []byte) error {
  48. //
  49. var s scanner.Scanner
  50. var errs scanner.ErrorList
  51. s.Init(file, src, func(p token.Position, m string) { errs.Add(p, m) }, 0)
  52. sect, sectsub := "", ""
  53. pos, tok, lit := s.Scan()
  54. errfn := func(msg string) error {
  55. return fmt.Errorf("%s: %s", fset.Position(pos), msg)
  56. }
  57. for {
  58. if errs.Len() > 0 {
  59. if err := c.Collect(errs.Err()); err != nil {
  60. return err
  61. }
  62. }
  63. switch tok {
  64. case token.EOF:
  65. return nil
  66. case token.EOL, token.COMMENT:
  67. pos, tok, lit = s.Scan()
  68. case token.LBRACK:
  69. pos, tok, lit = s.Scan()
  70. if errs.Len() > 0 {
  71. if err := c.Collect(errs.Err()); err != nil {
  72. return err
  73. }
  74. }
  75. if tok != token.IDENT {
  76. if err := c.Collect(errfn("expected section name")); err != nil {
  77. return err
  78. }
  79. }
  80. sect, sectsub = lit, ""
  81. pos, tok, lit = s.Scan()
  82. if errs.Len() > 0 {
  83. if err := c.Collect(errs.Err()); err != nil {
  84. return err
  85. }
  86. }
  87. if tok == token.STRING {
  88. sectsub = unquote(lit)
  89. if sectsub == "" {
  90. if err := c.Collect(errfn("empty subsection name")); err != nil {
  91. return err
  92. }
  93. }
  94. pos, tok, lit = s.Scan()
  95. if errs.Len() > 0 {
  96. if err := c.Collect(errs.Err()); err != nil {
  97. return err
  98. }
  99. }
  100. }
  101. if tok != token.RBRACK {
  102. if sectsub == "" {
  103. if err := c.Collect(errfn("expected subsection name or right bracket")); err != nil {
  104. return err
  105. }
  106. }
  107. if err := c.Collect(errfn("expected right bracket")); err != nil {
  108. return err
  109. }
  110. }
  111. pos, tok, lit = s.Scan()
  112. if tok != token.EOL && tok != token.EOF && tok != token.COMMENT {
  113. if err := c.Collect(errfn("expected EOL, EOF, or comment")); err != nil {
  114. return err
  115. }
  116. }
  117. // If a section/subsection header was found, ensure a
  118. // container object is created, even if there are no
  119. // variables further down.
  120. err := c.Collect(callback(sect, sectsub, "", "", true))
  121. if err != nil {
  122. return err
  123. }
  124. case token.IDENT:
  125. if sect == "" {
  126. if err := c.Collect(errfn("expected section header")); err != nil {
  127. return err
  128. }
  129. }
  130. n := lit
  131. pos, tok, lit = s.Scan()
  132. if errs.Len() > 0 {
  133. return errs.Err()
  134. }
  135. blank, v := tok == token.EOF || tok == token.EOL || tok == token.COMMENT, ""
  136. if !blank {
  137. if tok != token.ASSIGN {
  138. if err := c.Collect(errfn("expected '='")); err != nil {
  139. return err
  140. }
  141. }
  142. pos, tok, lit = s.Scan()
  143. if errs.Len() > 0 {
  144. if err := c.Collect(errs.Err()); err != nil {
  145. return err
  146. }
  147. }
  148. if tok != token.STRING {
  149. if err := c.Collect(errfn("expected value")); err != nil {
  150. return err
  151. }
  152. }
  153. v = unquote(lit)
  154. pos, tok, lit = s.Scan()
  155. if errs.Len() > 0 {
  156. if err := c.Collect(errs.Err()); err != nil {
  157. return err
  158. }
  159. }
  160. if tok != token.EOL && tok != token.EOF && tok != token.COMMENT {
  161. if err := c.Collect(errfn("expected EOL, EOF, or comment")); err != nil {
  162. return err
  163. }
  164. }
  165. }
  166. err := c.Collect(callback(sect, sectsub, n, v, blank))
  167. if err != nil {
  168. return err
  169. }
  170. default:
  171. if sect == "" {
  172. if err := c.Collect(errfn("expected section header")); err != nil {
  173. return err
  174. }
  175. }
  176. if err := c.Collect(errfn("expected section header or variable declaration")); err != nil {
  177. return err
  178. }
  179. }
  180. }
  181. panic("never reached")
  182. }
  183. func readInto(config interface{}, fset *token.FileSet, file *token.File,
  184. src []byte) error {
  185. //
  186. c := warnings.NewCollector(isFatal)
  187. firstPassCallback := func(s string, ss string, k string, v string, bv bool) error {
  188. return set(c, config, s, ss, k, v, bv, false)
  189. }
  190. err := read(c, firstPassCallback, fset, file, src)
  191. if err != nil {
  192. return err
  193. }
  194. secondPassCallback := func(s string, ss string, k string, v string, bv bool) error {
  195. return set(c, config, s, ss, k, v, bv, true)
  196. }
  197. err = read(c, secondPassCallback, fset, file, src)
  198. if err != nil {
  199. return err
  200. }
  201. return c.Done()
  202. }
  203. // ReadWithCallback reads gcfg formatted data from reader and calls
  204. // callback with each section and option found.
  205. //
  206. // Callback is called with section, subsection, option key, option value
  207. // and blank value flag as arguments.
  208. //
  209. // When a section is found, callback is called with nil subsection, option key
  210. // and option value.
  211. //
  212. // When a subsection is found, callback is called with nil option key and
  213. // option value.
  214. //
  215. // If blank value flag is true, it means that the value was not set for an option
  216. // (as opposed to set to empty string).
  217. //
  218. // If callback returns an error, ReadWithCallback terminates with an error too.
  219. func ReadWithCallback(reader io.Reader, callback func(string, string, string, string, bool) error) error {
  220. src, err := ioutil.ReadAll(reader)
  221. if err != nil {
  222. return err
  223. }
  224. fset := token.NewFileSet()
  225. file := fset.AddFile("", fset.Base(), len(src))
  226. c := warnings.NewCollector(isFatal)
  227. return read(c, callback, fset, file, src)
  228. }
  229. // ReadInto reads gcfg formatted data from reader and sets the values into the
  230. // corresponding fields in config.
  231. func ReadInto(config interface{}, reader io.Reader) error {
  232. src, err := ioutil.ReadAll(reader)
  233. if err != nil {
  234. return err
  235. }
  236. fset := token.NewFileSet()
  237. file := fset.AddFile("", fset.Base(), len(src))
  238. return readInto(config, fset, file, src)
  239. }
  240. // ReadStringInto reads gcfg formatted data from str and sets the values into
  241. // the corresponding fields in config.
  242. func ReadStringInto(config interface{}, str string) error {
  243. r := strings.NewReader(str)
  244. return ReadInto(config, r)
  245. }
  246. // ReadFileInto reads gcfg formatted data from the file filename and sets the
  247. // values into the corresponding fields in config.
  248. func ReadFileInto(config interface{}, filename string) error {
  249. f, err := os.Open(filename)
  250. if err != nil {
  251. return err
  252. }
  253. defer f.Close()
  254. src, err := ioutil.ReadAll(f)
  255. if err != nil {
  256. return err
  257. }
  258. fset := token.NewFileSet()
  259. file := fset.AddFile(filename, fset.Base(), len(src))
  260. return readInto(config, fset, file, src)
  261. }