#![no_std]
#![forbid(unsafe_code)]
#![doc(html_root_url = "https://docs.rs/indent-string/0.1.0")]
extern crate alloc;
use alloc::string::{String, ToString};
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct ReadmeDoctests;
fn is_js_whitespace(c: char) -> bool {
matches!(
c,
'\u{0009}'
| '\u{000A}'
| '\u{000B}'
| '\u{000C}'
| '\u{000D}'
| '\u{0020}'
| '\u{00A0}'
| '\u{1680}'
| '\u{2000}'
..='\u{200A}'
| '\u{2028}'
| '\u{2029}'
| '\u{202F}'
| '\u{205F}'
| '\u{3000}'
| '\u{FEFF}'
)
}
fn is_blank(line: &str) -> bool {
line.chars().all(is_js_whitespace)
}
fn is_line_terminator(c: char) -> bool {
matches!(c, '\n' | '\r' | '\u{2028}' | '\u{2029}')
}
#[must_use]
pub fn indent_string(string: &str) -> String {
indent_string_with(string, 1, " ", false)
}
#[must_use]
pub fn indent_string_with(
string: &str,
count: usize,
indent: &str,
include_empty_lines: bool,
) -> String {
if count == 0 {
return string.to_string();
}
let prefix_len = indent.len().saturating_mul(count);
let mut out = String::with_capacity(string.len() + prefix_len.saturating_add(prefix_len));
let maybe_indent = |line: &str, out: &mut String| {
if include_empty_lines || !is_blank(line) {
for _ in 0..count {
out.push_str(indent);
}
}
};
let mut rest = string;
loop {
let Some(position) = rest.find(is_line_terminator) else {
maybe_indent(rest, &mut out);
out.push_str(rest);
break;
};
let (line, after) = rest.split_at(position);
maybe_indent(line, &mut out);
out.push_str(line);
let mut chars = after.chars();
if let Some(terminator) = chars.next() {
out.push(terminator);
}
rest = chars.as_str();
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults() {
assert_eq!(indent_string("foo\nbar"), " foo\n bar");
assert_eq!(indent_string("foo\r\nbar"), " foo\r\n bar");
assert_eq!(indent_string(""), "");
assert_eq!(indent_string("single"), " single");
}
#[test]
fn count_and_indent() {
assert_eq!(
indent_string_with("foo\nbar", 2, " ", false),
" foo\n bar"
);
assert_eq!(
indent_string_with("foo\nbar", 1, "\t", false),
"\tfoo\n\tbar"
);
assert_eq!(indent_string_with("x\ny", 3, "-", false), "---x\n---y");
assert_eq!(indent_string_with("foo", 0, " ", false), "foo");
}
#[test]
fn empty_lines() {
assert_eq!(indent_string("foo\n\nbar"), " foo\n\n bar");
assert_eq!(indent_string("foo\n \nbar"), " foo\n \n bar");
assert_eq!(
indent_string_with("foo\n\nbar", 1, " ", true),
" foo\n \n bar"
);
assert_eq!(indent_string_with("a\n \nb", 1, " ", true), " a\n \n b");
}
#[test]
fn carriage_return_is_a_line_terminator_with_include_empty_lines() {
assert_eq!(indent_string_with("foo\r\nbar", 1, " ", true), " foo\r \n bar");
assert_eq!(indent_string_with("a\u{2028}b", 1, "-", true), "-a\u{2028}-b");
}
}