Let's Go
- 程序包开发,读取简单配置文件
- 概述
- 配置文件格式
- 核心功能
- 主要实现
- 提供自定义错误
- 配置文件读取
- init函数
- 配置文件监听函数
- 单元测试
- 程序包内容
- 参考资料
程序包开发,读取简单配置文件
概述
配置文件(Configuration File,CF)是一种文本文档,为计算机系统或程序配置参数和初始设置。传统的配置文件就是文本行,在 Unix 系统中随处可见,通常使用 .conf,.config,.cfg 作为后缀,并逐步形成了 key = value 的配置习惯。在 Windows 系统中添加了对 section 支持,通常用 .ini 作为后缀。面向对象语言的兴起,程序员需要直接将文本反序列化成内存对象作为配置,逐步提出了一些新的配置文件格式,包括 JSON,YAML,TOML 等。
配置文件格式
程序包读取ini配置文件格式如下:
# possible values : production, development
app_mode = development
[paths]
# Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used)
data = /home/git/grafana
[server]
# Protocol (http or https)
protocol = http
# The http port to use
http_port = 9999
# Redirect to correct domain if host header does not match domain
# Prevents DNS rebinding attacks
enforce_domain = true
核心功能
主要实现
包必须提供一个函数 Watch(filename,listener) (configuration, error)
- 输入 filename 是配置文件名
- 输入 listener 一个特殊的接口,用来监听配置文件是否被修改,让开发者自己决定如何处理配置变化
- type ListenFunc func(string)
- type inteface Listener { listen(inifile string) }
- ListenFunc 实现接口方法 listen 直接调用函数
- 优点
- 所有满足签名的函数、方法都可以作为参数
- 所有实现 Listener 接口的数据类型都可作为参数
- 输出 configuration 数据类型,可根据 key 读对应的 value。 key 和 value 都是字符串
- 输出 error 是错误数据,如配置文件不存在,无法打开等
提供自定义错误
首先我们需要提供自定义错误,以便于接受程序运行时产生的错误
//创建一个自定义的错误类型
func myError(text string) errorString {
return errorString{text}
}
//错误信息的数据结构
type errorString struct {
s string
}
//错误的方法:打印错误信息并返回错误信息
func (e *errorString) Error() string {
fmt.Fprintf(os.Stdout, e.s)
return e.s
}
配置文件读取
接着我们需要从ini文件中读取配置信息,并将信息存储到相应的数据结构中
func readFile(iniFile string) (map[string]string, errorString) {
config := map[string]string{} //储存配置文件信息
var e errorString //自定义的错误类型
file, err := os.Open(iniFile)
if err != nil {
e = myError("can not open file")
return config, e
}
// 在函数结束时,关闭文件
defer file.Close()
//读取输入流
reader := bufio.NewReader(file)
// 当前读取的段的名字
sectionName := ""
//进行循环读取文件逐行进行文件读取
for {
// 读取文件的一行
linestr, err := reader.ReadString('\n')
if err != nil {
break
}
// 切掉行的左右两边的空白字符
linestr = strings.TrimSpace(linestr)
// 忽略空行
if linestr == "" {
continue
}
// 忽略注释
if linestr[0] == ';' || linestr[0] == '#'{
continue
}
// 行首和尾巴分别是方括号的,说明是段标记的起止符
if linestr[0] == '[' && linestr[len(linestr)-1] == ']' {
// 将段名取出
sectionName = linestr[1 : len(linestr)-1]
fmt.Printf("%s:\n", sectionName)
// 这个段是希望读取的
} else if sectionName != "" {
// 切开等号分割的键值对
pair := strings.Split(linestr, "=")
// 保证切开只有1个等号分割的简直情况
if len(pair) == 2 {
// 去掉键的多余空白字符
key := strings.TrimSpace(pair[0])
// 是期望的键
if key != "" {
// 返回去掉空白字符的值
value := strings.TrimSpace(pair[1])
config[key] = strings.TrimSpace(pair[1])
fmt.Printf("%s = %s\n", key, value)
}
}
}
}
return config, e
}
init函数
使用init 函数,使得 Unix 系统默认采用 # 作为注释行,Windows 系统默认采用 ; 作为注释行。
//判断系统类型,根据系统类型判断注释符
func initSys() {
sysType := runtime.GOOS
if sysType == "linux" {
symbol = '#'
} else if sysType == "window" {
symbol = ';'
}
}
配置文件监听函数
接着需要完成配置文件监听函数 Watch(filename,listener) (configuration, error)
//函数签名,定义一个参数为string的函数类型
type ListenFunc func(string)
//接口
type Listener interface {
listen(inifile string)
}
//函数签名ListenFunc实现接口方法
func (Listen ListenFunc) listen(inifile string) {
changeConfig, _ := readFile(inifile)
print("Listening :\n")
for {
config, _ := readFile(inifile)
if !mapCompare(changeConfig, config) {
break
}
}
}
//读取文件,然后监听文件,如果文件在监听期间修改,则打印返回最新文件,并结束
func Watch(iniFile string, Listen Listener) (map[string]string, errorString) {
//判断系统类型,根据系统类型判断注释符
initSys()
config, err := readFile(iniFile)
//存在错误,报错并结束
if len(err.s) > 0 {
fmt.Printf("error : %s", err.s)
return config, err
}
//打印配置文件信息
readFile(iniFile)
//开启监听文件
Listen.listen(iniFile)
//文件被修改:
print("The configuration file has changed :\n")
//读取更新后的文件
config, err = readFile(iniFile)
if len(err.s) > 0 {
fmt.Printf("error : %s", err.s)
}
return config, err
}
运行监听函数,但监听到变化时函数中断返回
单元测试
每个go文件必须有对应的测试文件
func TestReadFile(t *testing.T) {
read, err := readFile("config.ini")
if len(err.s) != 0 {
t.Errorf("input error")
} else {
if read == nil {
t.Errorf("output error")
}
}
}
运行监听函数,但检测到字段发生改变时,函数中断返回
运行测试文件得到单元测试的结果
程序包内容
- 可选的函数 WatchWithOption(filename,listener,…) (configuration, error)
我们可以通过WatchWithOption函数监听ini配置文件中特殊字段的修改,首先修改读取配置文件函数,我们只将特殊字段读取进入数据结构
func readFile(iniFile string, expectedSection string) (map[string]string, errorString)
接着我们通过WatchWithOption函数传入需要监听的配置文件中特殊字段
func WatchWithOptions(iniFile string, expectedSection string ,Listen Listener) (map[string]string, errorString)
运行单元测试得到
- 生成的中文 api 文档
我们可以通过godoc来生成相应的api文档 - 较好的 Readme 文件,包括一个简单的使用案例。我们需要Readme文件来引导用户使用我们的程序包