程序包开发,读简单配置文件 v1

服务计算Homework04

  • 项目地址
  • 使用说明:Watch文件夹存放路径为xx/github.com/Watch,直接运行main.go即可,注意将配置文件my.inimain.go放在同一目录下

任务目标

  • 熟悉程序包的编写习惯(idioms)和风格(convetions)
  • 熟悉 io 库操作
  • 使用测试驱动的方法
  • 简单 Go 程使用
  • 事件通知

任务内容

在 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

任务要求

  • 核心任务:包必须提供一个函数 Watch(filename,listener) (configuration, error)
    • 输入 filename 是配置文件名
    • 输入 listener 一个特殊的接口,用来监听配置文件是否被修改,让开发者自己决定如何处理配置变化
    • 输出 configuration 数据类型,可根据 key 读对应的 value。 key 和 value 都是字符串
    • 输出 error 是错误数据,如配置文件不存在,无法打开等
    • 可选的函数 WatchWithOption(filename,listener,...) (configuration, error)
  • 包必须包括以下内容:
    • 生成的中文 api 文档
    • 有较好的 Readme 文件,包括一个简单的使用案例
    • 每个go文件必须有对应的测试文件
    • 必须提供自定义错误
    • 使有 init 函数,使得 Unix 系统默认采用#作为注释行,Windows 系统默认采用 ; 作为注释行。

实现过程

1. 实现一个基础的读取ini文件

  • 打开文件,如果出错则直接返回
  • 读取文件内容并逐行查找键值对
  • 注意最后一行可能没有换行符,要做特殊处理
  • 返回存储键值对的configuration和自定义错误error
// 读取文件并返回ini的键值对和自定义错误类型
func readFile(filename string) (map[string]string, errorString) {
	// 打开文件
	var error errorString
	file, err := os.Open(filename)
	configuration := make(map[string]string)
	// 打开文件错误
	if err != nil {
		error = createErr("error1: failed to open the configuration file")
		return configuration, error
	} else {
		error = createErr("")
	}

	// 函数结束时关闭文件
	defer file.Close()
	// 读取文件内容
	reader := bufio.NewReader(file)
	for {
		// 读取一行文件
		linestr, err := reader.ReadString('\n')
		// 最后一行没有换行符,err==EOF直接退出, 不能读取最后一行
		if err != nil && err != io.EOF {
			fmt.Println(err)
			break
		}

		// 切掉行的左右两边的空白字符
		linestr = strings.TrimSpace(linestr)
		// 忽略空行、注释、分段
		if linestr == "" || linestr[0] == comment || linestr[0] == '[' {
			continue
		}
		pair := strings.Split(linestr, "=")
		// 保证切开只有一个等号的情况
		if len(pair) == 2 {
			// 去掉多余空白字符
			key := strings.TrimSpace(pair[0])
			value := strings.TrimSpace(pair[1])
			configuration[key] = value
			// fmt.Println(key)
		}

		// 判断是否为最后一行,可改为:err != io.EOF
		if err != nil {
			// fmt.Println(err)
			break
		}
	}

	return configuration, error
}

2. readFile中用到的自定义错误类型

  • 自定义错误数据结构,只有一个成员变量为string类型,记录错误信息
  • createErr方法,新创建一个错误,类似于面向对象语言的构造函数
  • Error方法,输出错误信息,类似于面向对象的成员函数
// 错误信息的数据结构
type errorString struct {
	str string
}

// 创建错误信息的方法
func createErr(text string) errorString {
	return errorString{text}
}

// 打印错误信息并返回
func (err *errorString) Error() string {
	fmt.Fprintf(os.Stdout, err.str)
	return err.str
}

3. 接口Listener的实现

  • ListenFunc是函数签名,定义一个参数为string的函数类型
  • Listener是一个自定义接口,拥有方法listen
  • 函数签名ListenFunc实现接口Listener的方法listen, 使得所有满足签名的函数、方法、所有实现 Listener 接口的数据类型都可作为参数,方便传参实现Watch函数
// 函数签名
type ListenFunc func(string)

// 自定义接口
type Listener interface {
	listen(infile string)
}

//函数签名ListenFunc实现接口方法
func (Listen ListenFunc) listen(inifile string) {
	// 调用具体的fileListen
	fileListen(inifile)
}

