/** * Dedent a multiline string by removing common leading whitespace. * * This function analyzes all non-empty lines in the input text to determine * the minimum indentation level, then removes that amount of whitespace * from the beginning of each line. This is useful for working with * multi-line strings in code that have been indented to match the * surrounding code structure. * * # Arguments * * * `text` - The multiline string to dedent * * # Returns * * * `String` - The dedented string * * # Examples * * ``` * let indented = " line 1\n line 2\n line 3"; * let dedented = dedent(indented); * assert_eq!(dedented, "line 1\nline 2\n line 3"); * ``` * * # Notes * * - Empty lines are preserved but have all leading whitespace removed * - Tabs are counted as 4 spaces for indentation purposes */ pub fn dedent(text: &str) -> String { let lines: Vec<&str> = text.lines().collect(); // Find the minimum indentation level (ignore empty lines) let min_indent = lines.iter() .filter(|line| !line.trim().is_empty()) .map(|line| { let mut spaces = 0; for c in line.chars() { if c == ' ' { spaces += 1; } else if c == '\t' { spaces += 4; // Count tabs as 4 spaces } else { break; } } spaces }) .min() .unwrap_or(0); // Remove that many spaces from the beginning of each line lines.iter() .map(|line| { if line.trim().is_empty() { return String::new(); } let mut count = 0; let mut chars = line.chars().peekable(); // Skip initial spaces up to min_indent while count < min_indent && chars.peek().is_some() { match chars.peek() { Some(' ') => { chars.next(); count += 1; }, Some('\t') => { chars.next(); count += 4; }, _ => break, } } // Return the remaining characters chars.collect::() }) .collect::>() .join("\n") } /** * Prefix a multiline string with a specified prefix. * * This function adds the specified prefix to the beginning of each line in the input text. * * # Arguments * * * `text` - The multiline string to prefix * * `prefix` - The prefix to add to each line * * # Returns * * * `String` - The prefixed string * * # Examples * * ``` * let text = "line 1\nline 2\nline 3"; * let prefixed = prefix(text, " "); * assert_eq!(prefixed, " line 1\n line 2\n line 3"); * ``` */ pub fn prefix(text: &str, prefix: &str) -> String { text.lines() .map(|line| format!("{}{}", prefix, line)) .collect::>() .join("\n") } #[cfg(test)] mod tests { use super::*; #[test] fn test_dedent() { let indented = " line 1\n line 2\n line 3"; let dedented = dedent(indented); assert_eq!(dedented, "line 1\nline 2\n line 3"); } #[test] fn test_prefix() { let text = "line 1\nline 2\nline 3"; let prefixed = prefix(text, " "); assert_eq!(prefixed, " line 1\n line 2\n line 3"); } }