Let‘s Go —— Go语言开发之旅(四)

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

概述

配置文件(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文件来引导用户使用我们的程序包

参考资料

程序包开发
Go配置文件读取

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值