TOML v1.0.0

全称:Tom 的(语义)明显、(配置)最小化的语言。(Tom’s Obvious, Minimal Language)
作者:Tom Preston-Werner、Pradyun Gedam 等人。

宗旨:

  • TOML 旨在成为一个语义明显且易于阅读的最小化配置文件格式。
  • TOML 被设计成可以无歧义地映射为哈希表。
  • TOML 应该能很容易地被解析成各种语言中的数据结构。

详细介绍参见:TOML: 简体中文 v1.0.0

安装依赖

cargo add toml #toml文件解析crate
cargo add serde #toml crate的依赖
cargo add chrono #用于解析配置文件中的时间

定义配置结构体

配置文件内容如下

[Boss."进化巨型丛林地虫"]
position = "None"
name = "进化巨型丛林地虫"
refresh-time-list = [00:00:00,04:00:00,08:00:00,12:00:00,16:00:00,20:00:00]

[Boss."莫迪尔沃尔格斯"]
position = "None"
name = "莫迪尔沃尔格斯"
refresh-time-list = [00:30:00,03:30:00,06:30:00,09:30:00,12:30:00,15:30:30,18:30:00,21:30:00]

根据配置文件的结构定义一个结构体

pub struct Boss {
    pub name: String,
    pub position: Option<String>,
    pub refresh_time_list: Vec<base::value::Datetime>,
}

解析配置文件

贴代码参考

use std::{env, fs};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use base::Value;
use crate::boss::Boss;
use crate::error::ConfigError;

type Result<T> = std::result::Result<T, ConfigError>;

const BOSS_CONFIG_FILE: &str = "Boss.toml";

pub struct ConfigHandler {}

impl ConfigHandler {
    /// 解析配置文件

    pub fn parse_config() -> Result<HashMap<String,Boss>> {
        let file = Self::get_config_file()?;
        let content = Self::read_from(file)?;
        let parse_result = content.parse::<Value>().map_err(|_| ConfigError::BindConfigError)?;
        let count_and_keys = Self::get_config_count("Boss".to_string(), &parse_result)?;
        let mut config = HashMap::new();
        for key in count_and_keys.1.iter() {
            if !config.contains_key(key) {
                if let Ok(boss) = Self::bind_config(&parse_result,key.to_string()){
                    config.insert(key.to_string(),boss);
                }
            }
        }
        Ok(config)
    }

    /// 获取配置文件的root节点下的子节点及节点名称,便于后期配置文件子节点修改时能同步更新
    ///
    /// # Arguments
    ///
    /// * `root` 将root设置为要计算的根节点
    /// * `parsed_content` 已解析的Value配置树
    pub fn get_config_count(root: String, parsed_content: &Value) -> Result<(usize, Vec<String>)> {
        let mut res = (0, vec![]);
        if let Some(t) = parsed_content[root].as_table() {
            for key in t.keys() {
                res.1.insert(res.0, key.into());
                res.0 += 1;
            }
        } else {
            return Err(ConfigError::BindConfigError);
        }
        Ok(res)
    }

    /// 读取配置文件内容
    pub fn read_from<P: AsRef<Path>>(path: P) -> Result<String> {
        let config_file = path.as_ref();
        let content = fs::read_to_string(config_file).map_err(|_| ConfigError::IOError)?;
        Ok(content)
    }

    /// 获取配置文件路径,从执行路径向上查找
    pub fn get_config_file() -> Result<PathBuf> {
        let mut current_path = env::current_dir().map_err(|_| ConfigError::NotFound)?;
        loop {
            let file = current_path.join(BOSS_CONFIG_FILE);
            if fs::metadata(&file).is_ok() {
                return Ok(file);
            } else {
                match current_path.parent() {
                    Some(cwd) => {
                        current_path = cwd.to_path_buf()
                    }
                    None => break
                }
            }
        }
        Err(ConfigError::NotFound)
    }

    /// 将解析的Table对象绑定为配置对象
    ///
    /// # Arguments
    ///
    /// * `parsed_content` - 已解析的Value对象
    /// * `key` - 具名Boss的节点名称
    pub fn bind_config(parsed_content: &Value,key:String) -> Result<Boss> {
        let mut boss = Boss::new();
        if let Some(current_table) = parsed_content["Boss"][key].as_table(){
            if  let Some(name) = current_table["name"].as_str(){
                boss.name = name.to_string();
            }
            if let Some(position) = current_table["position"].as_str(){
                if position != "None"{
                    boss.position = Some(position.to_string());
                }
            }

            if let Some(refresh_time_list) = current_table["refresh-time-list"].as_array(){
                let mut index = 0;
                for val in refresh_time_list{
                    if let Some(datetime) = val.as_datetime(){
                        boss.refresh_time_list.insert(index, datetime.clone());
                        index += 1;
                    }
                }
            }
        }
        Ok(boss)
    }
}

小结

  • toml文件的解析结果其实就是一棵树

  • 根据节点的类型我们需要做相应的转换

  • toml会将配置文件解析为一个Value枚举,然后根据配置节点实际情况做相应转换

    pub enum Value {
        String(String),
        Integer(i64),
        Float(f64),
        Boolean(bool),
        Datetime(Datetime),
        Array(Array),
        Table(Table),
    }