sal/src/text/dedent.rs
Mahmoud Emad 1ebd591f19 feat: Enhance documentation and add .gitignore entries
- Add new documentation sections for PostgreSQL installer
  functions and usage examples.  Improves clarity and
  completeness of the documentation.
- Add new files and patterns to .gitignore to prevent
  unnecessary files from being committed to the repository.
  Improves repository cleanliness and reduces clutter.
2025-05-10 08:50:05 +03:00

138 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
*
* ```
* use sal::text::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");
* ```
*
* # 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
*
* ```
* use sal::text::prefix;
*
* 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");
}
}