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}
262