gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

file.go (13957B)


      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 
     15 package ini
     16 
     17 import (
     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 one or more INI files in memory.
     29 type 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 	// To keep track of the index of a section with same name.
     40 	// This meta list is only used with non-unique section names are allowed.
     41 	sectionIndexes []int
     42 
     43 	// Actual data is stored here.
     44 	sections map[string][]*Section
     45 
     46 	NameMapper
     47 	ValueMapper
     48 }
     49 
     50 // newFile initializes File object with given data sources.
     51 func newFile(dataSources []dataSource, opts LoadOptions) *File {
     52 	if len(opts.KeyValueDelimiters) == 0 {
     53 		opts.KeyValueDelimiters = "=:"
     54 	}
     55 	if len(opts.KeyValueDelimiterOnWrite) == 0 {
     56 		opts.KeyValueDelimiterOnWrite = "="
     57 	}
     58 	if len(opts.ChildSectionDelimiter) == 0 {
     59 		opts.ChildSectionDelimiter = "."
     60 	}
     61 
     62 	return &File{
     63 		BlockMode:   true,
     64 		dataSources: dataSources,
     65 		sections:    make(map[string][]*Section),
     66 		options:     opts,
     67 	}
     68 }
     69 
     70 // Empty returns an empty file object.
     71 func Empty(opts ...LoadOptions) *File {
     72 	var opt LoadOptions
     73 	if len(opts) > 0 {
     74 		opt = opts[0]
     75 	}
     76 
     77 	// Ignore error here, we are sure our data is good.
     78 	f, _ := LoadSources(opt, []byte(""))
     79 	return f
     80 }
     81 
     82 // NewSection creates a new section.
     83 func (f *File) NewSection(name string) (*Section, error) {
     84 	if len(name) == 0 {
     85 		return nil, errors.New("empty section name")
     86 	}
     87 
     88 	if (f.options.Insensitive || f.options.InsensitiveSections) && name != DefaultSection {
     89 		name = strings.ToLower(name)
     90 	}
     91 
     92 	if f.BlockMode {
     93 		f.lock.Lock()
     94 		defer f.lock.Unlock()
     95 	}
     96 
     97 	if !f.options.AllowNonUniqueSections && inSlice(name, f.sectionList) {
     98 		return f.sections[name][0], nil
     99 	}
    100 
    101 	f.sectionList = append(f.sectionList, name)
    102 
    103 	// NOTE: Append to indexes must happen before appending to sections,
    104 	// otherwise index will have off-by-one problem.
    105 	f.sectionIndexes = append(f.sectionIndexes, len(f.sections[name]))
    106 
    107 	sec := newSection(f, name)
    108 	f.sections[name] = append(f.sections[name], sec)
    109 
    110 	return sec, nil
    111 }
    112 
    113 // NewRawSection creates a new section with an unparseable body.
    114 func (f *File) NewRawSection(name, body string) (*Section, error) {
    115 	section, err := f.NewSection(name)
    116 	if err != nil {
    117 		return nil, err
    118 	}
    119 
    120 	section.isRawSection = true
    121 	section.rawBody = body
    122 	return section, nil
    123 }
    124 
    125 // NewSections creates a list of sections.
    126 func (f *File) NewSections(names ...string) (err error) {
    127 	for _, name := range names {
    128 		if _, err = f.NewSection(name); err != nil {
    129 			return err
    130 		}
    131 	}
    132 	return nil
    133 }
    134 
    135 // GetSection returns section by given name.
    136 func (f *File) GetSection(name string) (*Section, error) {
    137 	secs, err := f.SectionsByName(name)
    138 	if err != nil {
    139 		return nil, err
    140 	}
    141 
    142 	return secs[0], err
    143 }
    144 
    145 // HasSection returns true if the file contains a section with given name.
    146 func (f *File) HasSection(name string) bool {
    147 	section, _ := f.GetSection(name)
    148 	return section != nil
    149 }
    150 
    151 // SectionsByName returns all sections with given name.
    152 func (f *File) SectionsByName(name string) ([]*Section, error) {
    153 	if len(name) == 0 {
    154 		name = DefaultSection
    155 	}
    156 	if f.options.Insensitive || f.options.InsensitiveSections {
    157 		name = strings.ToLower(name)
    158 	}
    159 
    160 	if f.BlockMode {
    161 		f.lock.RLock()
    162 		defer f.lock.RUnlock()
    163 	}
    164 
    165 	secs := f.sections[name]
    166 	if len(secs) == 0 {
    167 		return nil, fmt.Errorf("section %q does not exist", name)
    168 	}
    169 
    170 	return secs, nil
    171 }
    172 
    173 // Section assumes named section exists and returns a zero-value when not.
    174 func (f *File) Section(name string) *Section {
    175 	sec, err := f.GetSection(name)
    176 	if err != nil {
    177 		if name == "" {
    178 			name = DefaultSection
    179 		}
    180 		sec, _ = f.NewSection(name)
    181 		return sec
    182 	}
    183 	return sec
    184 }
    185 
    186 // SectionWithIndex assumes named section exists and returns a new section when not.
    187 func (f *File) SectionWithIndex(name string, index int) *Section {
    188 	secs, err := f.SectionsByName(name)
    189 	if err != nil || len(secs) <= index {
    190 		// NOTE: It's OK here because the only possible error is empty section name,
    191 		// but if it's empty, this piece of code won't be executed.
    192 		newSec, _ := f.NewSection(name)
    193 		return newSec
    194 	}
    195 
    196 	return secs[index]
    197 }
    198 
    199 // Sections returns a list of Section stored in the current instance.
    200 func (f *File) Sections() []*Section {
    201 	if f.BlockMode {
    202 		f.lock.RLock()
    203 		defer f.lock.RUnlock()
    204 	}
    205 
    206 	sections := make([]*Section, len(f.sectionList))
    207 	for i, name := range f.sectionList {
    208 		sections[i] = f.sections[name][f.sectionIndexes[i]]
    209 	}
    210 	return sections
    211 }
    212 
    213 // ChildSections returns a list of child sections of given section name.
    214 func (f *File) ChildSections(name string) []*Section {
    215 	return f.Section(name).ChildSections()
    216 }
    217 
    218 // SectionStrings returns list of section names.
    219 func (f *File) SectionStrings() []string {
    220 	list := make([]string, len(f.sectionList))
    221 	copy(list, f.sectionList)
    222 	return list
    223 }
    224 
    225 // DeleteSection deletes a section or all sections with given name.
    226 func (f *File) DeleteSection(name string) {
    227 	secs, err := f.SectionsByName(name)
    228 	if err != nil {
    229 		return
    230 	}
    231 
    232 	for i := 0; i < len(secs); i++ {
    233 		// For non-unique sections, it is always needed to remove the first one so
    234 		// in the next iteration, the subsequent section continue having index 0.
    235 		// Ignoring the error as index 0 never returns an error.
    236 		_ = f.DeleteSectionWithIndex(name, 0)
    237 	}
    238 }
    239 
    240 // DeleteSectionWithIndex deletes a section with given name and index.
    241 func (f *File) DeleteSectionWithIndex(name string, index int) error {
    242 	if !f.options.AllowNonUniqueSections && index != 0 {
    243 		return fmt.Errorf("delete section with non-zero index is only allowed when non-unique sections is enabled")
    244 	}
    245 
    246 	if len(name) == 0 {
    247 		name = DefaultSection
    248 	}
    249 	if f.options.Insensitive || f.options.InsensitiveSections {
    250 		name = strings.ToLower(name)
    251 	}
    252 
    253 	if f.BlockMode {
    254 		f.lock.Lock()
    255 		defer f.lock.Unlock()
    256 	}
    257 
    258 	// Count occurrences of the sections
    259 	occurrences := 0
    260 
    261 	sectionListCopy := make([]string, len(f.sectionList))
    262 	copy(sectionListCopy, f.sectionList)
    263 
    264 	for i, s := range sectionListCopy {
    265 		if s != name {
    266 			continue
    267 		}
    268 
    269 		if occurrences == index {
    270 			if len(f.sections[name]) <= 1 {
    271 				delete(f.sections, name) // The last one in the map
    272 			} else {
    273 				f.sections[name] = append(f.sections[name][:index], f.sections[name][index+1:]...)
    274 			}
    275 
    276 			// Fix section lists
    277 			f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
    278 			f.sectionIndexes = append(f.sectionIndexes[:i], f.sectionIndexes[i+1:]...)
    279 
    280 		} else if occurrences > index {
    281 			// Fix the indices of all following sections with this name.
    282 			f.sectionIndexes[i-1]--
    283 		}
    284 
    285 		occurrences++
    286 	}
    287 
    288 	return nil
    289 }
    290 
    291 func (f *File) reload(s dataSource) error {
    292 	r, err := s.ReadCloser()
    293 	if err != nil {
    294 		return err
    295 	}
    296 	defer r.Close()
    297 
    298 	return f.parse(r)
    299 }
    300 
    301 // Reload reloads and parses all data sources.
    302 func (f *File) Reload() (err error) {
    303 	for _, s := range f.dataSources {
    304 		if err = f.reload(s); err != nil {
    305 			// In loose mode, we create an empty default section for nonexistent files.
    306 			if os.IsNotExist(err) && f.options.Loose {
    307 				_ = f.parse(bytes.NewBuffer(nil))
    308 				continue
    309 			}
    310 			return err
    311 		}
    312 		if f.options.ShortCircuit {
    313 			return nil
    314 		}
    315 	}
    316 	return nil
    317 }
    318 
    319 // Append appends one or more data sources and reloads automatically.
    320 func (f *File) Append(source interface{}, others ...interface{}) error {
    321 	ds, err := parseDataSource(source)
    322 	if err != nil {
    323 		return err
    324 	}
    325 	f.dataSources = append(f.dataSources, ds)
    326 	for _, s := range others {
    327 		ds, err = parseDataSource(s)
    328 		if err != nil {
    329 			return err
    330 		}
    331 		f.dataSources = append(f.dataSources, ds)
    332 	}
    333 	return f.Reload()
    334 }
    335 
    336 func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
    337 	equalSign := DefaultFormatLeft + f.options.KeyValueDelimiterOnWrite + DefaultFormatRight
    338 
    339 	if PrettyFormat || PrettyEqual {
    340 		equalSign = fmt.Sprintf(" %s ", f.options.KeyValueDelimiterOnWrite)
    341 	}
    342 
    343 	// Use buffer to make sure target is safe until finish encoding.
    344 	buf := bytes.NewBuffer(nil)
    345 	lastSectionIdx := len(f.sectionList) - 1
    346 	for i, sname := range f.sectionList {
    347 		sec := f.SectionWithIndex(sname, f.sectionIndexes[i])
    348 		if len(sec.Comment) > 0 {
    349 			// Support multiline comments
    350 			lines := strings.Split(sec.Comment, LineBreak)
    351 			for i := range lines {
    352 				if lines[i][0] != '#' && lines[i][0] != ';' {
    353 					lines[i] = "; " + lines[i]
    354 				} else {
    355 					lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
    356 				}
    357 
    358 				if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
    359 					return nil, err
    360 				}
    361 			}
    362 		}
    363 
    364 		if i > 0 || DefaultHeader || (i == 0 && strings.ToUpper(sec.name) != DefaultSection) {
    365 			if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
    366 				return nil, err
    367 			}
    368 		} else {
    369 			// Write nothing if default section is empty
    370 			if len(sec.keyList) == 0 {
    371 				continue
    372 			}
    373 		}
    374 
    375 		isLastSection := i == lastSectionIdx
    376 		if sec.isRawSection {
    377 			if _, err := buf.WriteString(sec.rawBody); err != nil {
    378 				return nil, err
    379 			}
    380 
    381 			if PrettySection && !isLastSection {
    382 				// Put a line between sections
    383 				if _, err := buf.WriteString(LineBreak); err != nil {
    384 					return nil, err
    385 				}
    386 			}
    387 			continue
    388 		}
    389 
    390 		// Count and generate alignment length and buffer spaces using the
    391 		// longest key. Keys may be modified if they contain certain characters so
    392 		// we need to take that into account in our calculation.
    393 		alignLength := 0
    394 		if PrettyFormat {
    395 			for _, kname := range sec.keyList {
    396 				keyLength := len(kname)
    397 				// First case will surround key by ` and second by """
    398 				if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) {
    399 					keyLength += 2
    400 				} else if strings.Contains(kname, "`") {
    401 					keyLength += 6
    402 				}
    403 
    404 				if keyLength > alignLength {
    405 					alignLength = keyLength
    406 				}
    407 			}
    408 		}
    409 		alignSpaces := bytes.Repeat([]byte(" "), alignLength)
    410 
    411 	KeyList:
    412 		for _, kname := range sec.keyList {
    413 			key := sec.Key(kname)
    414 			if len(key.Comment) > 0 {
    415 				if len(indent) > 0 && sname != DefaultSection {
    416 					buf.WriteString(indent)
    417 				}
    418 
    419 				// Support multiline comments
    420 				lines := strings.Split(key.Comment, LineBreak)
    421 				for i := range lines {
    422 					if lines[i][0] != '#' && lines[i][0] != ';' {
    423 						lines[i] = "; " + strings.TrimSpace(lines[i])
    424 					} else {
    425 						lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
    426 					}
    427 
    428 					if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
    429 						return nil, err
    430 					}
    431 				}
    432 			}
    433 
    434 			if len(indent) > 0 && sname != DefaultSection {
    435 				buf.WriteString(indent)
    436 			}
    437 
    438 			switch {
    439 			case key.isAutoIncrement:
    440 				kname = "-"
    441 			case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters):
    442 				kname = "`" + kname + "`"
    443 			case strings.Contains(kname, "`"):
    444 				kname = `"""` + kname + `"""`
    445 			}
    446 
    447 			writeKeyValue := func(val string) (bool, error) {
    448 				if _, err := buf.WriteString(kname); err != nil {
    449 					return false, err
    450 				}
    451 
    452 				if key.isBooleanType {
    453 					buf.WriteString(LineBreak)
    454 					return true, nil
    455 				}
    456 
    457 				// Write out alignment spaces before "=" sign
    458 				if PrettyFormat {
    459 					buf.Write(alignSpaces[:alignLength-len(kname)])
    460 				}
    461 
    462 				// In case key value contains "\n", "`", "\"", "#" or ";"
    463 				if strings.ContainsAny(val, "\n`") {
    464 					val = `"""` + val + `"""`
    465 				} else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
    466 					val = "`" + val + "`"
    467 				} else if len(strings.TrimSpace(val)) != len(val) {
    468 					val = `"` + val + `"`
    469 				}
    470 				if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
    471 					return false, err
    472 				}
    473 				return false, nil
    474 			}
    475 
    476 			shadows := key.ValueWithShadows()
    477 			if len(shadows) == 0 {
    478 				if _, err := writeKeyValue(""); err != nil {
    479 					return nil, err
    480 				}
    481 			}
    482 
    483 			for _, val := range shadows {
    484 				exitLoop, err := writeKeyValue(val)
    485 				if err != nil {
    486 					return nil, err
    487 				} else if exitLoop {
    488 					continue KeyList
    489 				}
    490 			}
    491 
    492 			for _, val := range key.nestedValues {
    493 				if _, err := buf.WriteString(indent + "  " + val + LineBreak); err != nil {
    494 					return nil, err
    495 				}
    496 			}
    497 		}
    498 
    499 		if PrettySection && !isLastSection {
    500 			// Put a line between sections
    501 			if _, err := buf.WriteString(LineBreak); err != nil {
    502 				return nil, err
    503 			}
    504 		}
    505 	}
    506 
    507 	return buf, nil
    508 }
    509 
    510 // WriteToIndent writes content into io.Writer with given indention.
    511 // If PrettyFormat has been set to be true,
    512 // it will align "=" sign with spaces under each section.
    513 func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) {
    514 	buf, err := f.writeToBuffer(indent)
    515 	if err != nil {
    516 		return 0, err
    517 	}
    518 	return buf.WriteTo(w)
    519 }
    520 
    521 // WriteTo writes file content into io.Writer.
    522 func (f *File) WriteTo(w io.Writer) (int64, error) {
    523 	return f.WriteToIndent(w, "")
    524 }
    525 
    526 // SaveToIndent writes content to file system with given value indention.
    527 func (f *File) SaveToIndent(filename, indent string) error {
    528 	// Note: Because we are truncating with os.Create,
    529 	// 	so it's safer to save to a temporary file location and rename after done.
    530 	buf, err := f.writeToBuffer(indent)
    531 	if err != nil {
    532 		return err
    533 	}
    534 
    535 	return ioutil.WriteFile(filename, buf.Bytes(), 0666)
    536 }
    537 
    538 // SaveTo writes content to file system.
    539 func (f *File) SaveTo(filename string) error {
    540 	return f.SaveToIndent(filename, "")
    541 }