fix: quote wildcard array items in global extension script output (#7414)

This commit is contained in:
StarsSail
2026-07-04 18:20:37 +00:00
committed by GitHub
parent 49ea0cd5df
commit 3da6d7e466
6 changed files with 114 additions and 9 deletions
+2 -3
View File
@@ -1,6 +1,6 @@
use super::CmdResult;
use crate::feat;
use crate::utils::dirs;
use crate::utils::{dirs, yaml_emitter};
use crate::{
cmd::StringifyErr as _,
config::{ClashInfo, Config},
@@ -136,14 +136,13 @@ pub async fn test_delay(url: String) -> CmdResult<u32> {
#[tauri::command]
pub async fn save_dns_config(dns_config: Mapping) -> CmdResult {
use crate::utils::dirs;
use serde_yaml_ng;
use tokio::fs;
// 获取DNS配置文件路径
let dns_path = dirs::app_home_dir().stringify_err()?.join(constants::files::DNS_CONFIG);
// 保存DNS配置到文件
let yaml_str = serde_yaml_ng::to_string(&dns_config).stringify_err()?;
let yaml_str = yaml_emitter::to_mihomo_config_string(&dns_config).stringify_err()?;
fs::write(&dns_path, yaml_str).await.stringify_err()?;
logging!(info, Type::Config, "DNS config saved to {dns_path:?}");
+3 -3
View File
@@ -1,5 +1,5 @@
use super::CmdResult;
use crate::{cmd::StringifyErr as _, config::Config, core::CoreManager};
use crate::{cmd::StringifyErr as _, config::Config, core::CoreManager, utils::yaml_emitter};
use anyhow::{Context as _, anyhow};
use clash_verge_logging::{Type, logging};
use serde_yaml_ng::Mapping;
@@ -22,7 +22,7 @@ pub async fn get_runtime_yaml() -> CmdResult<String> {
config
.ok_or_else(|| anyhow!("failed to parse config to yaml file"))
.and_then(|config| {
serde_yaml_ng::to_string(config)
yaml_emitter::to_mihomo_config_string(config)
.context("failed to convert config to yaml")
.map(|s| s.into())
})
@@ -82,7 +82,7 @@ pub async fn get_runtime_proxy_chain_config(proxy_chain_exit_node: String) -> Cm
config.insert("proxies".into(), proxies_chain);
serde_yaml_ng::to_string(&config)
yaml_emitter::to_mihomo_config_string(&config)
.context("YAML generation failed")
.map(|s| s.into())
.stringify_err()
+2 -1
View File
@@ -1,4 +1,5 @@
use crate::constants::files::DNS_CONFIG;
use crate::utils::yaml_emitter;
use crate::{config::Config, process::AsyncHandler, utils::dirs};
use anyhow::Error;
use arc_swap::{ArcSwap, ArcSwapOption};
@@ -287,7 +288,7 @@ pub async fn create_backup() -> Result<(String, PathBuf), Error> {
obj.remove("webdav_url");
}
zip.start_file(dirs::VERGE_CONFIG, options)?;
zip.write_all(serde_yaml_ng::to_string(&verge_config)?.as_bytes())?;
zip.write_all(yaml_emitter::to_mihomo_config_string(&verge_config)?.as_bytes())?;
let dns_config_path = dirs::app_home_dir()?.join(DNS_CONFIG);
if dns_config_path.exists() {
+2 -2
View File
@@ -1,4 +1,4 @@
use crate::{config::with_encryption, enhance::seq::SeqMap};
use crate::{config::with_encryption, enhance::seq::SeqMap, utils::yaml_emitter};
use anyhow::{Context as _, Result, anyhow, bail};
use clash_verge_logging::{Type, logging};
use nanoid::nanoid;
@@ -59,7 +59,7 @@ pub async fn read_seq_map(path: &PathBuf) -> Result<SeqMap> {
/// 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 = with_encryption(|| async { serde_yaml_ng::to_string(data) }).await?;
let data_str = with_encryption(|| async { yaml_emitter::to_mihomo_config_string(data) }).await?;
let yaml_str = match prefix {
Some(prefix) => format!("{prefix}\n\n{data_str}"),
+1
View File
@@ -17,3 +17,4 @@ pub mod tmpl;
#[cfg(target_os = "macos")]
pub mod tray_speed;
pub mod window_manager;
pub mod yaml_emitter;
+104
View File
@@ -0,0 +1,104 @@
use serde::{Serialize, ser};
use serde_yaml_ng::Result;
pub fn to_mihomo_config_string<T: Serialize>(data: &T) -> Result<String> {
let value = serde_yaml_ng::to_value(data)?;
Ok(emit_value(&value, 0, &DEFAULT_OPTIONS)? + "\n")
}
#[derive(Clone)]
struct Options {
checkout_wildcard: bool,
}
const DEFAULT_OPTIONS: Options = Options {
checkout_wildcard: false,
};
fn serde_yaml_ng_to_string_without_last<T: ?Sized + ser::Serialize>(value: &T) -> Result<String> {
let value = serde_yaml_ng::to_string(value)?;
if value.ends_with('\n') {
Ok(value[..value.len() - 1].to_string())
} else {
Ok(value)
}
}
fn emit_value(value: &serde_yaml_ng::Value, depth: usize, options: &Options) -> Result<String> {
match value {
serde_yaml_ng::Value::Null => serde_yaml_ng_to_string_without_last(&value),
serde_yaml_ng::Value::Bool(b) => serde_yaml_ng_to_string_without_last(&b),
serde_yaml_ng::Value::Number(number) => serde_yaml_ng_to_string_without_last(&number),
serde_yaml_ng::Value::String(s) => emit_string(s, options),
serde_yaml_ng::Value::Sequence(values) => {
if values.is_empty() {
return Ok("[]".to_string());
}
let mut result = String::new();
let mut first = true;
for value in values {
if !first || depth > 0 {
result.push('\n');
}
first = false;
result.push_str(&" ".repeat(depth));
result.push_str("- ");
result.push_str(&emit_value(value, depth + 1, options)?);
}
Ok(result)
}
serde_yaml_ng::Value::Mapping(mapping) => {
if mapping.is_empty() {
return Ok("{}".to_string());
}
let mut result = String::new();
let mut first = true;
for (key, value) in mapping {
if !first || depth > 0 {
result.push('\n');
}
first = false;
result.push_str(&" ".repeat(depth));
let new_key = emit_value(key, depth + 1, options)?;
result.push_str(&new_key);
result.push_str(": ");
let mut value_emiter_options = options.clone();
if need_checkout_wildcard(key) {
value_emiter_options.checkout_wildcard = true;
}
result.push_str(&emit_value(value, depth + 1, &value_emiter_options)?);
}
Ok(result)
}
serde_yaml_ng::Value::Tagged(tagged_value) => {
let mut result = String::new();
result.push_str(&format!("!{} ", tagged_value.tag));
result.push_str(&emit_value(&tagged_value.value, depth, options)?);
Ok(result)
}
}
}
fn emit_string(s: &str, options: &Options) -> Result<String> {
let mut s = serde_yaml_ng_to_string_without_last(s)?;
if !s.starts_with('\"') && !s.starts_with('\'') && options.checkout_wildcard && contains_wildcard(&s) {
s = quote_string(&s)
}
Ok(s)
}
fn quote_string(s: &str) -> String {
"\'".to_string() + s.replace('\'', "''").as_str() + "\'"
}
fn need_checkout_wildcard(key: &serde_yaml_ng::Value) -> bool {
if let serde_yaml_ng::Value::String(key_str) = key {
key_str == "fake-ip-filter"
} else {
false
}
}
fn contains_wildcard(value: &str) -> bool {
value.contains('*') || value.starts_with('+') || value.starts_with('.')
}