iniutils for Golang
一直有计划将 Delphi 中的譬如 TIniFile 等相关功能移植到 Golang,这些设施在 Delphi 中(相对而言)比较常用,使用起来也非常方便。
虽然 Github 上早已有这些三方库,但我还是想仿照 Delphi 的做法来实现一套(其实只是模仿了 TMemIniFile 而非 Windows 下的 TIniFile 实现,暂时无意愿去实现 GetPrivateProfileXXX/WritePrivateProfileXXX 等 API),并额外提供直接干脆的调用接口等。
代码已托管至 Github。
// Copyright 2017 ecofast(无尽愿). All rights reserved. // Use of this source code is governed by a BSD-style license. // Package iniutils was translated from TMemIniFile in Delphi(2007) RTL, // which loads an entire INI file into memory // and allows all operations to be performed on the memory image. // The image can then be written out to the disk file. package iniutils import ( "bufio" "bytes" "fmt" "os" "strconv" "strings" "github.com/ecofast/sysutils" ) type IniFile struct { fileName string caseSensitive bool sections map[string][]string } func NewIniFile(filename string, casesensitive bool) *IniFile { ini := &IniFile{ fileName: filename, caseSensitive: casesensitive, sections: make(map[string][]string), } ini.loadValues() return ini } func (ini *IniFile) FileName() string { return ini.fileName } func (ini *IniFile) CaseSensitive() bool { return ini.caseSensitive } func (ini *IniFile) String() string { var buf bytes.Buffer for sec, lst := range ini.sections { buf.WriteString(fmt.Sprintf("[%s]\n", sec)) for _, s := range lst { buf.WriteString(fmt.Sprintf("%s\n", s)) } buf.WriteString("\n") } return buf.String() } func (ini *IniFile) getRealValue(s string) string { if !ini.caseSensitive { return strings.ToLower(s) } return s } func (ini *IniFile) loadValues() { if !sysutils.FileExists(ini.fileName) { return } file, err := os.Open(ini.fileName) if err != nil { return } defer file.Close() section := "" scanner := bufio.NewScanner(file) for scanner.Scan() { s := scanner.Text() s = strings.TrimSpace(s) s = ini.getRealValue(s) if s != "" && s[0] != ';' { if s[0] == '[' && s[len(s)-1] == ']' { s = s[1 : len(s)-1] section = s } else { if section != "" { if pos := strings.Index(s, "="); pos > 0 { if sl, ok := ini.sections[section]; ok { ini.sections[section] = append(sl, s) } else { ini.sections[section] = []string{s} } } else { // ingore invalid ident // } } } } } } func (ini *IniFile) flush() { file, err := os.Create(ini.fileName) sysutils.CheckError(err) defer file.Close() fw := bufio.NewWriter(file) for sec, lst := range ini.sections { _, err = fw.WriteString(fmt.Sprintf("[%s]\n", sec)) sysutils.CheckError(err) for _, s := range lst { _, err = fw.WriteString(fmt.Sprintf("%s\n", s)) sysutils.CheckError(err) } _, err = fw.WriteString("\n") sysutils.CheckError(err) } fw.Flush() } func (ini *IniFile) SectionExists(section string) bool { sec := ini.getRealValue(section) if _, ok := ini.sections[sec]; ok { return true } return false } func (ini *IniFile) ReadSections() []string { var ss []string for sec, _ := range ini.sections { ss = append(ss, sec) } return ss } func (ini *IniFile) EraseSection(section string) { sec := ini.getRealValue(section) delete(ini.sections, sec) } func (ini *IniFile) ReadSectionIdents(section string) []string { var ss []string sec := ini.getRealValue(section) if sl, ok := ini.sections[sec]; ok { for _, s := range sl { if pos := strings.Index(s, "="); pos > 0 { ss = append(ss, s[0:pos]) } } } return ss } func (ini *IniFile) ReadSectionValues(section string) []string { var ss []string sec := ini.getRealValue(section) if sl, ok := ini.sections[sec]; ok { for _, s := range sl { ss = append(ss, s) } } return ss } func (ini *IniFile) DeleteIdent(section, ident string) { sec := ini.getRealValue(section) id := ini.getRealValue(ident) if sl, ok := ini.sections[sec]; ok { for i := 0; i < len(sl); i++ { s := sl[i] if pos := strings.Index(s, "="); pos > 0 { if s[0:pos] == id { var ss []string for j := 0; j < i; j++ { ss = append(ss, sl[j]) } for j := i + 1; j < len(sl); j++ { ss = append(ss, sl[j]) } ini.sections[sec] = ss return } } } } } func (ini *IniFile) IdentExists(section, ident string) bool { sec := ini.getRealValue(section) id := ini.getRealValue(ident) if sl, ok := ini.sections[sec]; ok { for _, s := range sl { if pos := strings.Index(s, "="); pos > 0 { if s[0:pos] == id { return true } } } } return false } func (ini *IniFile) ReadString(section, ident, defaultValue string) string { sec := ini.getRealValue(section) id := ini.getRealValue(ident) if sl, ok := ini.sections[sec]; ok { for _, s := range sl { if pos := strings.Index(s, "="); pos > 0 { if s[0:pos] == id { return s[pos+1:] } } } } return defaultValue } func (ini *IniFile) WriteString(section, ident, value string) { sec := ini.getRealValue(section) id := ini.getRealValue(ident) if sl, ok := ini.sections[sec]; ok { for i := 0; i < len(sl); i++ { s := sl[i] if pos := strings.Index(s, "="); pos > 0 { if s[0:pos] == id { var ss []string for j := 0; j < i; j++ { ss = append(ss, sl[j]) } ss = append(ss, ident+"="+value) for j := i + 1; j < len(sl); j++ { ss = append(ss, sl[j]) } ini.sections[sec] = ss return } } } ini.sections[sec] = append(sl, ident+"="+value) } else { ini.sections[sec] = []string{ident + "=" + value} } } func (ini *IniFile) ReadInt(section, ident string, defaultValue int) int { s := ini.ReadString(section, ident, "") if ret, err := strconv.Atoi(s); err == nil { return ret } else { return defaultValue } } func (ini *IniFile) WriteInt(section, ident string, value int) { ini.WriteString(section, ident, strconv.Itoa(value)) } func (ini *IniFile) ReadBool(section, ident string, defaultValue bool) bool { s := ini.ReadString(section, ident, sysutils.BoolToStr(defaultValue)) return sysutils.StrToBool(s) } func (ini *IniFile) WriteBool(section, ident string, value bool) { ini.WriteString(section, ident, sysutils.BoolToStr(value)) } func (ini *IniFile) ReadFloat(section, ident string, defaultValue float64) float64 { s := ini.ReadString(section, ident, "") if s != "" { if ret, err := strconv.ParseFloat(s, 64); err == nil { return ret } } return defaultValue } func (ini *IniFile) WriteFloat(section, ident string, value float64) { ini.WriteString(section, ident, sysutils.FloatToStr(value)) } func (ini *IniFile) Close() { ini.flush() } func (ini *IniFile) Clear() { ini.sections = make(map[string][]string) } func IniReadString(fileName, section, ident, defaultValue string) string { inifile := NewIniFile(fileName, false) defer inifile.Close() return inifile.ReadString(section, ident, defaultValue) } func IniWriteString(fileName, section, ident, value string) { inifile := NewIniFile(fileName, false) defer inifile.Close() inifile.WriteString(section, ident, value) } func IniReadInt(fileName, section, ident string, defaultValue int) int { inifile := NewIniFile(fileName, false) defer inifile.Close() return inifile.ReadInt(section, ident, defaultValue) } func IniWriteInt(fileName, section, ident string, value int) { inifile := NewIniFile(fileName, false) defer inifile.Close() inifile.WriteInt(section, ident, value) } func IniSectionExists(fileName, section string) bool { inifile := NewIniFile(fileName, false) defer inifile.Close() return inifile.SectionExists(section) } func IniReadSectionIdents(fileName, section string) []string { inifile := NewIniFile(fileName, false) defer inifile.Close() return inifile.ReadSectionIdents(section) } func IniReadSections(fileName string) []string { inifile := NewIniFile(fileName, false) defer inifile.Close() return inifile.ReadSections() } func IniReadSectionValues(fileName, section string) []string { inifile := NewIniFile(fileName, false) defer inifile.Close() return inifile.ReadSectionValues(section) } func IniEraseSection(fileName, section string) { inifile := NewIniFile(fileName, false) defer inifile.Close() inifile.EraseSection(section) } func IniIdentExists(fileName, section, ident string) bool { inifile := NewIniFile(fileName, false) defer inifile.Close() return inifile.IdentExists(section, ident) } func IniDeleteIdent(fileName, section, ident string) { inifile := NewIniFile(fileName, false) defer inifile.Close() inifile.DeleteIdent(section, ident) }
而 Delphi 的 RTL 里提供有非常多的方便、实用、简洁的函数如 IntToStr、FileExists、IncludeTrailingBackslash 等等等等,我也打算慢慢地移植一些到 Golang,算是造点基础的轮子吧。
// Copyright 2016~2017 ecofast(无尽愿). All rights reserved. // Use of this source code is governed by a BSD-style license. // Package sysutils implements some useful system utility functions // in the way of which Delphi(2007) RTL has done. package sysutils import ( "fmt" "os" "path/filepath" "strconv" "strings" ) func CheckError(e error) { if e != nil { panic(e) } } func BoolToStr(b bool) string { if b { return "1" } return "0" } func StrToBool(s string) bool { if ret, err := strconv.ParseBool(s); err == nil { return ret } return false } func FloatToStr(f float64) string { return fmt.Sprintf("%g", f) } func GetApplicationPath() string { path := filepath.Dir(os.Args[0]) return path + string(os.PathSeparator) } func DirectoryExists(path string) bool { fileInfo, err := os.Stat(path) if err == nil && fileInfo.IsDir() { return true } return false } func FileExists(filename string) bool { _, err := os.Stat(filename) return err == nil || os.IsExist(err) } func CreateFile(filename string) bool { // os.MkdirAll(path.Dir(filename)) _, err := os.Create(filename) if err == nil { return true } return false } func IncludeTrailingBackslash(path string) string { if !strings.HasSuffix(path, string(os.PathSeparator)) { return path + string(os.PathSeparator) } return path }
最后再来个 litelog,基本照搬的 Go Recipes 里的代码。
// Copyright 2016~2017 ecofast(无尽愿). All rights reserved. // Use of this source code is governed by a BSD-style license. // Package litelog provides a logging infrastructure with an option // to set the log level, log file and write log data into log file package litelog import ( "io" "io/ioutil" "log" "os" ) // holds the log level type LogLvl int const ( // logs nothing LvlNone LogLvl = iota // logs everything LvlTrace // logs Info, Warnings and Errors LvlInfo // logs Warnings and Errors LvlWarning // just logs Errors LvlError ) // package level variables which are pointers to log.Logger var ( Trace *log.Logger Info *log.Logger Warning *log.Logger Error *log.Logger ) // initializes log.Logger objects func initLogger(trace, info, warn, err io.Writer, flags int) { flag := log.Ldate | log.Ltime | log.Lshortfile if flags != 0 { flag = flags } Trace = log.New(trace, "[Trace] ", flag) Info = log.New(info, "[Info] ", flag) Warning = log.New(warn, "[Warning] ", flag) Error = log.New(err, "[Error] ", flag) } // Setup logging facilities func Initialize(loglvl LogLvl, logflag int, logfile string) { logFile, err := os.OpenFile(logfile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { log.Fatalf("Error opening log file: %s", err.Error()) } switch loglvl { case LvlTrace: initLogger(logFile, logFile, logFile, logFile, logflag) return case LvlInfo: initLogger(ioutil.Discard, logFile, logFile, logFile, logflag) return case LvlWarning: initLogger(ioutil.Discard, ioutil.Discard, logFile, logFile, logflag) return case LvlError: initLogger(ioutil.Discard, ioutil.Discard, ioutil.Discard, logFile, logflag) return default: initLogger(ioutil.Discard, ioutil.Discard, ioutil.Discard, ioutil.Discard, logflag) logFile.Close() return } }
这是示例代码和执行结果。
// litelogdemo project main.go package main import ( "errors" "flag" "litelog" "github.com/ecofast/sysutils" ) func main() { loglvl := flag.Int("loglvl", 0, "an integer value(0--4)") flag.Parse() litelog.Initialize(litelog.LogLvl(*loglvl), 0, sysutils.GetApplicationPath()+"logs.txt") litelog.Trace.Println("=====Main started=====") test() err := errors.New("Sample error") litelog.Error.Println(err.Error()) litelog.Trace.Println("=====Main completed=====") } func test() { litelog.Trace.Println("Test started") for i := 0; i < 10; i++ { litelog.Info.Println("Counter value is:", i) } litelog.Warning.Println("The counter variable is not being used") litelog.Trace.Println("Test completed") }