1
0

ini.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474
  1. // Copyright 2014 beego Author. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package config
  15. import (
  16. "bufio"
  17. "bytes"
  18. "errors"
  19. "io"
  20. "io/ioutil"
  21. "os"
  22. "path/filepath"
  23. "strconv"
  24. "strings"
  25. "sync"
  26. )
  27. var (
  28. defaultSection = "default" // default section means if some ini items not in a section, make them in default section,
  29. bNumComment = []byte{'#'} // number signal
  30. bSemComment = []byte{';'} // semicolon signal
  31. bEmpty = []byte{}
  32. bEqual = []byte{'='} // equal signal
  33. bDQuote = []byte{'"'} // quote signal
  34. sectionStart = []byte{'['} // section start signal
  35. sectionEnd = []byte{']'} // section end signal
  36. lineBreak = "\n"
  37. )
  38. // IniConfig implements Config to parse ini file.
  39. type IniConfig struct {
  40. }
  41. // Parse creates a new Config and parses the file configuration from the named file.
  42. func (ini *IniConfig) Parse(name string) (Configer, error) {
  43. return ini.parseFile(name)
  44. }
  45. func (ini *IniConfig) parseFile(name string) (*IniConfigContainer, error) {
  46. data, err := ioutil.ReadFile(name)
  47. if err != nil {
  48. return nil, err
  49. }
  50. return ini.parseData(filepath.Dir(name), data)
  51. }
  52. func (ini *IniConfig) parseData(dir string, data []byte) (*IniConfigContainer, error) {
  53. cfg := &IniConfigContainer{
  54. data: make(map[string]map[string]string),
  55. sectionComment: make(map[string]string),
  56. keyComment: make(map[string]string),
  57. RWMutex: sync.RWMutex{},
  58. }
  59. cfg.Lock()
  60. defer cfg.Unlock()
  61. var comment bytes.Buffer
  62. buf := bufio.NewReader(bytes.NewBuffer(data))
  63. // check the BOM
  64. head, err := buf.Peek(3)
  65. if err == nil && head[0] == 239 && head[1] == 187 && head[2] == 191 {
  66. for i := 1; i <= 3; i++ {
  67. buf.ReadByte()
  68. }
  69. }
  70. section := defaultSection
  71. for {
  72. line, _, err := buf.ReadLine()
  73. if err == io.EOF {
  74. break
  75. }
  76. //It might be a good idea to throw a error on all unknonw errors?
  77. if _, ok := err.(*os.PathError); ok {
  78. return nil, err
  79. }
  80. line = bytes.TrimSpace(line)
  81. if bytes.Equal(line, bEmpty) {
  82. continue
  83. }
  84. var bComment []byte
  85. switch {
  86. case bytes.HasPrefix(line, bNumComment):
  87. bComment = bNumComment
  88. case bytes.HasPrefix(line, bSemComment):
  89. bComment = bSemComment
  90. }
  91. if bComment != nil {
  92. line = bytes.TrimLeft(line, string(bComment))
  93. // Need append to a new line if multi-line comments.
  94. if comment.Len() > 0 {
  95. comment.WriteByte('\n')
  96. }
  97. comment.Write(line)
  98. continue
  99. }
  100. if bytes.HasPrefix(line, sectionStart) && bytes.HasSuffix(line, sectionEnd) {
  101. section = strings.ToLower(string(line[1 : len(line)-1])) // section name case insensitive
  102. if comment.Len() > 0 {
  103. cfg.sectionComment[section] = comment.String()
  104. comment.Reset()
  105. }
  106. if _, ok := cfg.data[section]; !ok {
  107. cfg.data[section] = make(map[string]string)
  108. }
  109. continue
  110. }
  111. if _, ok := cfg.data[section]; !ok {
  112. cfg.data[section] = make(map[string]string)
  113. }
  114. keyValue := bytes.SplitN(line, bEqual, 2)
  115. key := string(bytes.TrimSpace(keyValue[0])) // key name case insensitive
  116. key = strings.ToLower(key)
  117. // handle include "other.conf"
  118. if len(keyValue) == 1 && strings.HasPrefix(key, "include") {
  119. includefiles := strings.Fields(key)
  120. if includefiles[0] == "include" && len(includefiles) == 2 {
  121. otherfile := strings.Trim(includefiles[1], "\"")
  122. if !filepath.IsAbs(otherfile) {
  123. otherfile = filepath.Join(dir, otherfile)
  124. }
  125. i, err := ini.parseFile(otherfile)
  126. if err != nil {
  127. return nil, err
  128. }
  129. for sec, dt := range i.data {
  130. if _, ok := cfg.data[sec]; !ok {
  131. cfg.data[sec] = make(map[string]string)
  132. }
  133. for k, v := range dt {
  134. cfg.data[sec][k] = v
  135. }
  136. }
  137. for sec, comm := range i.sectionComment {
  138. cfg.sectionComment[sec] = comm
  139. }
  140. for k, comm := range i.keyComment {
  141. cfg.keyComment[k] = comm
  142. }
  143. continue
  144. }
  145. }
  146. if len(keyValue) != 2 {
  147. return nil, errors.New("read the content error: \"" + string(line) + "\", should key = val")
  148. }
  149. val := bytes.TrimSpace(keyValue[1])
  150. if bytes.HasPrefix(val, bDQuote) {
  151. val = bytes.Trim(val, `"`)
  152. }
  153. cfg.data[section][key] = ExpandValueEnv(string(val))
  154. if comment.Len() > 0 {
  155. cfg.keyComment[section+"."+key] = comment.String()
  156. comment.Reset()
  157. }
  158. }
  159. return cfg, nil
  160. }
  161. // ParseData parse ini the data
  162. // When include other.conf,other.conf is either absolute directory
  163. // or under beego in default temporary directory(/tmp/beego).
  164. func (ini *IniConfig) ParseData(data []byte) (Configer, error) {
  165. dir := filepath.Join(os.TempDir(), "beego")
  166. os.MkdirAll(dir, os.ModePerm)
  167. return ini.parseData(dir, data)
  168. }
  169. // IniConfigContainer A Config represents the ini configuration.
  170. // When set and get value, support key as section:name type.
  171. type IniConfigContainer struct {
  172. data map[string]map[string]string // section=> key:val
  173. sectionComment map[string]string // section : comment
  174. keyComment map[string]string // id: []{comment, key...}; id 1 is for main comment.
  175. sync.RWMutex
  176. }
  177. // Bool returns the boolean value for a given key.
  178. func (c *IniConfigContainer) Bool(key string) (bool, error) {
  179. return ParseBool(c.getdata(key))
  180. }
  181. // DefaultBool returns the boolean value for a given key.
  182. // if err != nil return defaltval
  183. func (c *IniConfigContainer) DefaultBool(key string, defaultval bool) bool {
  184. v, err := c.Bool(key)
  185. if err != nil {
  186. return defaultval
  187. }
  188. return v
  189. }
  190. // Int returns the integer value for a given key.
  191. func (c *IniConfigContainer) Int(key string) (int, error) {
  192. return strconv.Atoi(c.getdata(key))
  193. }
  194. // DefaultInt returns the integer value for a given key.
  195. // if err != nil return defaltval
  196. func (c *IniConfigContainer) DefaultInt(key string, defaultval int) int {
  197. v, err := c.Int(key)
  198. if err != nil {
  199. return defaultval
  200. }
  201. return v
  202. }
  203. // Int64 returns the int64 value for a given key.
  204. func (c *IniConfigContainer) Int64(key string) (int64, error) {
  205. return strconv.ParseInt(c.getdata(key), 10, 64)
  206. }
  207. // DefaultInt64 returns the int64 value for a given key.
  208. // if err != nil return defaltval
  209. func (c *IniConfigContainer) DefaultInt64(key string, defaultval int64) int64 {
  210. v, err := c.Int64(key)
  211. if err != nil {
  212. return defaultval
  213. }
  214. return v
  215. }
  216. // Float returns the float value for a given key.
  217. func (c *IniConfigContainer) Float(key string) (float64, error) {
  218. return strconv.ParseFloat(c.getdata(key), 64)
  219. }
  220. // DefaultFloat returns the float64 value for a given key.
  221. // if err != nil return defaltval
  222. func (c *IniConfigContainer) DefaultFloat(key string, defaultval float64) float64 {
  223. v, err := c.Float(key)
  224. if err != nil {
  225. return defaultval
  226. }
  227. return v
  228. }
  229. // String returns the string value for a given key.
  230. func (c *IniConfigContainer) String(key string) string {
  231. return c.getdata(key)
  232. }
  233. // DefaultString returns the string value for a given key.
  234. // if err != nil return defaltval
  235. func (c *IniConfigContainer) DefaultString(key string, defaultval string) string {
  236. v := c.String(key)
  237. if v == "" {
  238. return defaultval
  239. }
  240. return v
  241. }
  242. // Strings returns the []string value for a given key.
  243. // Return nil if config value does not exist or is empty.
  244. func (c *IniConfigContainer) Strings(key string) []string {
  245. v := c.String(key)
  246. if v == "" {
  247. return nil
  248. }
  249. return strings.Split(v, ";")
  250. }
  251. // DefaultStrings returns the []string value for a given key.
  252. // if err != nil return defaltval
  253. func (c *IniConfigContainer) DefaultStrings(key string, defaultval []string) []string {
  254. v := c.Strings(key)
  255. if v == nil {
  256. return defaultval
  257. }
  258. return v
  259. }
  260. // GetSection returns map for the given section
  261. func (c *IniConfigContainer) GetSection(section string) (map[string]string, error) {
  262. if v, ok := c.data[section]; ok {
  263. return v, nil
  264. }
  265. return nil, errors.New("not exist section")
  266. }
  267. // SaveConfigFile save the config into file.
  268. //
  269. // BUG(env): The environment variable config item will be saved with real value in SaveConfigFile Funcation.
  270. func (c *IniConfigContainer) SaveConfigFile(filename string) (err error) {
  271. // Write configuration file by filename.
  272. f, err := os.Create(filename)
  273. if err != nil {
  274. return err
  275. }
  276. defer f.Close()
  277. // Get section or key comments. Fixed #1607
  278. getCommentStr := func(section, key string) string {
  279. comment, ok := "", false
  280. if len(key) == 0 {
  281. comment, ok = c.sectionComment[section]
  282. } else {
  283. comment, ok = c.keyComment[section+"."+key]
  284. }
  285. if ok {
  286. // Empty comment
  287. if len(comment) == 0 || len(strings.TrimSpace(comment)) == 0 {
  288. return string(bNumComment)
  289. }
  290. prefix := string(bNumComment)
  291. // Add the line head character "#"
  292. return prefix + strings.Replace(comment, lineBreak, lineBreak+prefix, -1)
  293. }
  294. return ""
  295. }
  296. buf := bytes.NewBuffer(nil)
  297. // Save default section at first place
  298. if dt, ok := c.data[defaultSection]; ok {
  299. for key, val := range dt {
  300. if key != " " {
  301. // Write key comments.
  302. if v := getCommentStr(defaultSection, key); len(v) > 0 {
  303. if _, err = buf.WriteString(v + lineBreak); err != nil {
  304. return err
  305. }
  306. }
  307. // Write key and value.
  308. if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
  309. return err
  310. }
  311. }
  312. }
  313. // Put a line between sections.
  314. if _, err = buf.WriteString(lineBreak); err != nil {
  315. return err
  316. }
  317. }
  318. // Save named sections
  319. for section, dt := range c.data {
  320. if section != defaultSection {
  321. // Write section comments.
  322. if v := getCommentStr(section, ""); len(v) > 0 {
  323. if _, err = buf.WriteString(v + lineBreak); err != nil {
  324. return err
  325. }
  326. }
  327. // Write section name.
  328. if _, err = buf.WriteString(string(sectionStart) + section + string(sectionEnd) + lineBreak); err != nil {
  329. return err
  330. }
  331. for key, val := range dt {
  332. if key != " " {
  333. // Write key comments.
  334. if v := getCommentStr(section, key); len(v) > 0 {
  335. if _, err = buf.WriteString(v + lineBreak); err != nil {
  336. return err
  337. }
  338. }
  339. // Write key and value.
  340. if _, err = buf.WriteString(key + string(bEqual) + val + lineBreak); err != nil {
  341. return err
  342. }
  343. }
  344. }
  345. // Put a line between sections.
  346. if _, err = buf.WriteString(lineBreak); err != nil {
  347. return err
  348. }
  349. }
  350. }
  351. if _, err = buf.WriteTo(f); err != nil {
  352. return err
  353. }
  354. return nil
  355. }
  356. // Set writes a new value for key.
  357. // if write to one section, the key need be "section::key".
  358. // if the section is not existed, it panics.
  359. func (c *IniConfigContainer) Set(key, value string) error {
  360. c.Lock()
  361. defer c.Unlock()
  362. if len(key) == 0 {
  363. return errors.New("key is empty")
  364. }
  365. var (
  366. section, k string
  367. sectionKey = strings.Split(key, "::")
  368. )
  369. if len(sectionKey) >= 2 {
  370. section = sectionKey[0]
  371. k = sectionKey[1]
  372. } else {
  373. section = defaultSection
  374. k = sectionKey[0]
  375. }
  376. if _, ok := c.data[section]; !ok {
  377. c.data[section] = make(map[string]string)
  378. }
  379. c.data[section][k] = value
  380. return nil
  381. }
  382. // DIY returns the raw value by a given key.
  383. func (c *IniConfigContainer) DIY(key string) (v interface{}, err error) {
  384. if v, ok := c.data[strings.ToLower(key)]; ok {
  385. return v, nil
  386. }
  387. return v, errors.New("key not find")
  388. }
  389. // section.key or key
  390. func (c *IniConfigContainer) getdata(key string) string {
  391. if len(key) == 0 {
  392. return ""
  393. }
  394. c.RLock()
  395. defer c.RUnlock()
  396. var (
  397. section, k string
  398. sectionKey = strings.Split(strings.ToLower(key), "::")
  399. )
  400. if len(sectionKey) >= 2 {
  401. section = sectionKey[0]
  402. k = sectionKey[1]
  403. } else {
  404. section = defaultSection
  405. k = sectionKey[0]
  406. }
  407. if v, ok := c.data[section]; ok {
  408. if vv, ok := v[k]; ok {
  409. return vv
  410. }
  411. }
  412. return ""
  413. }
  414. func init() {
  415. Register("ini", &IniConfig{})
  416. }