mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
In Rust, the `or` and `or_else` methods have distinct behavioral differences. The `or` method always eagerly evaluates its argument and executes any associated function calls. This can lead to unnecessary performance costs—especially in expensive operations like string processing or file handling—and may even trigger unintended side effects. In contrast, `or_else` evaluates its closure lazily, only when necessary. Introducing a Clippy lint to disallow `or` sacrifices a bit of code simplicity but ensures predictable behavior and enforces lazy evaluation for better performance.
159 lines
4.7 KiB
Rust
159 lines
4.7 KiB
Rust
use crate::{enhance::seq::SeqMap, logging, utils::logging::Type};
|
|
use anyhow::{Context, Result, anyhow, bail};
|
|
use nanoid::nanoid;
|
|
use serde::{Serialize, de::DeserializeOwned};
|
|
use serde_yaml_ng::Mapping;
|
|
use std::{path::PathBuf, str::FromStr};
|
|
|
|
/// read data from yaml as struct T
|
|
pub async fn read_yaml<T: DeserializeOwned>(path: &PathBuf) -> Result<T> {
|
|
if !tokio::fs::try_exists(path).await.unwrap_or(false) {
|
|
bail!("file not found \"{}\"", path.display());
|
|
}
|
|
|
|
let yaml_str = tokio::fs::read_to_string(path).await?;
|
|
|
|
Ok(serde_yaml_ng::from_str::<T>(&yaml_str)?)
|
|
}
|
|
|
|
/// read mapping from yaml
|
|
pub async fn read_mapping(path: &PathBuf) -> Result<Mapping> {
|
|
if !tokio::fs::try_exists(path).await.unwrap_or(false) {
|
|
bail!("file not found \"{}\"", path.display());
|
|
}
|
|
|
|
let yaml_str = tokio::fs::read_to_string(path)
|
|
.await
|
|
.with_context(|| format!("failed to read the file \"{}\"", path.display()))?;
|
|
|
|
// YAML语法检查
|
|
match serde_yaml_ng::from_str::<serde_yaml_ng::Value>(&yaml_str) {
|
|
Ok(mut val) => {
|
|
val.apply_merge()
|
|
.with_context(|| format!("failed to apply merge \"{}\"", path.display()))?;
|
|
|
|
Ok(val
|
|
.as_mapping()
|
|
.ok_or_else(|| {
|
|
anyhow!("failed to transform to yaml mapping \"{}\"", path.display())
|
|
})?
|
|
.to_owned())
|
|
}
|
|
Err(err) => {
|
|
let error_msg = format!("YAML syntax error in {}: {}", path.display(), err);
|
|
logging!(error, Type::Config, "{}", error_msg);
|
|
|
|
crate::core::handle::Handle::notice_message(
|
|
"config_validate::yaml_syntax_error",
|
|
&error_msg,
|
|
);
|
|
|
|
bail!("YAML syntax error: {}", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// read mapping from yaml fix #165
|
|
pub async fn read_seq_map(path: &PathBuf) -> Result<SeqMap> {
|
|
read_yaml(path).await
|
|
}
|
|
|
|
/// save the data to the file
|
|
/// can set `prefix` string to add some comments
|
|
pub async fn save_yaml<T: Serialize + Sync>(
|
|
path: &PathBuf,
|
|
data: &T,
|
|
prefix: Option<&str>,
|
|
) -> Result<()> {
|
|
let data_str = serde_yaml_ng::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();
|
|
tokio::fs::write(path, yaml_str.as_bytes())
|
|
.await
|
|
.with_context(|| format!("failed to save file \"{path_str}\""))
|
|
}
|
|
|
|
const ALPHABET: [char; 62] = [
|
|
'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',
|
|
];
|
|
|
|
/// generate the uid
|
|
pub fn get_uid(prefix: &str) -> String {
|
|
let id = nanoid!(11, &ALPHABET);
|
|
format!("{prefix}{id}")
|
|
}
|
|
|
|
/// parse the string
|
|
/// xxx=123123; => 123123
|
|
pub fn parse_str<T: FromStr>(target: &str, key: &str) -> Option<T> {
|
|
target.split(';').map(str::trim).find_map(|s| {
|
|
let mut parts = s.splitn(2, '=');
|
|
match (parts.next(), parts.next()) {
|
|
(Some(k), Some(v)) if k == key => v.parse::<T>().ok(),
|
|
_ => None,
|
|
}
|
|
})
|
|
}
|
|
|
|
/// get the last part of the url, if not found, return empty string
|
|
pub fn get_last_part_and_decode(url: &str) -> Option<String> {
|
|
let path = url.split('?').next().unwrap_or(""); // Splits URL and takes the path part
|
|
let segments: Vec<&str> = path.split('/').collect();
|
|
let last_segment = segments.last()?;
|
|
|
|
Some(
|
|
percent_encoding::percent_decode_str(last_segment)
|
|
.decode_utf8_lossy()
|
|
.to_string(),
|
|
)
|
|
}
|
|
|
|
/// open file
|
|
pub fn open_file(path: PathBuf) -> Result<()> {
|
|
open::that_detached(path.as_os_str())?;
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
pub fn linux_elevator() -> String {
|
|
use std::process::Command;
|
|
match Command::new("which").arg("pkexec").output() {
|
|
Ok(output) => {
|
|
if !output.stdout.is_empty() {
|
|
// Convert the output to a string slice
|
|
if let Ok(path) = std::str::from_utf8(&output.stdout) {
|
|
path.trim().to_string()
|
|
} else {
|
|
"sudo".to_string()
|
|
}
|
|
} else {
|
|
"sudo".to_string()
|
|
}
|
|
}
|
|
Err(_) => "sudo".to_string(),
|
|
}
|
|
}
|
|
|
|
/// return the string literal error
|
|
#[macro_export]
|
|
macro_rules! ret_err {
|
|
($str: expr) => {
|
|
return Err($str.into())
|
|
};
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! t {
|
|
($en:expr, $zh:expr, $use_zh:expr) => {
|
|
if $use_zh { $zh } else { $en }
|
|
};
|
|
}
|