pad/
lib.rs

1#![deny(unsafe_code)]
2
3#![warn(missing_copy_implementations)]
4#![warn(missing_debug_implementations)]
5#![warn(missing_docs)]
6#![warn(trivial_numeric_casts)]
7#![warn(unreachable_pub)]
8#![warn(unused_results)]
9
10
11//! This is a library for padding strings at runtime.
12//!
13//! It provides four helper functions for the most common use cases, and one
14//! main function (`pad`) to cover the other cases.
15//!
16//! String length is determined with the
17//! [width](http://doc.rust-lang.org/nightly/std/str/trait.StrExt.html#tymethod.width)
18//! function, without assuming CJK.
19//!
20//! Padding in the stdlib
21//! ---------------------
22//!
23//! **You do not need this crate for simple padding!**
24//! It’s possible to pad strings using the Rust standard library.
25//!
26//! For example, to pad a number with zeroes:
27//!
28//! ```
29//! // Padding using std::fmt
30//! assert_eq!("0000012345", format!("{:0>10}", 12345));
31//! ```
32//!
33//! You can even use a variable for the padding width:
34//!
35//! ```
36//! // Padding using std::fmt
37//! assert_eq!("hello       ", format!("{:width$}", "hello", width=12));
38//! ```
39//!
40//! The [Rust documentation for `std::fmt`](https://doc.rust-lang.org/std/fmt/)
41//! contains more examples. The rest of the examples will use the `pad` crate.
42//!
43//! Examples
44//! --------
45//!
46//! You can pad a string to have a minimum width with the `pad_to_width`
47//! method:
48//!
49//! ```
50//! use pad::PadStr;
51//!
52//! println!("{}", "Hi there!".pad_to_width(16));
53//! ```
54//!
55//! This will print out “Hi there!” followed by seven spaces, which is the
56//! number of spaces necessary to bring it up to a total of sixteen characters
57//! wide.
58//!
59//!
60//! Alignment
61//! ---------
62//!
63//! By default, strings are left-aligned: any extra characters are added on
64//! the right. To change this, pass in an `Alignment` value:
65//!
66//! ```
67//! use pad::{PadStr, Alignment};
68//!
69//! let s = "I'm over here".pad_to_width_with_alignment(20, Alignment::Right);
70//! ```
71//!
72//! There are four of these in total:
73//!
74//! - **Left**, which puts the text on the left and spaces on the right;
75//! - **Right**, which puts the text on the right and spaces on the left;
76//! - **Middle**, which centres the text evenly, putting it slightly to the
77//!   left if it can’t be exactly centered;
78//! - **MiddleRight**, as above, but to the right.
79//!
80//!
81//! Characters
82//! ----------
83//!
84//! Another thing that’s set by default is the character that’s used to pad
85//! the strings — by default, it’s space, but you can change it:
86//!
87//! ```
88//! use pad::PadStr;
89//!
90//! let s = "Example".pad_to_width_with_char(10, '_');
91//! ```
92//!
93//!
94//! Truncation
95//! ----------
96//!
97//! Finally, you can override what happens when a value exceeds the width you
98//! give. By default, the width parameter indicates a *minimum width*: any
99//! string less will be padded, but any string greater will still be returned
100//! in its entirety.
101//!
102//! You can instead tell it to pad with a maximum value, which will truncate
103//! the input when a string longer than the width is passed in.
104//!
105//! ```
106//! use pad::PadStr;
107//!
108//! let short = "short".with_exact_width(10);                // "short     "
109//! let long  = "this string is long".with_exact_width(10);  // "this strin"
110//! ```
111//!
112//!
113//! A Full Example
114//! --------------
115//!
116//! All of the above functions delegate to the `pad` function, which you can
117//! use in special cases. Here, in order to **right**-pad a number with
118//! **zeroes**, pass in all the arguments:
119//!
120//! ```
121//! use pad::{PadStr, Alignment};
122//!
123//! let s = "12345".pad(10, '0', Alignment::Right, true);
124//! ```
125//!
126//! (The `true` at the end governs whether to truncate or not.)
127//!
128//!
129//! Note on Debugging
130//! -----------------
131//!
132//! One very last point: the width function takes a `usize`, rather than a
133//! signed number type. This means that if you try to pass in a negative size,
134//! it’ll wrap around to a positive size, and produce a massive string and
135//! possibly crash your program. So if your padding calls are failing for some
136//! reason, this is probably why.
137
138
139extern crate unicode_width;
140use unicode_width::UnicodeWidthStr;
141
142
143/// An **alignment** tells the padder where to put the spaces.
144#[derive(PartialEq, Eq, Debug, Copy, Clone)]
145pub enum Alignment {
146
147    /// Text on the left, spaces on the right.
148    Left,
149
150    /// Text on the right, spaces on the left.
151    Right,
152
153    /// Text in the middle, spaces around it, but **shifted to the left** if it can’t be exactly central.
154    Middle,
155
156    /// Text in the middle, spaces around it, but **shifted to the right** if it can’t be exactly central.
157    MiddleRight,
158}
159
160/// Functions to do with string padding.
161pub trait PadStr {
162
163    /// Pad a string to be at least the given width by adding spaces on the
164    /// right.
165    fn pad_to_width(&self, width: usize) -> String {
166        self.pad(width, ' ', Alignment::Left, false)
167    }
168
169    /// Pad a string to be at least the given width by adding the given
170    /// character on the right.
171    fn pad_to_width_with_char(&self, width: usize, pad_char: char) -> String {
172        self.pad(width, pad_char, Alignment::Left, false)
173    }
174
175    /// Pad a string to be at least the given with by adding spaces around it.
176    fn pad_to_width_with_alignment(&self, width: usize, alignment: Alignment) -> String {
177        self.pad(width, ' ', alignment, false)
178    }
179
180    /// Pad a string to be *exactly* the given width by either adding spaces
181    /// on the right, or by truncating it to that width.
182    fn with_exact_width(&self, width: usize) -> String {
183        self.pad(width, ' ', Alignment::Left, true)
184    }
185
186    /// Pad a string to the given width somehow.
187    fn pad(&self, width: usize, pad_char: char, alignment: Alignment, truncate: bool) -> String;
188}
189
190impl PadStr for str {
191    fn pad(&self, width: usize, pad_char: char, alignment: Alignment, truncate: bool) -> String {
192        // Use width instead of len for graphical display
193        let cols = UnicodeWidthStr::width(self);
194
195        if cols >= width {
196            if truncate {
197                return self[..width].to_string();
198            }
199            else {
200                return self.to_string();
201            }
202        }
203
204        let diff = width - cols;
205
206        let (left_pad, right_pad) = match alignment {
207            Alignment::Left         => (0, diff),
208            Alignment::Right        => (diff, 0),
209            Alignment::Middle       => (diff / 2, diff - diff / 2),
210            Alignment::MiddleRight  => (diff - diff / 2, diff / 2),
211        };
212
213        let mut s = String::new();
214        for _ in 0..left_pad { s.push(pad_char) }
215        s.push_str(self);
216        for _ in 0..right_pad { s.push(pad_char) }
217        s
218    }
219}
220
221
222#[cfg(test)]
223mod test {
224    use super::PadStr;
225    use super::Alignment::*;
226
227    macro_rules! test {
228    	($name: ident: $input: expr => $result: expr) => {
229    		#[test]
230    		fn $name() {
231    			assert_eq!($result.to_string(), $input)
232    		}
233    	};
234    }
235
236    test!(zero: "".pad_to_width(0) => "");
237
238    test!(simple: "hello".pad_to_width(10) => "hello     ");
239    test!(spaces: "".pad_to_width(6)       => "      ");
240
241    test!(too_long:      "hello".pad_to_width(2)      => "hello");
242    test!(still_to_long: "hi there".pad_to_width(0)   => "hi there");
243    test!(exact_length:  "greetings".pad_to_width(9)  => "greetings");
244    test!(one_more:      "greetings".pad_to_width(10) => "greetings ");
245    test!(one_less:      "greetings".pad_to_width(8)  => "greetings");
246
247    test!(left:  "left align".pad_to_width_with_alignment(13, Left)   => "left align   ");
248    test!(right: "right align".pad_to_width_with_alignment(13, Right) => "  right align");
249
250    test!(centre_even:     "good day".pad_to_width_with_alignment(12, Middle)    => "  good day  ");
251    test!(centre_odd:      "salutations".pad_to_width_with_alignment(13, Middle) => " salutations ");
252    test!(centre_offset:   "odd".pad_to_width_with_alignment(6, Middle)          => " odd  ");
253    test!(centre_offset_2: "odd".pad_to_width_with_alignment(6, MiddleRight)     => "  odd ");
254
255    test!(character: "testing".pad_to_width_with_char(10, '_') => "testing___");
256
257    test!(accent: "pâté".pad_to_width(6) => "pâté  ");
258
259    test!(truncate:  "this song is just six words long".with_exact_width(7) => "this so");
260    test!(too_short: "stormclouds".with_exact_width(15) => "stormclouds    ");
261}