133 lines
3.5 KiB
Rust
133 lines
3.5 KiB
Rust
/**
|
|
* 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::<String>()
|
|
})
|
|
.collect::<Vec<String>>()
|
|
.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::<Vec<String>>()
|
|
.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");
|
|
}
|
|
}
|