e_utils/
parse.rs

1use std::path::Path;
2
3use crate::Result;
4use chrono::{DateTime, Datelike as _, Local, Timelike as _};
5use once_cell::sync::Lazy;
6use regex::Regex;
7
8/// 解析格式化字符串
9pub trait MyParseFormat {
10  /// 解析所有
11  fn parse_all(&self) -> Result<String>;
12  /// 解析特殊关键词
13  fn parse_format(&self) -> Result<String>;
14  /// 解析自定义关键词
15  fn parse_replace<F>(&self, start: char, end: char, match_value: F) -> Result<String>
16  where
17    F: Fn(String) -> String;
18  /// 解析含中文自定义关键词
19  fn parse_replace_zh<F>(&self, start: char, end: char, match_value: F) -> Result<String>
20  where
21    F: Fn(String) -> String;
22  /// 解析系统环境变量
23  /// 解析跨平台系统环境变量(windows、linux、mac等)
24  fn parse_env(&self) -> Result<String>;
25  /// 解析路径规范,统一'/'
26  fn parse_path(&self) -> String;
27}
28
29/// 正则表达式,用于匹配格式化字符串中的占位符
30pub static REGEX_FORMAT_PATTERN: Lazy<Regex> = Lazy::new(|| Regex::new(r"\{([a-zA-Z0-9_]+)\}").expect("Invalid regex pattern for REGEX_FORMAT_PATTERN"));
31
32#[cfg(target_os = "windows")]
33/// Windows环境下环境变量的正则表达式
34pub static REGEX_ENV_PATTERN: Lazy<Regex> =
35  Lazy::new(|| Regex::new(r"\%([a-zA-Z0-9_]+)\%").expect("Invalid regex pattern for REGEX_ENV_PATTERN on Windows"));
36
37#[cfg(any(target_os = "linux", target_os = "macos"))]
38/// Linux和macOS环境下环境变量的正则表达式
39pub static REGEX_ENV_PATTERN: Lazy<Regex> =
40  Lazy::new(|| Regex::new(r"\$\{([a-zA-Z0-9_]+)\}").expect("Invalid regex pattern for REGEX_ENV_PATTERN on Unix-like systems"));
41
42#[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
43/// 对于其他操作系统的环境变量正则表达式
44pub static REGEX_ENV_PATTERN: Lazy<Regex> =
45  Lazy::new(|| Regex::new(r"\$([a-zA-Z0-9_]+)").expect("Invalid regex pattern for REGEX_ENV_PATTERN on other systems"));
46
47impl<S: AsRef<str>> MyParseFormat for S {
48  fn parse_all(&self) -> Result<String> {
49    self.as_ref().parse_format()?.parse_path().parse_env()
50  }
51
52  fn parse_format(&self) -> Result<String> {
53    let local: DateTime<Local> = Local::now();
54    Ok(
55      REGEX_FORMAT_PATTERN
56        .replace_all(self.as_ref(), |caps: &regex::Captures<'_>| {
57          let key: String = caps.get(1).map_or("", |m| m.as_str()).to_lowercase();
58          match &*key {
59            // 获取日期
60            "date" => format!("{:04}-{:02}-{:02}", local.year(), local.month(), local.day()),
61            // 获取日期
62            "time" => format!("{:02}:{:02}:{:02}", local.hour(), local.minute(), local.second()),
63            // 获取时间戳
64            "timestamp-millis" => local.timestamp_millis().to_string(),
65            "day" => format!("{:02}", local.day()),
66            "month" => format!("{:02}", local.month()),
67            "year" => format!("{:02}", local.year()),
68            "hour" => format!("{:02}", local.hour()),
69            "minute" => format!("{:02}", local.minute()),
70            "second" => format!("{:02}", local.second()),
71            "cwd" => std::env::current_dir().and_then(|x| Ok(x.display().to_string())).unwrap_or_default(),
72            "nanoid" => {
73              #[cfg(feature = "algorithm")]
74              return crate::algorithm!(nanoid 12);
75              #[cfg(not(feature = "algorithm"))]
76              return String::new();
77            }
78            _ => String::new(),
79          }
80        })
81        .to_string(),
82    )
83  }
84
85  fn parse_env(&self) -> Result<String> {
86    Ok(
87      REGEX_ENV_PATTERN
88        .replace_all(self.as_ref(), |caps: &regex::Captures<'_>| {
89          let key = caps.get(1).map_or("", |m| m.as_str());
90          let var = std::env::var(key).unwrap_or_default();
91          var
92        })
93        .to_string(),
94    )
95  }
96
97  fn parse_path(&self) -> String {
98    self.as_ref().replace("\\\\", "/").replace("\\", "/")
99  }
100
101  fn parse_replace<F>(&self, start: char, end: char, match_callback: F) -> Result<String>
102  where
103    F: Fn(String) -> String,
104  {
105    let re = regex::Regex::new(&format!(r"\{}([a-zA-Z0-9_]+)\{}", start, end))?;
106    Ok(
107      re.replace_all(self.as_ref(), |caps: &regex::Captures<'_>| {
108        let key: String = caps.get(1).map_or("", |m| m.as_str()).to_lowercase();
109        match_callback(key)
110      })
111      .to_string(),
112    )
113  }
114  fn parse_replace_zh<F>(&self, start: char, end: char, match_callback: F) -> Result<String>
115  where
116    F: Fn(String) -> String,
117  {
118    let re = regex::Regex::new(&format!(r"\{}([\p{{Han}}a-zA-Z0-9_@$]+)\{}", start, end))?;
119    Ok(
120      re.replace_all(self.as_ref(), |caps: &regex::Captures<'_>| {
121        let key: String = caps.get(1).map_or("", |m| m.as_str()).to_string();
122        match_callback(key)
123      })
124      .to_string(),
125    )
126  }
127}
128
129impl MyParseFormat for Path {
130  fn parse_format(&self) -> Result<String> {
131    self.to_string_lossy().parse_format()
132  }
133
134  fn parse_env(&self) -> Result<String> {
135    self.to_string_lossy().parse_env()
136  }
137
138  fn parse_all(&self) -> Result<String> {
139    self.to_string_lossy().parse_all()
140  }
141
142  fn parse_path(&self) -> String {
143    self.to_string_lossy().parse_path()
144  }
145
146  fn parse_replace<F>(&self, start: char, end: char, match_value: F) -> Result<String>
147  where
148    F: Fn(String) -> String,
149  {
150    self.to_string_lossy().parse_replace(start, end, match_value)
151  }
152  fn parse_replace_zh<F>(&self, start: char, end: char, match_value: F) -> Result<String>
153  where
154    F: Fn(String) -> String,
155  {
156    self.to_string_lossy().parse_replace_zh(start, end, match_value)
157  }
158}
159
160// 在文件末尾添加以下测试模块
161
162#[cfg(test)]
163mod tests {
164  use super::*;
165  use std::env;
166
167  #[test]
168  fn test_parse_all() {
169    env::set_var("TEST_ENV", "test_value");
170    let input = "Date: {date}, Time: {time}, Env: %TEST_ENV%";
171    let result = input.parse_all().unwrap();
172    assert!(result.contains("Date: "));
173    assert!(result.contains("Time: "));
174    assert!(result.contains("Env: test_value"));
175  }
176
177  #[test]
178  fn test_parse_format() {
179    let input = "{date} {time} {day} {month} {year} {hour} {minute} {second} {cwd}";
180    let result = input.parse_format().unwrap();
181    println!("{result}");
182    assert!(!result.contains("{"));
183    assert!(!result.contains("}"));
184  }
185
186  #[test]
187  fn test_parse_replace() {
188    let input = "Hello [name] [age]";
189    let result = input
190      .parse_replace('[', ']', |key| match key.as_str() {
191        "name" => "Alice".to_string(),
192        "age" => "30".to_string(),
193        _ => key,
194      })
195      .unwrap();
196    assert_eq!(result, "Hello Alice 30");
197  }
198
199  #[test]
200  fn test_parse_env() {
201    env::set_var("TEST_VAR", "test_value");
202    #[cfg(target_os = "windows")]
203    let input = "Path: %TEST_VAR%";
204    #[cfg(any(target_os = "linux", target_os = "macos"))]
205    let input = "Path: ${TEST_VAR}";
206    #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
207    let input = "Path: $TEST_VAR";
208
209    let result = input.parse_env().unwrap();
210    assert_eq!(result, "Path: test_value");
211  }
212
213  #[test]
214  fn test_parse_path() {
215    let input = r"C:\Users\Alice\Documents";
216    let result = input.parse_path();
217    assert_eq!(result, "C:/Users/Alice/Documents");
218  }
219
220  #[test]
221  fn test_path_parse_format() {
222    let path = Path::new("/home/user/{date}");
223    let result = path.parse_format().unwrap();
224    assert!(!result.contains("{date}"));
225  }
226
227  #[test]
228  fn test_path_parse_env() {
229    env::set_var("HOME", "/home/user");
230    #[cfg(target_os = "windows")]
231    let path = Path::new("%HOME%\\documents");
232    #[cfg(any(target_os = "linux", target_os = "macos"))]
233    let path = Path::new("${HOME}/documents");
234    #[cfg(not(any(target_os = "linux", target_os = "windows", target_os = "macos")))]
235    let path = Path::new("$HOME/documents");
236
237    let result = path.parse_env().unwrap();
238    assert!(result.contains("/home/user"));
239  }
240
241  #[test]
242  fn test_path_parse_path() {
243    let path = Path::new("C:\\Users\\Alice\\Documents");
244    let result = path.parse_path();
245    assert_eq!(result, "C:/Users/Alice/Documents");
246  }
247
248  #[test]
249  fn test_path_parse_replace() {
250    let path = Path::new("/home/[user]/[folder]");
251    let result = path
252      .parse_replace('[', ']', |key| match key.as_str() {
253        "user" => "alice".to_string(),
254        "folder" => "documents".to_string(),
255        _ => key,
256      })
257      .unwrap();
258    assert_eq!(result, "/home/alice/documents");
259  }
260}