1// Copyright 2017 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 15package ini 16 17import ( 18 "bytes" 19 "errors" 20 "fmt" 21 "io" 22 "io/ioutil" 23 "os" 24 "strings" 25 "sync" 26) 27 28// File represents a combination of a or more INI file(s) in memory. 29type File struct { 30 options LoadOptions 31 dataSources []dataSource 32 33 // Should make things safe, but sometimes doesn't matter. 34 BlockMode bool 35 lock sync.RWMutex 36 37 // To keep data in order. 38 sectionList []string 39 // Actual data is stored here. 40 sections map[string]*Section 41 42 NameMapper 43 ValueMapper 44} 45 46// newFile initializes File object with given data sources. 47func newFile(dataSources []dataSource, opts LoadOptions) *File { 48 if len(opts.KeyValueDelimiters) == 0 { 49 opts.KeyValueDelimiters = "=:" 50 } 51 return &File{ 52 BlockMode: true, 53 dataSources: dataSources, 54 sections: make(map[string]*Section), 55 sectionList: make([]string, 0, 10), 56 options: opts, 57 } 58} 59 60// Empty returns an empty file object. 61func Empty() *File { 62 // Ignore error here, we sure our data is good. 63 f, _ := Load([]byte("")) 64 return f 65} 66 67// NewSection creates a new section. 68func (f *File) NewSection(name string) (*Section, error) { 69 if len(name) == 0 { 70 return nil, errors.New("error creating new section: empty section name") 71 } else if f.options.Insensitive && name != DEFAULT_SECTION { 72 name = strings.ToLower(name) 73 } 74 75 if f.BlockMode { 76 f.lock.Lock() 77 defer f.lock.Unlock() 78 } 79 80 if inSlice(name, f.sectionList) { 81 return f.sections[name], nil 82 } 83 84 f.sectionList = append(f.sectionList, name) 85 f.sections[name] = newSection(f, name) 86 return f.sections[name], nil 87} 88 89// NewRawSection creates a new section with an unparseable body. 90func (f *File) NewRawSection(name, body string) (*Section, error) { 91 section, err := f.NewSection(name) 92 if err != nil { 93 return nil, err 94 } 95 96 section.isRawSection = true 97 section.rawBody = body 98 return section, nil 99} 100 101// NewSections creates a list of sections. 102func (f *File) NewSections(names ...string) (err error) { 103 for _, name := range names { 104 if _, err = f.NewSection(name); err != nil { 105 return err 106 } 107 } 108 return nil 109} 110 111// GetSection returns section by given name. 112func (f *File) GetSection(name string) (*Section, error) { 113 if len(name) == 0 { 114 name = DEFAULT_SECTION 115 } 116 if f.options.Insensitive { 117 name = strings.ToLower(name) 118 } 119 120 if f.BlockMode { 121 f.lock.RLock() 122 defer f.lock.RUnlock() 123 } 124 125 sec := f.sections[name] 126 if sec == nil { 127 return nil, fmt.Errorf("section '%s' does not exist", name) 128 } 129 return sec, nil 130} 131 132// Section assumes named section exists and returns a zero-value when not. 133func (f *File) Section(name string) *Section { 134 sec, err := f.GetSection(name) 135 if err != nil { 136 // Note: It's OK here because the only possible error is empty section name, 137 // but if it's empty, this piece of code won't be executed. 138 sec, _ = f.NewSection(name) 139 return sec 140 } 141 return sec 142} 143 144// Section returns list of Section. 145func (f *File) Sections() []*Section { 146 if f.BlockMode { 147 f.lock.RLock() 148 defer f.lock.RUnlock() 149 } 150 151 sections := make([]*Section, len(f.sectionList)) 152 for i, name := range f.sectionList { 153 sections[i] = f.sections[name] 154 } 155 return sections 156} 157 158// ChildSections returns a list of child sections of given section name. 159func (f *File) ChildSections(name string) []*Section { 160 return f.Section(name).ChildSections() 161} 162 163// SectionStrings returns list of section names. 164func (f *File) SectionStrings() []string { 165 list := make([]string, len(f.sectionList)) 166 copy(list, f.sectionList) 167 return list 168} 169 170// DeleteSection deletes a section. 171func (f *File) DeleteSection(name string) { 172 if f.BlockMode { 173 f.lock.Lock() 174 defer f.lock.Unlock() 175 } 176 177 if len(name) == 0 { 178 name = DEFAULT_SECTION 179 } 180 181 for i, s := range f.sectionList { 182 if s == name { 183 f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...) 184 delete(f.sections, name) 185 return 186 } 187 } 188} 189 190func (f *File) reload(s dataSource) error { 191 r, err := s.ReadCloser() 192 if err != nil { 193 return err 194 } 195 defer r.Close() 196 197 return f.parse(r) 198} 199 200// Reload reloads and parses all data sources. 201func (f *File) Reload() (err error) { 202 for _, s := range f.dataSources { 203 if err = f.reload(s); err != nil { 204 // In loose mode, we create an empty default section for nonexistent files. 205 if os.IsNotExist(err) && f.options.Loose { 206 f.parse(bytes.NewBuffer(nil)) 207 continue 208 } 209 return err 210 } 211 } 212 return nil 213} 214 215// Append appends one or more data sources and reloads automatically. 216func (f *File) Append(source interface{}, others ...interface{}) error { 217 ds, err := parseDataSource(source) 218 if err != nil { 219 return err 220 } 221 f.dataSources = append(f.dataSources, ds) 222 for _, s := range others { 223 ds, err = parseDataSource(s) 224 if err != nil { 225 return err 226 } 227 f.dataSources = append(f.dataSources, ds) 228 } 229 return f.Reload() 230} 231 232func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) { 233 equalSign := DefaultFormatLeft + "=" + DefaultFormatRight 234 235 if PrettyFormat || PrettyEqual { 236 equalSign = " = " 237 } 238 239 // Use buffer to make sure target is safe until finish encoding. 240 buf := bytes.NewBuffer(nil) 241 for i, sname := range f.sectionList { 242 sec := f.Section(sname) 243 if len(sec.Comment) > 0 { 244 // Support multiline comments 245 lines := strings.Split(sec.Comment, LineBreak) 246 for i := range lines { 247 if lines[i][0] != '#' && lines[i][0] != ';' { 248 lines[i] = "; " + lines[i] 249 } else { 250 lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:]) 251 } 252 253 if _, err := buf.WriteString(lines[i] + LineBreak); err != nil { 254 return nil, err 255 } 256 } 257 } 258 259 if i > 0 || DefaultHeader { 260 if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil { 261 return nil, err 262 } 263 } else { 264 // Write nothing if default section is empty 265 if len(sec.keyList) == 0 { 266 continue 267 } 268 } 269 270 if sec.isRawSection { 271 if _, err := buf.WriteString(sec.rawBody); err != nil { 272 return nil, err 273 } 274 275 if PrettySection { 276 // Put a line between sections 277 if _, err := buf.WriteString(LineBreak); err != nil { 278 return nil, err 279 } 280 } 281 continue 282 } 283 284 // Count and generate alignment length and buffer spaces using the 285 // longest key. Keys may be modifed if they contain certain characters so 286 // we need to take that into account in our calculation. 287 alignLength := 0 288 if PrettyFormat { 289 for _, kname := range sec.keyList { 290 keyLength := len(kname) 291 // First case will surround key by ` and second by """ 292 if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) { 293 keyLength += 2 294 } else if strings.Contains(kname, "`") { 295 keyLength += 6 296 } 297 298 if keyLength > alignLength { 299 alignLength = keyLength 300 } 301 } 302 } 303 alignSpaces := bytes.Repeat([]byte(" "), alignLength) 304 305 KEY_LIST: 306 for _, kname := range sec.keyList { 307 key := sec.Key(kname) 308 if len(key.Comment) > 0 { 309 if len(indent) > 0 && sname != DEFAULT_SECTION { 310 buf.WriteString(indent) 311 } 312 313 // Support multiline comments 314 lines := strings.Split(key.Comment, LineBreak) 315 for i := range lines { 316 if lines[i][0] != '#' && lines[i][0] != ';' { 317 lines[i] = "; " + strings.TrimSpace(lines[i]) 318 } else { 319 lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:]) 320 } 321 322 if _, err := buf.WriteString(lines[i] + LineBreak); err != nil { 323 return nil, err 324 } 325 } 326 } 327 328 if len(indent) > 0 && sname != DEFAULT_SECTION { 329 buf.WriteString(indent) 330 } 331 332 switch { 333 case key.isAutoIncrement: 334 kname = "-" 335 case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters): 336 kname = "`" + kname + "`" 337 case strings.Contains(kname, "`"): 338 kname = `"""` + kname + `"""` 339 } 340 341 for _, val := range key.ValueWithShadows() { 342 if _, err := buf.WriteString(kname); err != nil { 343 return nil, err 344 } 345 346 if key.isBooleanType { 347 if kname != sec.keyList[len(sec.keyList)-1] { 348 buf.WriteString(LineBreak) 349 } 350 continue KEY_LIST 351 } 352 353 // Write out alignment spaces before "=" sign 354 if PrettyFormat { 355 buf.Write(alignSpaces[:alignLength-len(kname)]) 356 } 357 358 // In case key value contains "\n", "`", "\"", "#" or ";" 359 if strings.ContainsAny(val, "\n`") { 360 val = `"""` + val + `"""` 361 } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") { 362 val = "`" + val + "`" 363 } 364 if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil { 365 return nil, err 366 } 367 } 368 369 for _, val := range key.nestedValues { 370 if _, err := buf.WriteString(indent + " " + val + LineBreak); err != nil { 371 return nil, err 372 } 373 } 374 } 375 376 if PrettySection { 377 // Put a line between sections 378 if _, err := buf.WriteString(LineBreak); err != nil { 379 return nil, err 380 } 381 } 382 } 383 384 return buf, nil 385} 386 387// WriteToIndent writes content into io.Writer with given indention. 388// If PrettyFormat has been set to be true, 389// it will align "=" sign with spaces under each section. 390func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) { 391 buf, err := f.writeToBuffer(indent) 392 if err != nil { 393 return 0, err 394 } 395 return buf.WriteTo(w) 396} 397 398// WriteTo writes file content into io.Writer. 399func (f *File) WriteTo(w io.Writer) (int64, error) { 400 return f.WriteToIndent(w, "") 401} 402 403// SaveToIndent writes content to file system with given value indention. 404func (f *File) SaveToIndent(filename, indent string) error { 405 // Note: Because we are truncating with os.Create, 406 // so it's safer to save to a temporary file location and rename afte done. 407 buf, err := f.writeToBuffer(indent) 408 if err != nil { 409 return err 410 } 411 412 return ioutil.WriteFile(filename, buf.Bytes(), 0666) 413} 414 415// SaveTo writes content to file system. 416func (f *File) SaveTo(filename string) error { 417 return f.SaveToIndent(filename, "") 418} 419