读简单ini配置文件

Watch

gitee仓库


一、功能介绍:

  • Watch:监听单个配置文件,并返回第一次修改信息
  • WatchWithOption:监听配置文件的某个section,并返回该section的第一次修改信息

二、程序实现:

Config结构体:

type Config struct {
	filepath string                         
	conflist []map[string]map[string]string 
}

listener 接口:

type Listener interface {
	listen(infile string)
}

ListenFunc 接口函数:

type ListenFunc func(infile string) (*Config, error)

2.1 init

初始化函数,在包被加载时调用,用于判断当前系统环境为linux还是windows,用于决定注释行的注释符号 # 或是 ;

func init() {
	sysType := runtime.GOOS
	if sysType == "linux" {
		// LINUX系统
		sys = "#"
	}
	if sysType == "windows" {
		// windows系统
		sys = ";"
	}
}

2.2 SetConfig

初始化Config结构体,设定对应的文件路径

func SetConfig(filepath string) *Config {
	c := new(Config)
	c.filepath = filepath
	return c
}

2.3 ReadList

读取文件,并将配置属性存储到Config中

  • 打开对应文件,如果打开失败则返回 自定义错误 ,完成打开后,使用ReadString来读取换行符,实现按行读取,并当读取失败时,输出自定义 行读取错误(自定义错误实现)
