Wave 3: 补齐九个适配器(Qwen/CodeBuddy/Cline/Copilot/Crush/Goose/Aider/Cursor/Warp)

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
leefer
2026-08-25 19:13:37 +08:00
co-authored by multica-agent
parent 4a2be5899d
commit b0d7124170
13 changed files with 1345 additions and 193 deletions
+77 -1
View File
@@ -14,9 +14,10 @@ pub use error::ConfigError;
use std::path::PathBuf;
/// 解析适配器声明的配置文件路径:展开 `~`(用户主目录),
/// 以及平台变量(Windows `%VAR%` / `${VAR}`,如 `%APPDATA%`)。
/// 并把 Windows 路径分隔符统一处理(YAML 里写的是 `~/.codex/...`)。
pub fn resolve_path(raw: &str) -> PathBuf {
let expanded = expand_home(raw);
let expanded = expand_env_vars(&expand_home(raw));
PathBuf::from(expanded)
}
@@ -34,6 +35,65 @@ pub fn expand_home(raw: &str) -> String {
raw.to_string()
}
/// 展开平台环境变量:`%VAR%`Windows)与 `${VAR}`。
/// 未定义或非法变量名保持原样,不做静默吞并。
pub fn expand_env_vars(raw: &str) -> String {
// 先用 ${VAR} 处理(避免与 %VAR% 互相干扰)
let mut out = String::with_capacity(raw.len());
let bytes: Vec<char> = raw.chars().collect();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == '%' {
if let Some((name, end)) = take_until(&bytes, i + 1, '%') {
if is_valid_var_name(&name) {
if let Ok(v) = std::env::var(&name) {
out.push_str(&v);
i = end + 1;
continue;
}
}
}
}
if bytes[i] == '$' && i + 1 < bytes.len() && bytes[i + 1] == '{' {
if let Some((name, end)) = take_until(&bytes, i + 2, '}') {
if is_valid_var_name(&name) {
if let Ok(v) = std::env::var(&name) {
out.push_str(&v);
i = end + 1;
continue;
}
}
}
}
out.push(bytes[i]);
i += 1;
}
out
}
/// 从 `from` 开始找 `close`,返回 (变量名, close 下标)。找不到返回 None。
fn take_until(chars: &[char], from: usize, close: char) -> Option<(String, usize)> {
let mut end = from;
while end < chars.len() {
if chars[end] == close {
let name: String = chars[from..end].iter().collect();
return Some((name, end));
}
end += 1;
}
None
}
/// 环境变量名合法性:`[A-Za-z_][A-Za-z0-9_]*`(与常见 shell/OS 约定一致)。
fn is_valid_var_name(name: &str) -> bool {
let mut chars = name.chars();
match chars.next() {
Some(c) if c.is_ascii_alphabetic() || c == '_' => {}
_ => return false,
}
chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
}
/// 用户主目录(含路径分隔符兜底)。
pub fn home_dir() -> String {
#[cfg(windows)]
@@ -63,4 +123,20 @@ mod tests {
fn plain_path_unchanged() {
assert_eq!(expand_home("/etc/codex/config.toml"), "/etc/codex/config.toml");
}
#[test]
fn expand_env_vars_windows_and_braces() {
std::env::set_var("AGENTDOCK_TEST_VAR", "C:\\Users\\Test");
assert_eq!(
expand_env_vars("%AGENTDOCK_TEST_VAR%\\block\\goose"),
"C:\\Users\\Test\\block\\goose"
);
assert_eq!(
expand_env_vars("${AGENTDOCK_TEST_VAR}/x"),
"C:\\Users\\Test/x"
);
// 未定义变量保持原样
assert_eq!(expand_env_vars("%AGENTDOCK_NOT_SET_VAR%\\x"), "%AGENTDOCK_NOT_SET_VAR%\\x");
std::env::remove_var("AGENTDOCK_TEST_VAR");
}
}