#[must_use]
pub fn format_path_file(path: &str) -> String {
let mut output = String::from("PATH!\n");
for entry in path.split(':').filter(|s| !s.is_empty()) {
output.push_str(entry);
output.push('\n');
}
output.push_str("\nENV!\n");
output
}
pub fn parse_path_file(content: &str) -> Result<String, String> {
let trimmed = content.trim();
if trimmed.is_empty() {
return Err("No PATH entries found in file".to_string());
}
if trimmed.starts_with("PATH!") {
parse_new_format(trimmed)
} else {
parse_legacy_format(trimmed)
}
}
fn parse_new_format(content: &str) -> Result<String, String> {
let mut path_entries = Vec::new();
let mut in_path_section = false;
let mut in_env_section = false;
for line in content.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
if line == "PATH!" {
in_path_section = true;
in_env_section = false;
continue;
} else if line == "ENV!" {
in_path_section = false;
in_env_section = true;
continue;
}
if in_path_section {
path_entries.push(line.to_string());
} else if in_env_section {
}
}
if path_entries.is_empty() {
return Err("No PATH entries found in file".to_string());
}
Ok(path_entries.join(":"))
}
fn parse_legacy_format(content: &str) -> Result<String, String> {
let all_lines = content.lines().map(str::trim).collect::<Vec<_>>().join("");
let entries: Vec<&str> = all_lines
.split(':')
.map(str::trim)
.filter(|s| !s.is_empty())
.collect();
if entries.is_empty() {
return Err("No PATH entries found in file".to_string());
}
Ok(entries.join(":"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_path_file() {
let path = "/usr/bin:/bin:/usr/local/bin";
let formatted = format_path_file(path);
assert!(formatted.contains("PATH!"));
assert!(formatted.contains("/usr/bin\n"));
assert!(formatted.contains("/bin\n"));
assert!(formatted.contains("/usr/local/bin\n"));
assert!(formatted.contains("ENV!"));
}
#[test]
fn test_parse_path_file() {
let content = r#"PATH!
/usr/bin
/bin
/usr/local/bin
ENV!
"#;
let path = parse_path_file(content).unwrap();
assert_eq!(path, "/usr/bin:/bin:/usr/local/bin");
}
#[test]
fn test_roundtrip() {
let original = "/usr/bin:/bin:/usr/local/bin:/opt/bin";
let formatted = format_path_file(original);
let parsed = parse_path_file(&formatted).unwrap();
assert_eq!(parsed, original);
}
#[test]
fn test_parse_with_extra_whitespace() {
let content = r#"
PATH!
/usr/bin
/bin
/usr/local/bin
ENV!
"#;
let path = parse_path_file(content).unwrap();
assert_eq!(path, "/usr/bin:/bin:/usr/local/bin");
}
#[test]
fn test_parse_empty_file() {
let content = "";
let result = parse_path_file(content);
assert!(result.is_err());
assert!(result.unwrap_err().contains("No PATH entries"));
}
#[test]
fn test_parse_no_path_section() {
let content = "ENV!\nVAR=value\n";
let result = parse_path_file(content);
assert!(result.is_ok());
}
#[test]
fn test_parse_legacy_format_single_line() {
let content = "/usr/bin:/bin:/usr/local/bin";
let path = parse_path_file(content).unwrap();
assert_eq!(path, "/usr/bin:/bin:/usr/local/bin");
}
#[test]
fn test_parse_legacy_format_with_whitespace() {
let content = " /usr/bin:/bin:/usr/local/bin \n";
let path = parse_path_file(content).unwrap();
assert_eq!(path, "/usr/bin:/bin:/usr/local/bin");
}
#[test]
fn test_parse_legacy_format_multiline() {
let content = "/usr/bin:/bin:\n/usr/local/bin:/opt/bin";
let path = parse_path_file(content).unwrap();
assert_eq!(path, "/usr/bin:/bin:/usr/local/bin:/opt/bin");
}
#[test]
fn test_parse_legacy_format_with_empty_entries() {
let content = "/usr/bin::/bin::::/usr/local/bin";
let path = parse_path_file(content).unwrap();
assert_eq!(path, "/usr/bin:/bin:/usr/local/bin");
}
#[test]
fn test_backward_compatibility_upgrade() {
let legacy_content = "/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin";
let parsed = parse_path_file(legacy_content).unwrap();
assert_eq!(parsed, legacy_content);
let new_content = "PATH!\n/usr/bin\n/bin\n/usr/local/bin\n/opt/homebrew/bin\n\nENV!\n";
let parsed_new = parse_path_file(new_content).unwrap();
assert_eq!(parsed_new, "/usr/bin:/bin:/usr/local/bin:/opt/homebrew/bin");
assert_eq!(parsed, parsed_new);
}
}