file, err := os.Open(c.filepath)
	if err != nil {
		return nil, fmt.Errorf("Error in ReadList func, can't open the file ") // self define error
	}
	defer file.Close()
	var data map[string]map[string]string
	var section string
	buf := bufio.NewReader(file)
	for {
		l, err := buf.ReadString('\n')
		line := strings.TrimSpace(l)
		if err != nil {
			if err != io.EOF {
				return nil, fmt.Errorf("Error in ReadList func, can't Read the file data ") // self define error
			}
			if len(line) == 0 {
				break
			}
		}
  • 按行进行数据的判断,判断是空行、注释行、section行还是配置行,若配置行的section为空,则设定为默认section: defaultsection
switch {
		case len(line) == 0: //empty line
		case string(line[0]) == sys: // note line
		case line[0] == '[' && line[len(line)-1] == ']':
			section = strings.TrimSpace(line[1 : len(line)-1])
			data = make(map[string]map[string]string)
			data[section] = make(map[string]string)
		default:
			i := strings.IndexAny(line, "=")
			if i == -1 {
				fmt.Println("i = -1")
				continue
			}
			value := strings.TrimSpace(line[i+1 : len(line)])
			if section == "" {
				section = "defaultsection"
			}

			data = make(map[string]map[string]string)
			data[section] = make(map[string]string)
			valmap := strings.TrimSpace(line[0:i])
			data[section][valmap] = value
			if c.unique(section, valmap) == true {
				c.conflist = append(c.conflist, data)
			}
		}

2.4 unique

判断配置项是否重复,防止相同配置的重复写入

func (c *Config) unique(conf string, confmap string) bool {
	for _, v := range c.conflist {
		for k, kmap := range v {
			if k == conf {
				for val, _ := range kmap {
					if val == confmap {
						return false
					}
				}

			}
		}
	}
	return true
}

2.5 Watch

监听文件配置项是否被改变

  • 通过调用listen接口函数来实现
func Watch(filename string, Listener ListenFunc) (*Config, error) {
	Listener = filelisten
	return Listener.listen(filename)
}

通过设定接口函数Listener = filelisten,来调用filelisten函数:

循环读取配置文件,并当配置文件发生改变时,返回改变后的配置属性

func filelisten(infile string) (*Config, error) {
	conf := SetConfig(infile)
	out, err := conf.ReadList()
	if err != nil {
		return nil, err
	}
	var exist bool = false
	for {
		reconf := SetConfig(infile)
		reout, err := reconf.ReadList()
		if err != nil {
			return nil, err
		}
		if len(out) != len(reout) {
			c := SetConfig(infile)
			c.conflist = out
			return c, nil
		}
		for _, val := range reout {
			for valval, valend := range val {
				for finval, v1 := range valend {
					exist = false
					for _, oldval := range out {
						oldmap1 := oldval[valval]
						oldmap2 := oldmap1[finval]
						if oldmap2 == v1 {
							exist = true
						}
					}
					if exist == false {
						c := SetConfig(infile)
						c.conflist = out
						return c, nil
					}
				}
			}
        }
        time.Sleep(1000)
	}
}

2.6 WatchWithOption

监听配置文件的section项是否改变,并返回第一次改变的配置信息

func WatchWithOption(filename string, Listener ListenFunc, section string) (*Config, error) {
	mapstr1 = section
	Listener = optionlisten
	return Listener.listen(filename)
}

通过设定接口函数Listener = optionlisten,来调用optionlisten函数:

返回监听开始后,配置文件该section变化后的配置文件数据

func optionlisten(infile string) (*Config, error) {
	conf := SetConfig(infile)
	out, err := conf.ReadList()
	if err != nil {
		return nil, err
	}
	var exist bool = false
	for {
		reconf := SetConfig(infile)
		reout, err := reconf.ReadList()
		if err != nil {
			return nil, err
		}
		renum := 0
		oldnum := 0
		for _, val := range reout {
			for valnumname, _ := range val {
				if valnumname == mapstr1 {
					renum++
				}
			}
		}
		for _, val := range out {
			for valnumname2, _ := range val {
				if valnumname2 == mapstr1 {
					oldnum++
				}
			}
		}
		if oldnum != renum {
			c := SetConfig(infile)
			c.conflist = out
			return c, nil
		}
		for _, val := range reout {
			for valval, valend := range val {
				if valval == mapstr1 {
					for finval, v1 := range valend {
						exist = false
						for _, oldval := range out {
							for oldvalname, _ := range oldval {
								if oldvalname == mapstr1 {
									oldmap1 := oldval[valval]
									oldmap2 := oldmap1[finval]
									if oldmap2 == v1 {
										exist = true
									}
								}
							}
						}
						if exist == false {
							c := SetConfig(infile)
							c.conflist = out
							return c, nil
						}
					}
				}
			}
        }
        time.Sleep(1000)
	}
}

三、功能测试:

  • changefile函数:
func changefile() {
	wireteString := "a = c\n#noteline\n[sec]\nsecindex = index\n"
	var d1 = []byte(wireteString)
	ioutil.WriteFile("./config.ini", d1, 0666) //写入文件(字节数组)
}

3.1 测试Watch实现:

由于要在gotest中检测到文件改变,所以我们使用go来将changefile函数在子线程运行,让其实现文件的更改供watch函数观察读取,之后进行test的判断

  • watchres函数:先在这里调用写入一次读取,之后当读取到子进程的读取操作后就会触发watch,返回读取的配置属性
func watchres(lis ListenFunc) {
	wireteString := "e = f\n#notline\n[sec]\nsecbasic = basic"
	var d1 = []byte(wireteString)
	ioutil.WriteFile("./config.ini", d1, 0666) //写入文件(字节数组)
	a, _ := Watch("config.ini", lis)
	res = a.Conflist
}

测试函数:


func TestWatch(t *testing.T) {
	var lis ListenFunc = Filelisten
	go changefile()
	watchres(lis)
	if res[0]["defaultsection"]["a"] != "c" || res[1]["sec"]["secindex"] != "index" {
		t.Errorf("Test watch error%s, %s", res[0]["defaultsection"]["a"], res[1]["sec"]["secindex"])
	}
}

测试结果:

在这里插入图片描述

3.2 测试WachWithOption实现

由于要在gotest中检测到文件改变,所以我们使用go来将changefile函数在子线程运行,让其实现文件的更改供watch函数观察读取,之后进行test的判断

  • watchoptionres函数:先在这里调用写入一次读取,之后当读取到子进程的读取操作后就会触发watch,返回读取的配置属性
func watchoptionres(lis ListenFunc) {
	wireteString := "c = d\n#notline\n[sec]\nsecbasic = basic"
	var d1 = []byte(wireteString)
	ioutil.WriteFile("./config.ini", d1, 0666) //写入文件(字节数组)
	a, _ := WatchWithOption("config.ini", lis, "sec")
	sectionres = a.Conflist
}

  • 测试函数:
func TestWatchWithOption(t *testing.T) {
	var lis ListenFunc = Optionlisten
	go changefile()
	watchoptionres(lis)
	if sectionres[0]["defaultsection"]["a"] != "c" || sectionres[1]["sec"]["secindex"] != "index" {
		t.Errorf("Test watch error%s, %s", sectionres[0]["defaultsection"]["a"], sectionres[1]["sec"]["secindex"])
	}
}
  • 测试结果:

在这里插入图片描述

3.3 测试SetConfig函数

func TestSetConfig(t *testing.T) {
	c := SetConfig("config2.ini")
	if c.Filepath != "config2.ini" {
		t.Errorf("set config wrong, should be config2.ini, but set is %s", c.Filepath)
	}
}
  • 测试结果:

在这里插入图片描述

  • 压力测试:
func BenchmarkSetConfig(b *testing.B) {
	for i := 0; i < b.N; i++ {
		SetConfig("a")
	}
}
  • 压测结果:

在这里插入图片描述

3.4 测试ReadList函数

func TestReadList(t *testing.T) {
	conf := new(Config)
	conf.Filepath = "config2.ini"
	_, err := conf.ReadList()
	if err != nil {
		t.Errorf("ReadList wrong\n%s\n", err)
	}
}
  • 测试结果:

在这里插入图片描述

  • 压力测试:
func BenchmarkReadList(b *testing.B) {
	conf := new(Config)
	conf.Filepath = "config2.ini"
	for i := 0; i < b.N; i++ {
		_, err := conf.ReadList()
		if err != nil {
			fmt.Printf("ReadList wrong\n%s\n", err)
		}
	}
}
  • 压测结果:

在这里插入图片描述

3.5 测试unique函数

func TestUnique(t *testing.T) {
	var conf *Config = new(Config)
	conf.Filepath = "config2.ini"
	data := make(map[string]map[string]string)
	section := "a"
	data[section] = make(map[string]string)
	valmap := "b"
	data[section][valmap] = "c"
	conf.Conflist = append(conf.Conflist, data)
	bool1 := conf.unique("a", "b")
	bool2 := conf.unique("c", "d")
	if bool1 != false || bool2 != true {
		t.Errorf("unique wrong")
	}
}
  • 测试结果:

在这里插入图片描述

  • 压力测试:
func BenchmarkUnique(b *testing.B) {
	var conf *Config = new(Config)
	conf.Filepath = "config2.ini"
	data := make(map[string]map[string]string)
	section := "a"
	data[section] = make(map[string]string)
	valmap := "b"
	data[section][valmap] = "c"
	conf.Conflist = append(conf.Conflist, data)
	for i := 0; i < b.N; i++ {
		conf.unique("a", "b")
	}
}
  • 压测结果:

在这里插入图片描述

四、简单使用案例:

我们选择使用课件案例的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
  • 首先定义接口函数,来选择调用监听文件还是配置项section

    var listen Listener = Filelisten//or Optionlisten

  • 选择调用watch还是watchwithoption函数我们这里选用文件监听

    a, err := Watch(infile, listen)//文件监听
    a, err := Watch(infile, listen, sectionname)//选用监听section

  • 输出读取到的更改后的配置信息或者自定义错误:

    if err != nil {
    fmt.Println(err.Error())
    } else {
    fmt.Println(a)
    }

  • 完整简单运行代码:

func main() {
	var listen ListenFunc = Filelisten //or Optionlisten
	infile := "config2.ini"
	a, err := Watch(infile, listen)// WatchWithOption(infile, listen, "section")
	if err != nil {
		fmt.Println(err.Error())
	} else {
		fmt.Println(a)
	}
}

运行结果:(我们在配置文件的http后加一个9,保存后watch即可捕捉到变更

在这里插入图片描述

两种测例均放在watch2.go文件的最后,已注释)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
纯cini配置文件 用c/c++ini配置文件有不少第三方的开源库,如iniparser、libini、rwini、UltraLightINIParser等,但都不理想,往往代码较大、功能较弱、 接口使用不方便。尤其在大小写处理、前后空格、各种注释、跨平台换行符支持、带引号字符串处理、无section操作、原格式保持等方面存在问题。 现将本人精心制作的ini写程序源码奉献给大家,纯c编写,简洁好用。支持windows和linux。 主要特点: 1、支持;和#注释符号,支持行尾注释。 2、支持带引号'或"成对匹配的字符串,提取时自动去引号。引号中可带其它引号或;#注释符。 3、支持无section或空section(名称为空)。 4、支持10、16、8进制数,0x开头为16进制数,0开头为8进制。 5、支持section、key或=号前后带空格。 6、支持\n、\r、\r\n或\n\r换行格式。 7、不区分section、key大小写,但写入时以新串为准,并保持其大小写。 8、新增数据时,若section存在则在该节最后一个有效数据后添加,否则在文件尾部添加。 9、支持指定key所在整行删除,即删除该键值,包括注释。 10、可自动跳过格式错误行,修改时仍然保留。 11、修改时保留原注释:包括整行注释、行尾注释(包括前面空格)。 12、修改时保留原空行。以上三点主要是尽量保留原格式。 不足之处: 1、不支持单key多value(逗号分割),只能一次性提取后自行处理。 2、不支持同名重复section和key。(重复section可视为错误,重复key则可能造成分歧) 3、不能提取所有section或key名称。 使用只需两个文件inirw.h、inirw.c,另有测试程序和工程文件,支持windows和linux。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值