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.

ini.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. // Copyright 2014 Unknwon
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License"): you may
  4. // not use this file except in compliance with the License. You may obtain
  5. // 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, WITHOUT
  11. // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. // License for the specific language governing permissions and limitations
  13. // under the License.
  14. // Package ini provides INI file read and write functionality in Go.
  15. package ini
  16. import (
  17. "bytes"
  18. "errors"
  19. "fmt"
  20. "io"
  21. "os"
  22. "regexp"
  23. "runtime"
  24. "strconv"
  25. "strings"
  26. "sync"
  27. "time"
  28. )
  29. const (
  30. // Name for default section. You can use this constant or the string literal.
  31. // In most of cases, an empty string is all you need to access the section.
  32. DEFAULT_SECTION = "DEFAULT"
  33. // Maximum allowed depth when recursively substituing variable names.
  34. _DEPTH_VALUES = 99
  35. _VERSION = "1.21.1"
  36. )
  37. // Version returns current package version literal.
  38. func Version() string {
  39. return _VERSION
  40. }
  41. var (
  42. // Delimiter to determine or compose a new line.
  43. // This variable will be changed to "\r\n" automatically on Windows
  44. // at package init time.
  45. LineBreak = "\n"
  46. // Variable regexp pattern: %(variable)s
  47. varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
  48. // Indicate whether to align "=" sign with spaces to produce pretty output
  49. // or reduce all possible spaces for compact format.
  50. PrettyFormat = true
  51. // Explicitly write DEFAULT section header
  52. DefaultHeader = false
  53. )
  54. func init() {
  55. if runtime.GOOS == "windows" {
  56. LineBreak = "\r\n"
  57. }
  58. }
  59. func inSlice(str string, s []string) bool {
  60. for _, v := range s {
  61. if str == v {
  62. return true
  63. }
  64. }
  65. return false
  66. }
  67. // dataSource is an interface that returns object which can be read and closed.
  68. type dataSource interface {
  69. ReadCloser() (io.ReadCloser, error)
  70. }
  71. // sourceFile represents an object that contains content on the local file system.
  72. type sourceFile struct {
  73. name string
  74. }
  75. func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
  76. return os.Open(s.name)
  77. }
  78. type bytesReadCloser struct {
  79. reader io.Reader
  80. }
  81. func (rc *bytesReadCloser) Read(p []byte) (n int, err error) {
  82. return rc.reader.Read(p)
  83. }
  84. func (rc *bytesReadCloser) Close() error {
  85. return nil
  86. }
  87. // sourceData represents an object that contains content in memory.
  88. type sourceData struct {
  89. data []byte
  90. }
  91. func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
  92. return &bytesReadCloser{bytes.NewReader(s.data)}, nil
  93. }
  94. // File represents a combination of a or more INI file(s) in memory.
  95. type File struct {
  96. // Should make things safe, but sometimes doesn't matter.
  97. BlockMode bool
  98. // Make sure data is safe in multiple goroutines.
  99. lock sync.RWMutex
  100. // Allow combination of multiple data sources.
  101. dataSources []dataSource
  102. // Actual data is stored here.
  103. sections map[string]*Section
  104. // To keep data in order.
  105. sectionList []string
  106. options LoadOptions
  107. NameMapper
  108. ValueMapper
  109. }
  110. // newFile initializes File object with given data sources.
  111. func newFile(dataSources []dataSource, opts LoadOptions) *File {
  112. return &File{
  113. BlockMode: true,
  114. dataSources: dataSources,
  115. sections: make(map[string]*Section),
  116. sectionList: make([]string, 0, 10),
  117. options: opts,
  118. }
  119. }
  120. func parseDataSource(source interface{}) (dataSource, error) {
  121. switch s := source.(type) {
  122. case string:
  123. return sourceFile{s}, nil
  124. case []byte:
  125. return &sourceData{s}, nil
  126. default:
  127. return nil, fmt.Errorf("error parsing data source: unknown type '%s'", s)
  128. }
  129. }
  130. type LoadOptions struct {
  131. // Loose indicates whether the parser should ignore nonexistent files or return error.
  132. Loose bool
  133. // Insensitive indicates whether the parser forces all section and key names to lowercase.
  134. Insensitive bool
  135. // IgnoreContinuation indicates whether to ignore continuation lines while parsing.
  136. IgnoreContinuation bool
  137. // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
  138. // This type of keys are mostly used in my.cnf.
  139. AllowBooleanKeys bool
  140. }
  141. func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
  142. sources := make([]dataSource, len(others)+1)
  143. sources[0], err = parseDataSource(source)
  144. if err != nil {
  145. return nil, err
  146. }
  147. for i := range others {
  148. sources[i+1], err = parseDataSource(others[i])
  149. if err != nil {
  150. return nil, err
  151. }
  152. }
  153. f := newFile(sources, opts)
  154. if err = f.Reload(); err != nil {
  155. return nil, err
  156. }
  157. return f, nil
  158. }
  159. // Load loads and parses from INI data sources.
  160. // Arguments can be mixed of file name with string type, or raw data in []byte.
  161. // It will return error if list contains nonexistent files.
  162. func Load(source interface{}, others ...interface{}) (*File, error) {
  163. return LoadSources(LoadOptions{}, source, others...)
  164. }
  165. // LooseLoad has exactly same functionality as Load function
  166. // except it ignores nonexistent files instead of returning error.
  167. func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
  168. return LoadSources(LoadOptions{Loose: true}, source, others...)
  169. }
  170. // InsensitiveLoad has exactly same functionality as Load function
  171. // except it forces all section and key names to be lowercased.
  172. func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
  173. return LoadSources(LoadOptions{Insensitive: true}, source, others...)
  174. }
  175. // Empty returns an empty file object.
  176. func Empty() *File {
  177. // Ignore error here, we sure our data is good.
  178. f, _ := Load([]byte(""))
  179. return f
  180. }
  181. // NewSection creates a new section.
  182. func (f *File) NewSection(name string) (*Section, error) {
  183. if len(name) == 0 {
  184. return nil, errors.New("error creating new section: empty section name")
  185. } else if f.options.Insensitive && name != DEFAULT_SECTION {
  186. name = strings.ToLower(name)
  187. }
  188. if f.BlockMode {
  189. f.lock.Lock()
  190. defer f.lock.Unlock()
  191. }
  192. if inSlice(name, f.sectionList) {
  193. return f.sections[name], nil
  194. }
  195. f.sectionList = append(f.sectionList, name)
  196. f.sections[name] = newSection(f, name)
  197. return f.sections[name], nil
  198. }
  199. // NewSections creates a list of sections.
  200. func (f *File) NewSections(names ...string) (err error) {
  201. for _, name := range names {
  202. if _, err = f.NewSection(name); err != nil {
  203. return err
  204. }
  205. }
  206. return nil
  207. }
  208. // GetSection returns section by given name.
  209. func (f *File) GetSection(name string) (*Section, error) {
  210. if len(name) == 0 {
  211. name = DEFAULT_SECTION
  212. } else if f.options.Insensitive {
  213. name = strings.ToLower(name)
  214. }
  215. if f.BlockMode {
  216. f.lock.RLock()
  217. defer f.lock.RUnlock()
  218. }
  219. sec := f.sections[name]
  220. if sec == nil {
  221. return nil, fmt.Errorf("section '%s' does not exist", name)
  222. }
  223. return sec, nil
  224. }
  225. // Section assumes named section exists and returns a zero-value when not.
  226. func (f *File) Section(name string) *Section {
  227. sec, err := f.GetSection(name)
  228. if err != nil {
  229. // Note: It's OK here because the only possible error is empty section name,
  230. // but if it's empty, this piece of code won't be executed.
  231. sec, _ = f.NewSection(name)
  232. return sec
  233. }
  234. return sec
  235. }
  236. // Section returns list of Section.
  237. func (f *File) Sections() []*Section {
  238. sections := make([]*Section, len(f.sectionList))
  239. for i := range f.sectionList {
  240. sections[i] = f.Section(f.sectionList[i])
  241. }
  242. return sections
  243. }
  244. // SectionStrings returns list of section names.
  245. func (f *File) SectionStrings() []string {
  246. list := make([]string, len(f.sectionList))
  247. copy(list, f.sectionList)
  248. return list
  249. }
  250. // DeleteSection deletes a section.
  251. func (f *File) DeleteSection(name string) {
  252. if f.BlockMode {
  253. f.lock.Lock()
  254. defer f.lock.Unlock()
  255. }
  256. if len(name) == 0 {
  257. name = DEFAULT_SECTION
  258. }
  259. for i, s := range f.sectionList {
  260. if s == name {
  261. f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
  262. delete(f.sections, name)
  263. return
  264. }
  265. }
  266. }
  267. func (f *File) reload(s dataSource) error {
  268. r, err := s.ReadCloser()
  269. if err != nil {
  270. return err
  271. }
  272. defer r.Close()
  273. return f.parse(r)
  274. }
  275. // Reload reloads and parses all data sources.
  276. func (f *File) Reload() (err error) {
  277. for _, s := range f.dataSources {
  278. if err = f.reload(s); err != nil {
  279. // In loose mode, we create an empty default section for nonexistent files.
  280. if os.IsNotExist(err) && f.options.Loose {
  281. f.parse(bytes.NewBuffer(nil))
  282. continue
  283. }
  284. return err
  285. }
  286. }
  287. return nil
  288. }
  289. // Append appends one or more data sources and reloads automatically.
  290. func (f *File) Append(source interface{}, others ...interface{}) error {
  291. ds, err := parseDataSource(source)
  292. if err != nil {
  293. return err
  294. }
  295. f.dataSources = append(f.dataSources, ds)
  296. for _, s := range others {
  297. ds, err = parseDataSource(s)
  298. if err != nil {
  299. return err
  300. }
  301. f.dataSources = append(f.dataSources, ds)
  302. }
  303. return f.Reload()
  304. }
  305. // WriteToIndent writes content into io.Writer with given indention.
  306. // If PrettyFormat has been set to be true,
  307. // it will align "=" sign with spaces under each section.
  308. func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
  309. equalSign := "="
  310. if PrettyFormat {
  311. equalSign = " = "
  312. }
  313. // Use buffer to make sure target is safe until finish encoding.
  314. buf := bytes.NewBuffer(nil)
  315. for i, sname := range f.sectionList {
  316. sec := f.Section(sname)
  317. if len(sec.Comment) > 0 {
  318. if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
  319. sec.Comment = "; " + sec.Comment
  320. }
  321. if _, err = buf.WriteString(sec.Comment + LineBreak); err != nil {
  322. return 0, err
  323. }
  324. }
  325. if i > 0 || DefaultHeader {
  326. if _, err = buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
  327. return 0, err
  328. }
  329. } else {
  330. // Write nothing if default section is empty
  331. if len(sec.keyList) == 0 {
  332. continue
  333. }
  334. }
  335. // Count and generate alignment length and buffer spaces using the
  336. // longest key. Keys may be modifed if they contain certain characters so
  337. // we need to take that into account in our calculation.
  338. alignLength := 0
  339. if PrettyFormat {
  340. for _, kname := range sec.keyList {
  341. keyLength := len(kname)
  342. // First case will surround key by ` and second by """
  343. if strings.ContainsAny(kname, "\"=:") {
  344. keyLength += 2
  345. } else if strings.Contains(kname, "`") {
  346. keyLength += 6
  347. }
  348. if keyLength > alignLength {
  349. alignLength = keyLength
  350. }
  351. }
  352. }
  353. alignSpaces := bytes.Repeat([]byte(" "), alignLength)
  354. for _, kname := range sec.keyList {
  355. key := sec.Key(kname)
  356. if len(key.Comment) > 0 {
  357. if len(indent) > 0 && sname != DEFAULT_SECTION {
  358. buf.WriteString(indent)
  359. }
  360. if key.Comment[0] != '#' && key.Comment[0] != ';' {
  361. key.Comment = "; " + key.Comment
  362. }
  363. if _, err = buf.WriteString(key.Comment + LineBreak); err != nil {
  364. return 0, err
  365. }
  366. }
  367. if len(indent) > 0 && sname != DEFAULT_SECTION {
  368. buf.WriteString(indent)
  369. }
  370. switch {
  371. case key.isAutoIncrement:
  372. kname = "-"
  373. case strings.ContainsAny(kname, "\"=:"):
  374. kname = "`" + kname + "`"
  375. case strings.Contains(kname, "`"):
  376. kname = `"""` + kname + `"""`
  377. }
  378. if _, err = buf.WriteString(kname); err != nil {
  379. return 0, err
  380. }
  381. if key.isBooleanType {
  382. continue
  383. }
  384. // Write out alignment spaces before "=" sign
  385. if PrettyFormat {
  386. buf.Write(alignSpaces[:alignLength-len(kname)])
  387. }
  388. val := key.value
  389. // In case key value contains "\n", "`", "\"", "#" or ";"
  390. if strings.ContainsAny(val, "\n`") {
  391. val = `"""` + val + `"""`
  392. } else if strings.ContainsAny(val, "#;") {
  393. val = "`" + val + "`"
  394. }
  395. if _, err = buf.WriteString(equalSign + val + LineBreak); err != nil {
  396. return 0, err
  397. }
  398. }
  399. // Put a line between sections
  400. if _, err = buf.WriteString(LineBreak); err != nil {
  401. return 0, err
  402. }
  403. }
  404. return buf.WriteTo(w)
  405. }
  406. // WriteTo writes file content into io.Writer.
  407. func (f *File) WriteTo(w io.Writer) (int64, error) {
  408. return f.WriteToIndent(w, "")
  409. }
  410. // SaveToIndent writes content to file system with given value indention.
  411. func (f *File) SaveToIndent(filename, indent string) error {
  412. // Note: Because we are truncating with os.Create,
  413. // so it's safer to save to a temporary file location and rename afte done.
  414. tmpPath := filename + "." + strconv.Itoa(time.Now().Nanosecond()) + ".tmp"
  415. defer os.Remove(tmpPath)
  416. fw, err := os.Create(tmpPath)
  417. if err != nil {
  418. return err
  419. }
  420. if _, err = f.WriteToIndent(fw, indent); err != nil {
  421. fw.Close()
  422. return err
  423. }
  424. fw.Close()
  425. // Remove old file and rename the new one.
  426. os.Remove(filename)
  427. return os.Rename(tmpPath, filename)
  428. }
  429. // SaveTo writes content to file system.
  430. func (f *File) SaveTo(filename string) error {
  431. return f.SaveToIndent(filename, "")
  432. }