4.具体的fileListen函数实现

  • 将原始文件内容读入切片originSlice
  • 监听文件内容并读入切片curSlice
  • originSlicecurSlice的内容逐行比较,如果不等则检测到修改退出监听
// 监听文件
func fileListen(fileName string) {
	originSlice := []string{}
	originSlice = fileContent(fileName)
	flag := false

	for {
		curSlice := []string{}
		curSlice = fileContent(fileName)

		for i, value := range originSlice {
			if value != curSlice[i] {
				flag = true
				break
			}
		}

		if flag == true {
			break
		}
	}
}

另一种实现思路是监听文件状态的修改时间这一属性,但是可能造成内容没有修改然而修改时间发生改变的情况,所以不采取该实现方法

5. 监听函数Watch的实现

  • 读取文件并打印信息
  • 开始监听,如果文件有改变,则退出监听并打印最新文件内容
func Watch(filename string, Listen Listener) (map[string]string, errorString) {
	myInit("Windows")
	// 第一次读取文件
	config, error1 := readFile(filename)
	if len(error1.str) != 0 {
		fmt.Printf("First - Error occurs : %s", error1)
		return config, error1
	}

	// 打印配置文件信息
	printConfig(config)

	// 打印原始文件内容
	printFile(filename)
	fmt.Printf("Listening---------------------------------------------------:\n")
	// 开启监听文件
	Listen.listen(filename)
	// 文件被修改
	fmt.Printf("The ini file has been changed :\n")
	// 打印修改后文件内容
	printFile(filename)

	return config, error1
}

5. 其他辅助函数

  • init函数(init与内置函数重名,本文实现用myInit函数代替)

    func myInit(sysType string) {
    	if sysType == "Linux" {
    		comment = '#'
    	} else if sysType == "Windows" {
    		comment = ';'
    	}
    }
    
  • 打印函数printConfig,打印对应的ini文件键值对

    func printConfig(configure map[string]string) {
    	fmt.Printf("Print configure begin: \n")
    	for key, value := range configure {
    		fmt.Println(key, "=", value)
    	}
    	fmt.Printf("Print configure end\n\n")
    }
    
    
  • 打印函数printFile,打印指定文件内容

    func printFile(fileName string) {
    	// 打开文件
    	file, err := os.Open(fileName)
    	// 打开文件错误
    	if err != nil {
    		return
    	}
    
    	// 函数结束时关闭文件
    	defer file.Close()
    	// 读取文件内容
    	reader := bufio.NewReader(file)
    	fmt.Println("Print File begin")
    	for {
    		// 读取一行文件
    		linestr, err := reader.ReadString('\n')
    		fmt.Println(linestr)
    		if err != nil {
    			break
    		}
    	}
    	fmt.Println("Print File end")
    }
    
  • 打印函数fileContent,获取文件内容保存在切片并返回

    	func fileContent(fileName string) []string {
    	// 声明了一个暂不确定长度大小的string切片
    	// 一个切片在未初始化之前默认为 nil,长度为 0
    	var strSlice []string
    	file, err := os.Open(fileName)
    	// 打开文件错误
    	if err != nil {
    		return strSlice
    	}
    	// 函数结束时关闭文件
    	defer file.Close()
    	// 读取文件内容
    	reader := bufio.NewReader(file)
    
    	for {
    		// 读取一行文件
    		linestr, err := reader.ReadString('\n')
    		strSlice = append(strSlice, linestr)
    		// 错误判断
    		if err != nil {
    			break
    		}
    
    	}
    
    	return strSlice
    }
    

6. 结果展示

  • 配置文件内容

    [core]
    repositoryformatversion = 0
    
    filemode = false
    bare = false
    logallrefupdates = true
    symlinks = false
    ignorecase = true
    hideDotFiles = dotGitOnly
    [remote "origin"]
    url = https://github.com/davyxu/cellnet
    fetch = +refs/heads/*:refs/remotes/origin/*
    [branch "master"]
    remote = origin
    merge = refs/heads/master
    
  • 打印ini配置文件键值对

    红色方框内不是配置文件内容
    在这里插入图片描述

  • 监听文件内容修改前
    在这里插入图片描述

  • 监听文件内容修改后
    在这里插入图片描述

  • 测试readFile,测试文件见项目
    在这里插入图片描述

  • 自动生成的API文档,详见项目
    在这里插入图片描述

参考资料

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值