Skip to content
Snippets Groups Projects
help.rs 4.8 KiB
Newer Older
GyDi's avatar
GyDi committed
use anyhow::{anyhow, bail, Context, Result};
GyDi's avatar
GyDi committed
use nanoid::nanoid;
GyDi's avatar
GyDi committed
use serde::{de::DeserializeOwned, Serialize};
use serde_yaml::{Mapping, Value};
GyDi's avatar
GyDi committed
use std::{fs, path::PathBuf, str::FromStr};
GyDi's avatar
GyDi committed

/// read data from yaml as struct T
pub fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
    if !path.exists() {
        bail!("file not found \"{}\"", path.display());
    }

    let yaml_str = fs::read_to_string(&path)
        .with_context(|| format!("failed to read the file \"{}\"", path.display()))?;
GyDi's avatar
GyDi committed

    serde_yaml::from_str::<T>(&yaml_str).with_context(|| {
        format!(
            "failed to read the file with yaml format \"{}\"",
            path.display()
        )
    })
GyDi's avatar
GyDi committed
}

/// read mapping from yaml fix #165
pub fn read_merge_mapping(path: &PathBuf) -> Result<Mapping> {
    let mut val: Value = read_yaml(path)?;
    val.apply_merge()
        .with_context(|| format!("failed to apply merge \"{}\"", path.display()))?;
GyDi's avatar
GyDi committed

    Ok(val
        .as_mapping()
        .ok_or(anyhow!(
            "failed to transform to yaml mapping \"{}\"",
            path.display()
        ))?
        .to_owned())
}

/// save the data to the file
/// can set `prefix` string to add some comments
pub fn save_yaml<T: Serialize>(path: &PathBuf, data: &T, prefix: Option<&str>) -> Result<()> {
    let data_str = serde_yaml::to_string(data)?;

    let yaml_str = match prefix {
        Some(prefix) => format!("{prefix}\n\n{data_str}"),
        None => data_str,
    };

    let path_str = path.as_os_str().to_string_lossy().to_string();
    fs::write(path, yaml_str.as_bytes())
        .with_context(|| format!("failed to save file \"{path_str}\""))
GyDi's avatar
GyDi committed
}
GyDi's avatar
GyDi committed

GyDi's avatar
GyDi committed
const ALPHABET: [char; 62] = [
GyDi's avatar
GyDi committed
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
    'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B',
    'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U',
    'V', 'W', 'X', 'Y', 'Z',
GyDi's avatar
GyDi committed
];

GyDi's avatar
GyDi committed
/// generate the uid
pub fn get_uid(prefix: &str) -> String {
GyDi's avatar
GyDi committed
    let id = nanoid!(11, &ALPHABET);
    format!("{prefix}{id}")
GyDi's avatar
GyDi committed
}

/// parse the string
/// xxx=123123; => 123123
pub fn parse_str<T: FromStr>(target: &str, key: &str) -> Option<T> {
GyDi's avatar
GyDi committed
    target.find(key).and_then(|idx| {
        let idx = idx + key.len();
        let value = &target[idx..];

        match value.split(';').nth(0) {
            Some(value) => value.trim().parse(),
            None => value.trim().parse(),
        }
        .ok()
    })
GyDi's avatar
GyDi committed
}

GyDi's avatar
GyDi committed
/// open file
/// use vscode by default
pub fn open_file(path: PathBuf) -> Result<()> {
GyDi's avatar
GyDi committed
    #[cfg(target_os = "macos")]
    let code = "Visual Studio Code";
    #[cfg(not(target_os = "macos"))]
    let code = "code";
GyDi's avatar
GyDi committed

GyDi's avatar
GyDi committed
    // use vscode first
    if let Err(err) = open::with(&path, code) {
        log::error!(target: "app", "failed to open file with VScode `{err}`");
        // default open
        open::that(path)?;
GyDi's avatar
GyDi committed
    }

GyDi's avatar
GyDi committed
    Ok(())
GyDi's avatar
GyDi committed
}

GyDi's avatar
GyDi committed
#[macro_export]
macro_rules! error {
    ($result: expr) => {
        log::error!(target: "app", "{}", $result);
    };
}

#[macro_export]
macro_rules! log_err {
    ($result: expr) => {
        if let Err(err) = $result {
            log::error!(target: "app", "{err}");
        }
    };

    ($result: expr, $err_str: expr) => {
        if let Err(_) = $result {
            log::error!(target: "app", "{}", $err_str);
        }
    };
GyDi's avatar
GyDi committed
#[macro_export]
macro_rules! trace_err {
    ($result: expr, $err_str: expr) => {
        if let Err(err) = $result {
            log::trace!(target: "app", "{}, err {}", $err_str, err);
        }
    }
}

GyDi's avatar
GyDi committed
/// wrap the anyhow error
/// transform the error to String
#[macro_export]
macro_rules! wrap_err {
GyDi's avatar
GyDi committed
    ($stat: expr) => {
        match $stat {
            Ok(a) => Ok(a),
            Err(err) => {
                log::error!(target: "app", "{}", err.to_string());
                Err(format!("{}", err.to_string()))
            }
        }
    };
GyDi's avatar
GyDi committed
}

/// return the string literal error
#[macro_export]
macro_rules! ret_err {
GyDi's avatar
GyDi committed
    ($str: expr) => {
        return Err($str.into())
    };
GyDi's avatar
GyDi committed
}

#[test]
fn test_parse_value() {
GyDi's avatar
GyDi committed
    let test_1 = "upload=111; download=2222; total=3333; expire=444";
    let test_2 = "attachment; filename=Clash.yaml";

    assert_eq!(parse_str::<usize>(test_1, "upload=").unwrap(), 111);
    assert_eq!(parse_str::<usize>(test_1, "download=").unwrap(), 2222);
    assert_eq!(parse_str::<usize>(test_1, "total=").unwrap(), 3333);
    assert_eq!(parse_str::<usize>(test_1, "expire=").unwrap(), 444);
    assert_eq!(
        parse_str::<String>(test_2, "filename=").unwrap(),
        format!("Clash.yaml")
    );

    assert_eq!(parse_str::<usize>(test_1, "aaa="), None);
    assert_eq!(parse_str::<usize>(test_1, "upload1="), None);
    assert_eq!(parse_str::<usize>(test_1, "expire1="), None);
    assert_eq!(parse_str::<usize>(test_2, "attachment="), None);
GyDi's avatar
GyDi committed
}