第四次作业 程序包开发,读简单配置文件v1

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实现

主要是对InitReadFile函数进行测试。

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:

在这里插入图片描述

函数InitReadFile通过测试。

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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值