common_uu 1.9.4

公共工具库
Documentation
use crate::{IResult, JsonV, JsonVExentd};
use std::fs::File;
use std::io::Read;

// use hotwatch::{Event, Hotwatch};
use serde_json::json;
use std::ops::{Deref, DerefMut};
use std::path::Path;
use std::sync::RwLock;

pub type JsonMap = serde_json::Map<String, JsonV>;

/// 配置文件保存
pub static _CONFIG: once_cell::sync::Lazy<RwLock<JsonMap>> = once_cell::sync::Lazy::new(|| RwLock::new(JsonMap::new()));

/// 获得配置
#[allow(dead_code)]
pub(crate) fn get_config_all() -> JsonV {
    _CONFIG.read().map(|v| serde_json::json!(v.deref().clone())).unwrap_or_else(|e| {
        eprintln!("CONFIG.read: {:?}", e);
        JsonV::Null
    })
}

/// 获得配置,使用方式传参key: "api.port"
#[allow(dead_code)]
pub(crate) fn get_config_keys(keys: &str) -> JsonV {
    get_config(keys.split(".").collect::<Vec<&str>>())
}

/// 获得配置,使用方式传参key: vec!["api","port"]
pub fn get_config(keys: Vec<&str>) -> JsonV {
    _CONFIG
        .read()
        .map(|v| {
            let v = v.deref();
            let mut r = JsonV::Null;
            for i in 0..keys.len() {
                let k = keys[i];
                if i == 0 {
                    r = serde_json::json!(v)[k].clone();
                } else {
                    r = serde_json::json!(r)[k].to_owned();
                }
            }
            r
        })
        .unwrap_or_else(|e| {
            eprintln!("CONFIG.read: {:?}", e);
            JsonV::Null
        })
}


/// 监听配置文件修改进行实时读取
///     let ref toml_file_path_str = format!("config-{}.toml", crate::log4rs_mod::EXE_FILE_NAME.to_string());

pub fn run_watch(toml_file_path_str: &str, map: &mut Option<JsonMap>) -> IResult<crossbeam::channel::Receiver<String>> {
    load_file(toml_file_path_str)?;

    let (send, rev) = crossbeam::channel::bounded(10);

    let toml_file_path = toml_file_path_str.to_string();

    /** 
    let mut hotwatch = Hotwatch::new().expect("Hotwatch failed to initialize.");
    hotwatch.watch(toml_file_path, move |event: Event| {
        if let Event::Write(path) = event {
            let path = path.to_str().unwrap_or_else(|| "");
            if let Err(e) = load_file(path) {
                error!("file reload error: {:?}", e);
            }else{
                info!("file reload: {}", path);
            _ = send.try_send(path.to_string());
            }
            
        }
    })?;
    */

    {
        // 监听的配置文件
        let file_name = get_file_name(toml_file_path_str)?;

        let write_data = |map: &mut JsonMap| {
            let k = "_watch_mode".to_owned();
            if let Some(JsonV::String(v)) = map.get_mut(&k) {
                v.push_str(&format!("{},", file_name));
            } else {
                map.insert(k, json!(file_name));
            }
        };

        if let Some(v) = map{
            write_data(v);
        }else if let Ok(lock) = _CONFIG.write().as_deref_mut() {
            write_data(lock);
        }
    }

    Ok(rev)
}

#[test]
fn test_read_file() {
    let mut b = vec![];
    File::open(file!()).unwrap().read_to_end(&mut b).unwrap();
    let s = String::from_utf8(b).unwrap();
    println!("{}", s);
}

/// 读取配置文件: config.toml
/// F:  std::env::set_var(k, v); |k, v| std::env::set_var(k, v)
fn read_file(toml_file_path: &str) -> IResult<String> {
    let mut file_temp = match File::open(&toml_file_path) {
        Ok(v) => v,
        Err(e) => {
            let msg = format!("打开[{}]文件失败: {:?}", &toml_file_path, e);
            Err(msg)?
        }
    };

    let mut b = vec![];
    let _size = match file_temp.read_to_end(&mut b) {
        Err(e) => {
            let msg = format!("read_to_string[{}]文件失败: {:?}", &toml_file_path, e);
            Err(msg)?
        }
        Ok(_v) => _v,
    };

    let toml_str = match String::from_utf8(b) {
        Err(e) => {
            let msg = format!("解析toml文件失败: {:?}", e);
            Err(msg)?
        }
        Ok(v) => v,
    };

    Ok(toml_str)
}

