File size: 4,241 Bytes
e5034c3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | use std::fmt::Display;
pub struct NumberedContent<'a> {
start: usize,
raw_content: &'a str,
}
impl<'a> Display for NumberedContent<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let lines: Vec<&str> = self.raw_content.lines().collect();
if lines.is_empty() {
return Ok(());
}
// Calculate the width needed for the largest line number
let max_line_number = self.start + lines.len() - 1;
let width = max_line_number.to_string().len();
let start = self.start;
let last = lines.len() - 1;
for (i, line) in lines.into_iter().enumerate() {
if i < last {
writeln!(f, "{:>width$}:{}", start + i, line, width = width)?;
} else {
write!(f, "{:>width$}:{}", start + i, line, width = width)?;
}
}
Ok(())
}
}
pub trait LineNumbers {
/// Returns the text with each line numbered, starting at 1.
fn to_numbered(&self) -> NumberedContent<'_> {
self.to_numbered_from(1)
}
/// Returns the text with each line numbered, starting at the given offset.
fn to_numbered_from(&self, start: usize) -> NumberedContent<'_>;
}
impl<T: AsRef<str>> LineNumbers for T {
fn to_numbered_from(&self, start: usize) -> NumberedContent<'_> {
NumberedContent { start, raw_content: self.as_ref() }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_numbered_default_start() {
let text = "first line\nsecond line\nthird line";
let expected = "1:first line\n2:second line\n3:third line";
assert_eq!(text.to_numbered().to_string(), expected);
}
#[test]
fn test_numbered_from_custom_start() {
let text = "alpha\nbeta\ngamma";
let expected = "5:alpha\n6:beta\n7:gamma";
assert_eq!(text.to_numbered_from(5).to_string(), expected);
}
#[test]
fn test_numbered_single_line() {
let text = "single line";
let expected = "1:single line";
assert_eq!(text.to_numbered().to_string(), expected);
}
#[test]
fn test_numbered_empty_string() {
let text = "";
let expected = "";
assert_eq!(text.to_numbered().to_string(), expected);
}
#[test]
fn test_numbered_with_empty_lines() {
let text = "line1\n\nline3";
let expected = "1:line1\n2:\n3:line3";
assert_eq!(text.to_numbered().to_string(), expected);
}
#[test]
fn test_numbered_right_aligned_single_digit() {
let text = "line1\nline2\nline3";
let expected = "1:line1\n2:line2\n3:line3";
assert_eq!(text.to_numbered().to_string(), expected);
}
#[test]
fn test_numbered_right_aligned_two_digits() {
let text = "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk";
let expected = " 1:a\n 2:b\n 3:c\n 4:d\n 5:e\n 6:f\n 7:g\n 8:h\n 9:i\n10:j\n11:k";
assert_eq!(text.to_numbered().to_string(), expected);
}
#[test]
fn test_numbered_right_aligned_three_digits() {
let mut lines = Vec::new();
for i in 1..=100 {
lines.push(format!("line{}", i));
}
let text = lines.join("\n");
let actual = text.to_numbered().to_string();
// Check first line has 2 leading spaces (001 -> " 1")
assert!(actual.starts_with(" 1:line1"));
// Check line 10 has 1 leading space (010 -> " 10")
assert!(actual.contains("\n 10:line10\n"));
// Check line 100 has no leading spaces (100 -> "100")
assert!(actual.contains("\n100:line100"));
}
#[test]
fn test_numbered_from_right_aligned() {
let text = "alpha\nbeta\ngamma\ndelta";
// Starting from 98, so max is 101 (3 digits)
let expected = " 98:alpha\n 99:beta\n100:gamma\n101:delta";
assert_eq!(text.to_numbered_from(98).to_string(), expected);
}
#[test]
fn test_numbered_from_crosses_digit_boundary() {
let text = "line8\nline9\nline10\nline11";
// Starting from 8, max is 11 (2 digits)
let expected = " 8:line8\n 9:line9\n10:line10\n11:line11";
assert_eq!(text.to_numbered_from(8).to_string(), expected);
}
}
|