文章目录
sc-hw04 程序包开发,读简单配置文件
1. 概述
配置文件(Configuration File,CF)是一种文本文档,为计算机系统或程序配置参数和初始设置。传统的配置文件就是文本行,在 Unix 系统中随处可见,通常使用 .conf,.config,.cfg 作为后缀,并逐步形成了 key = value 的配置习惯。在 Windows 系统中添加了对 section 支持,通常用 .ini 作为后缀。面向对象语言的兴起,程序员需要直接将文本反序列化成内存对象作为配置,逐步提出了一些新的配置文件格式,包括 JSON,YAML,TOML 等。
2. 要求
1. 任务目标
1. 熟悉程序包的编写习惯(idioms)和风格(convetions)
2. 熟悉 io 库操作
3. 使用测试驱动的方法
4. 简单 Go 程使用
5. 事件通知
2.任务内容
在 Gitee 或 GitHub 上发布一个读配置文件程序包,第一版仅需要读 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
将该文件保存为config.ini
文件。
3. 任务要求
1. 核心任务:包必须提供一个函数 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 是错误数据,如配置文件不存在,无法打开等
可选的函数 WatchWithOption(filename,listener,...) (configuration, error)
2. 包必须包括以下内容:
生成的中文 api 文档
有较好的 Readme 文件,包括一个简单的使用案例
每个go文件必须有对应的测试文件
必须提供自定义错误
使有 init 函数,使得 Unix 系统默认采用 # 作为注释行,Windows 系统默认采用 ; 作为注释行。
3. 不能使用第三方包,但可以参考、甚至复制它们的代码。例如:
ini 读写包。 Github,中文支持
Viper 读配置集成解决方案包。Github
live watching and re-reading of config files (optional)
fsnotify 文件系统通知包。 Github
你可以参考这些代码,但不能在你的包中 import
3. 实现
1. readini.go
实现
1. 结构体定义
type KeyValue map[string]string //储存键值对
type Config map[string][]KeyValue //储存section及其对应键值对
type ListenFunc func(string)
type Listener interface {
listen(inifile string)
}
2. Init
函数:初始化当前系统所用的注释符。(linux系统采用#
,Windows系统采用;
)
var note byte
func Init(){
sysType := runtime.GOOS
if sysType == "linux" {
note = '#'
}
if sysType == "windows" {
note = ';'
}
}
3. ReadFile
函数:读取.ini
文件,唯一一个参数是要读取文件的路径,两个返回值Config和err
func ReadFile(filename string)(Config, error){
Init()
c := make(Config)
var err error
file, err1 := os.Open(filename)
if err1 != nil {
err = errors.New("Fail to open file.")
return c, err
}
defer file.Close()
//listener.listen(filename)
buf := bufio.NewReader(file)
section := ""
//replacer := strings.NewReplacer(" ","")
for {
lstr, err2 := buf.ReadString('\n')
if err2 == io.EOF{
break
}
if err2 != nil {
err = errors.New("Fail to read file")
break
}
lstr = strings.TrimSpace(lstr)
if lstr == ""{
continue
}
if lstr[0] == note {
continue
}
length := len(lstr)
if lstr[0] =='[' && lstr[length - 1] == ']'{
section = lstr[1: length - 1]
if _, ok := c[section]; !ok {
c[section] = []KeyValue{}
}
}else {
s := strings.Split(lstr, "=")
if len(s) < 2{
err = errors.New("syntax error: wrong key-value format.")
break
}
key := strings.TrimSpace(s[0])
value := strings.TrimSpace(s[1])
keyvalue := make(KeyValue)
keyvalue[key] = value
if section == ""{
c[section] = []KeyValue{}
}
if _, ok := c[section]; ok {
c[section] = append(c[section], keyvalue)
}
}
}
return c, err
}
func (listenfunc ListenFunc) listen(inifile string) {
config, _ := ReadFile(inifile)
fmt.Println("listen...")
for {
tmpConfig, _ := ReadFile(inifile)
if !reflect.DeepEqual(config, tmpConfig) {
break
}
time.Sleep(2 * time.Millisecond)
}
}
4. listen
函数:监听文件,如果文件发生改变就返回
func (listenfunc ListenFunc) listen(inifile string) {
config, _ := ReadFile(inifile)
fmt.Println("listen...")
for {
tmpConfig, _ := ReadFile(inifile)
if !reflect.DeepEqual(config, tmpConfig) {
break
}
time.Sleep(2 * time.Millisecond)
}
}
5. Watch
函数:调用listen
函数监听,返回文件改变前后的值
func Watch(filename string, listener Listener)(Config, error){
config, err := ReadFile(filename)
if err != nil{
fmt.Printf("error: %s\n", err)
}
//var str []string
fmt.Printf("Before:\n")
for s, _ := range config {
for _, value := range config[s] {
//str = append(str, s)
fmt.Printf("%s\n",s)
for k, v := range value {
fmt.Printf("%s %s\n",k,v)
//str = append(str, k)
//str = append(str, v)
}
}
}
fmt.Printf("\n")
listener.listen(filename)
fmt.Printf("file changed\n")
config, err = ReadFile(filename)
fmt.Printf("After:\n")
for s, _ := range config {
for _, value := range config[s] {
//str = append(str, s)
fmt.Printf("%s\n",s)
for k, v := range value {
fmt.Printf("%s %s\n",k,v)
//str = append(str, k)
//str = append(str, v)
}
}
}
fmt.Printf("\n")
return config, err
}
5. readini.go
完成后,执行以下指令生成readini
包:
go build readini.go
go install github.com/readini
这一步完成之后,接下来的测试就可以通过import github.com/readini
使用readini
包了。
2. readini_test.go
实现
主要是对Init
和ReadFile
函数进行测试。
1. TestInit
函数
func TestInit(t * testing.T){
var want byte
Init()
if runtime.GOOS == "linux"{
want = '#'
}
if runtime.GOOS == "windows"{
want =';'
}
if want != note{
t.Errorf("want %v but got %v", want, note)
}
}
2. TestReadFile
函数:测试读取的config.ini文件的内容是否与预期输出一致
func TestReadFile(t *testing.T){
got , _ := ReadFile("config.ini");
var str []string
for s, _ := range got {
for _, value := range got[s] {
str = append(str, s)
//fmt.Printf("%s\n",s)
for k, v := range value {
//fmt.Printf("%s %s\n",k,v)
str = append(str, k)
str = append(str, v)
}
}
}
//var want []string
want := []string{"","app_mode","development","paths","data","/home/git/grafana","server","protocol", "http","server","http_port","9999","server","enforce_domain","true"}
for i := 0; i < 15; i++{
if str[i] != want[i]{
t.Errorf("want '%v' but got '%v'", want[i], str[i]);
}
}
}
3. 运行readini_test.go
:
函数Init
和ReadFile
通过测试。
3. main.go
实现
1. 代码
package main
import (
watch "github.com/readini"
)
type Listenmain func(string)
func main() {
var listener watch.Listener
listener = watch.ListenFunc(func(string) {})
watch.Watch("test.ini", listener)
}
2. test.ini
配置文件:可以与上述config.ini
配置文件相同,也可以自己设置。这里与config.ini
配置文件相同。
3. 执行go run main.go
输出test.ini
配置文件修改前的内容,在文件被修改前一直处于监听状态。
此时将protocol键对应的值改为https,可以发现监听结束,程序结束运行。
4. 生成api文档
1. 安装godoc
这是前几次作业的内容了。这里再重复一遍,逐条执行以下指令:
mkdir -p $GOPATH/src/golang.org/x
cd $GOPATH/src/golang.org/x
git clone https://github.com/golang/tools.git
go build golang.org/x/tools/cmd/godoc
godoc安装成功。
2. 生成readini
包对应的api文档
(1)首先,进入readini 包所在目录:
cd /github.com/readini
(2)执行godoc -http :6060
;
(3)浏览器输入http://localhost:6060/
访问,找到readini
包,我的路径为:http://localhost:6060/pkg/github.com/readini/
,直接打开链接:http://localhost:6060/pkg/github.com/readini/。
(4)导出api文档:执行godoc -url “http://localhost:6060/pkg/github.com/readini/” > api.html.