一直有计划将 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")
}