Skip to main content

ferray_strings/
align.rs

1// ferray-strings: Alignment and padding operations (REQ-6)
2//
3// Implements center, ljust, rjust, zfill — elementwise on StringArray.
4
5use ferray_core::dimension::Dimension;
6use ferray_core::error::FerrayResult;
7
8use crate::string_array::StringArray;
9
10/// Center each string in a field of the given width, padded with `fillchar`.
11///
12/// If the string is already longer than `width`, it is returned unchanged.
13///
14/// # Errors
15/// Returns an error if the internal array construction fails.
16pub fn center<D: Dimension>(
17    a: &StringArray<D>,
18    width: usize,
19    fillchar: char,
20) -> FerrayResult<StringArray<D>> {
21    a.map(|s| {
22        let char_count = s.chars().count();
23        if char_count >= width {
24            return s.to_string();
25        }
26        let total_pad = width - char_count;
27        let left_pad = total_pad / 2;
28        let right_pad = total_pad - left_pad;
29        let mut result = String::with_capacity(s.len() + total_pad);
30        for _ in 0..left_pad {
31            result.push(fillchar);
32        }
33        result.push_str(s);
34        for _ in 0..right_pad {
35            result.push(fillchar);
36        }
37        result
38    })
39}
40
41/// Left-justify each string in a field of the given width, padded with spaces.
42///
43/// If the string is already longer than `width`, it is returned unchanged.
44///
45/// # Errors
46/// Returns an error if the internal array construction fails.
47pub fn ljust<D: Dimension>(a: &StringArray<D>, width: usize) -> FerrayResult<StringArray<D>> {
48    a.map(|s| {
49        let char_count = s.chars().count();
50        if char_count >= width {
51            return s.to_string();
52        }
53        let pad = width - char_count;
54        let mut result = String::with_capacity(s.len() + pad);
55        result.push_str(s);
56        for _ in 0..pad {
57            result.push(' ');
58        }
59        result
60    })
61}
62
63/// Right-justify each string in a field of the given width, padded with spaces.
64///
65/// If the string is already longer than `width`, it is returned unchanged.
66///
67/// # Errors
68/// Returns an error if the internal array construction fails.
69pub fn rjust<D: Dimension>(a: &StringArray<D>, width: usize) -> FerrayResult<StringArray<D>> {
70    a.map(|s| {
71        let char_count = s.chars().count();
72        if char_count >= width {
73            return s.to_string();
74        }
75        let pad = width - char_count;
76        let mut result = String::with_capacity(s.len() + pad);
77        for _ in 0..pad {
78            result.push(' ');
79        }
80        result.push_str(s);
81        result
82    })
83}
84
85/// Pad each string on the left with zeros to fill the given width.
86///
87/// If the string starts with a sign (`+` or `-`), the sign is placed
88/// before the zeros. If the string is already longer than `width`,
89/// it is returned unchanged.
90///
91/// # Errors
92/// Returns an error if the internal array construction fails.
93pub fn zfill<D: Dimension>(a: &StringArray<D>, width: usize) -> FerrayResult<StringArray<D>> {
94    a.map(|s| {
95        let char_count = s.chars().count();
96        if char_count >= width {
97            return s.to_string();
98        }
99        let pad = width - char_count;
100        let (sign, rest) = if s.starts_with('+') || s.starts_with('-') {
101            (&s[..1], &s[1..])
102        } else {
103            ("", s)
104        };
105        let mut result = String::with_capacity(s.len() + pad);
106        result.push_str(sign);
107        for _ in 0..pad {
108            result.push('0');
109        }
110        result.push_str(rest);
111        result
112    })
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118    use crate::string_array::array;
119
120    #[test]
121    fn test_center() {
122        let a = array(&["hi", "x"]).unwrap();
123        let b = center(&a, 6, '*').unwrap();
124        assert_eq!(b.as_slice(), &["**hi**", "**x***"]);
125    }
126
127    #[test]
128    fn test_center_no_pad_needed() {
129        let a = array(&["hello"]).unwrap();
130        let b = center(&a, 3, ' ').unwrap();
131        assert_eq!(b.as_slice(), &["hello"]);
132    }
133
134    #[test]
135    fn test_ljust() {
136        let a = array(&["hi", "hello"]).unwrap();
137        let b = ljust(&a, 6).unwrap();
138        assert_eq!(b.as_slice(), &["hi    ", "hello "]);
139    }
140
141    #[test]
142    fn test_ljust_no_pad_needed() {
143        let a = array(&["hello"]).unwrap();
144        let b = ljust(&a, 3).unwrap();
145        assert_eq!(b.as_slice(), &["hello"]);
146    }
147
148    #[test]
149    fn test_rjust() {
150        let a = array(&["hi", "hello"]).unwrap();
151        let b = rjust(&a, 6).unwrap();
152        assert_eq!(b.as_slice(), &["    hi", " hello"]);
153    }
154
155    #[test]
156    fn test_zfill() {
157        let a = array(&["42", "-17", "+5", "abc"]).unwrap();
158        let b = zfill(&a, 5).unwrap();
159        assert_eq!(b.as_slice(), &["00042", "-0017", "+0005", "00abc"]);
160    }
161
162    #[test]
163    fn test_zfill_no_pad_needed() {
164        let a = array(&["12345"]).unwrap();
165        let b = zfill(&a, 3).unwrap();
166        assert_eq!(b.as_slice(), &["12345"]);
167    }
168}