pub fn get_file_name(path: &str) -> IResult<String> {
    let r = Path::new(path)
        .file_name()
        .and_then(|v| v.to_str().map(|v| v.to_string()))
        .ok_or_else(|| format!("[{}]路径中不存在配置文件名", path))?
        .trim_end_matches(".toml")
        .trim_end_matches("config")
        .trim_end_matches('_')
        .trim_start_matches("config")
        .trim_start_matches("_")
        .to_string();
    Ok(r)
}

pub fn load_file(path: &str) -> IResult<()> {
    let file_name = get_file_name(path)?;
    let str_v = read_file(path)?;
    load_str(file_name, str_v)
}

pub fn load_str(file_name: String, toml_str: String) -> IResult<()> {
    let value: toml::Value = toml::from_str(&toml_str).map_err(|e| format!("解析toml字符串失败: {:?}", e))?;

    let data = serde_json::json!(value).as_object2().ok_or_else(|| "配置文件格式不是一个键值对对象")?;

    if let Ok(mut lock) = _CONFIG.write() {
        if lock.deref().get("mode").is_none() {
            let mode = data
                .get("mode")
                .map(|v| v.clone().as_string2())
                .ok_or_else(|| "读取的第一个配置文件中没有定义mode属性")?
                .ok_or_else(|| "读取的第一个配置文件中定义的mode属性不是一个字符串")?;
            lock.deref_mut().insert("mode".to_owned(), JsonV::String(mode));
        }

        if lock.deref().get("_default_name").is_none() {
            lock.deref_mut().insert("_default_name".to_owned(), JsonV::String(file_name.clone()));
        }

        // 读取后直接赋盖
        lock.deref_mut().insert(file_name, JsonV::Object(data));

        // append 追加 map 数据
        // if let Some(JsonV::Object(map)) = lock.get_mut(file_name.as_str()) {
        //     map.append(&mut data);
        // } else {
        //     lock.deref_mut().insert(file_name, JsonV::Object(data));
        // }

        debug!("config: {}", serde_json::json!(lock.deref()).to_string());
    }
    Ok(())
}

/*
/// 读取配置
/// std::env::set_var(k, v);
fn set_etax_value<F: FnMut(String, String) -> ()>(
    value: &toml::Value,
    k: &str,
    callback: &mut F,
) -> HashMap<String, String> {
    let mut map = HashMap::new();
    if let Some(v) = value.as_str() {
        // println!("Read Config File, set_var: {}={}", &k, v);
        callback(k.to_string(), v.to_string());
        map.insert(k.to_string(), v.to_string());
    } else if let Some(v) = value.as_integer() {
        // println!("Read Config File, set_var: {}={}", &k, v);
        callback(k.to_string(), v.to_string());
        map.insert(k.to_string(), v.to_string());
    } else if let Some(v) = value.as_float() {
        // println!("Read Config File, set_var: {}={}", &k, v);
        callback(k.to_string(), v.to_string());
        map.insert(k.to_string(), v.to_string());
    } else if let Some(v) = value.as_bool() {
        // println!("Read Config File, set_var: {}={}", &k, v);
        callback(k.to_string(), v.to_string());
        map.insert(k.to_string(), v.to_string());
    } else if let Some(arr) = value.as_array() {
        for i in 0..arr.len() {
            let v = &arr[i];
            let k = match k {
                "" => i.to_string(),
                _ => format!("{}[{}]", k, i),
            };
            let m = set_etax_value(v, &k, callback);
            map.extend(m);
        }
    } else if let Some(table) = value.as_table() {
        for (k1, value) in table.iter() {
            let k3 = match k {
                "" => k1.to_string(),
                _ => format!("{}.{}", k, k1),
            };
            let m = set_etax_value(value, &k3, callback);
            map.extend(m);
        }
    };
    map
}*/

/* 
#[test]
fn test_read() {
    use crate::arg::get_arg;
    load_str("dev".to_owned(), include_str!("../config/default.toml").to_string()).unwrap();
    let _watch = run_watch("./config/prod.toml");

    let config = get_arg("mail.enable");
    println!("get_arg mail.enable: {:?}", config);

    std::thread::sleep(std::time::Duration::from_secs(2));
    let config = get_config(vec!["mail", "enable"]);
    println!("enable: {:?}", config);

    let config = get_config(vec!["log", "filters"]);
    println!("filters: {:?}", config);

    let config = get_config(vec!["feishu_url"]);
    println!("feishu_url: {:?}", config);

    let config = get_config(vec!["log"]);
    println!("log: {:?}", config);

    std::thread::sleep(std::time::Duration::from_secs(20));
}
 */

/// 功能
///  把配置文件打包到 bin 文件
///  修改一个mode参数即可切换配置文件
///  在命令行加 ./xxx.exe mode=prod 即可运行正式环境配置
///
/// 模式 1: 输入多个配置文件: load_tomls!("./config/default.toml", "./config/prod.toml");
/// 以第一个配置文件作为默认的配置, 在定义的配置文件中找不到的值, 将在此配置寻找
/// 第一个配置文件中必须有一个 mode = "xxx" 的值, 此值可以是 "prod"
///
/// 模式 2: 输入运行环境需要加载的配置, 输入多个配置文件: load_tomls!("prod"; "./config/default.toml", "./config/prod.toml");
/// 以第一个配置文件作为默认的配置, 其它文件中找不到的值, 将在此配置寻找
/// 运行环境将被设置为 prod, 在 prod 中找不到的配置, 将在第一个配置文件中寻找
///
///
#[macro_export]
macro_rules! load_tomls {

    // 加载配置文件
    ($($file:expr), + $(,)?) => {{
        $(
            let file_name = $crate::toml_read::get_file_name($file).unwrap();
            let s = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/", $file));
            $crate::toml_read::load_str(file_name, s.to_string()).map_err(|e| e.to_string()).unwrap();
        )*
    }};

    // 加载带模式的配置文件
    ($mode:expr; $($file:expr), + $(,)?) => {{
        $crate::toml_read::_CONFIG
        .write()
        .unwrap()
        .insert("mode".to_owned(), JsonV::String($mode.to_string()));
        load_tomls! ($($file),*)
    }};
}
/* 
#[test]
fn test_loadfiles() {
    println!("path: {}", concat!(env!("CARGO_MANIFEST_DIR"), file!()));

    load_tomls!("./config/default.toml", "./config/prod.toml");
    let _watch = run_watch("./config/test.toml").unwrap();
    println!("config: {:?}", _CONFIG.read().unwrap().deref());
    let r = crate::get_arg("log.filters");
    println!("loadfiles!: {:?}", r);
    let r = crate::get_arg("log.level");
    println!("loadfiles!: {:?}", r);

    let r = crate::get_arg("read_value");
    println!("read_value: {:?}", r);
} */

/*
#[test]
fn test_dir() {
    let dir = std::fs::read_dir("./").unwrap();
    for x in dir {
        let f = x.unwrap().path();
        if !f.is_file() {
            continue;
        }
        let file_name = f
            .file_name()
            .map(|v| v.to_str().map(|v| v.to_string()))
            .unwrap()
            .unwrap();
        if !file_name.to_lowercase().ends_with(".toml") {
            continue;
        }
        let file_name = file_name.split(".").collect::<Vec<&str>>()[0].to_string();
        if file_name == "" {
            continue;
        }
        let abs_path = f
            .canonicalize()
            .map(|v| v.to_str().map(|v| v.to_string()))
            .unwrap()
            .unwrap();

        let s = format_args!("{}", abs_path);
        read_str(s.to_string());

        println!("{}, {}", file_name, abs_path);
        // let abs_path = include_str!(abs_path);
        let str = "";
    }
}
*/