#![deny(unsafe_code)] #![warn(missing_copy_implementations)] #![warn(missing_debug_implementations)] #![warn(missing_docs)] #![warn(trivial_numeric_casts)] #![warn(unreachable_pub)] #![warn(unused_results)] //! This is a library for padding strings at runtime. //! //! It provides four helper functions for the most common use cases, and one //! main function (`pad`) to cover the other cases. //! //! String length is determined with the //! [width](http://doc.rust-lang.org/nightly/std/str/trait.StrExt.html#tymethod.width) //! function, without assuming CJK. //! //! Padding in the stdlib //! --------------------- //! //! **You do not need this crate for simple padding!** //! It’s possible to pad strings using the Rust standard library. //! //! For example, to pad a number with zeroes: //! //! ``` //! // Padding using std::fmt //! assert_eq!("0000012345", format!("{:0>10}", 12345)); //! ``` //! //! You can even use a variable for the padding width: //! //! ``` //! // Padding using std::fmt //! assert_eq!("hello ", format!("{:width$}", "hello", width=12)); //! ``` //! //! The [Rust documentation for `std::fmt`](https://doc.rust-lang.org/std/fmt/) //! contains more examples. The rest of the examples will use the `pad` crate. //! //! Examples //! -------- //! //! You can pad a string to have a minimum width with the `pad_to_width` //! method: //! //! ``` //! use pad::PadStr; //! //! println!("{}", "Hi there!".pad_to_width(16)); //! ``` //! //! This will print out “Hi there!” followed by seven spaces, which is the //! number of spaces necessary to bring it up to a total of sixteen characters //! wide. //! //! //! Alignment //! --------- //! //! By default, strings are left-aligned: any extra characters are added on //! the right. To change this, pass in an `Alignment` value: //! //! ``` //! use pad::{PadStr, Alignment}; //! //! let s = "I'm over here".pad_to_width_with_alignment(20, Alignment::Right); //! ``` //! //! There are four of these in total: //! //! - **Left**, which puts the text on the left and spaces on the right; //! - **Right**, which puts the text on the right and spaces on the left; //! - **Middle**, which centres the text evenly, putting it slightly to the //! left if it can’t be exactly centered; //! - **MiddleRight**, as above, but to the right. //! //! //! Characters //! ---------- //! //! Another thing that’s set by default is the character that’s used to pad //! the strings — by default, it’s space, but you can change it: //! //! ``` //! use pad::PadStr; //! //! let s = "Example".pad_to_width_with_char(10, '_'); //! ``` //! //! //! Truncation //! ---------- //! //! Finally, you can override what happens when a value exceeds the width you //! give. By default, the width parameter indicates a *minimum width*: any //! string less will be padded, but any string greater will still be returned //! in its entirety. //! //! You can instead tell it to pad with a maximum value, which will truncate //! the input when a string longer than the width is passed in. //! //! ``` //! use pad::PadStr; //! //! let short = "short".with_exact_width(10); // "short " //! let long = "this string is long".with_exact_width(10); // "this strin" //! ``` //! //! //! A Full Example //! -------------- //! //! All of the above functions delegate to the `pad` function, which you can //! use in special cases. Here, in order to **right**-pad a number with //! **zeroes**, pass in all the arguments: //! //! ``` //! use pad::{PadStr, Alignment}; //! //! let s = "12345".pad(10, '0', Alignment::Right, true); //! ``` //! //! (The `true` at the end governs whether to truncate or not.) //! //! //! Note on Debugging //! ----------------- //! //! One very last point: the width function takes a `usize`, rather than a //! signed number type. This means that if you try to pass in a negative size, //! it’ll wrap around to a positive size, and produce a massive string and //! possibly crash your program. So if your padding calls are failing for some //! reason, this is probably why. extern crate unicode_width; use unicode_width::UnicodeWidthStr; /// An **alignment** tells the padder where to put the spaces. #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum Alignment { /// Text on the left, spaces on the right. Left, /// Text on the right, spaces on the left. Right, /// Text in the middle, spaces around it, but **shifted to the left** if it can’t be exactly central. Middle, /// Text in the middle, spaces around it, but **shifted to the right** if it can’t be exactly central. MiddleRight, } /// Functions to do with string padding. pub trait PadStr { /// Pad a string to be at least the given width by adding spaces on the /// right. fn pad_to_width(&self, width: usize) -> String { self.pad(width, ' ', Alignment::Left, false) } /// Pad a string to be at least the given width by adding the given /// character on the right. fn pad_to_width_with_char(&self, width: usize, pad_char: char) -> String { self.pad(width, pad_char, Alignment::Left, false) } /// Pad a string to be at least the given with by adding spaces around it. fn pad_to_width_with_alignment(&self, width: usize, alignment: Alignment) -> String { self.pad(width, ' ', alignment, false) } /// Pad a string to be *exactly* the given width by either adding spaces /// on the right, or by truncating it to that width. fn with_exact_width(&self, width: usize) -> String { self.pad(width, ' ', Alignment::Left, true) } /// Pad a string to the given width somehow. fn pad(&self, width: usize, pad_char: char, alignment: Alignment, truncate: bool) -> String; } impl PadStr for str { fn pad(&self, width: usize, pad_char: char, alignment: Alignment, truncate: bool) -> String { // Use width instead of len for graphical display let cols = UnicodeWidthStr::width(self); if cols >= width { if truncate { return self[..width].to_string(); } else { return self.to_string(); } } let diff = width - cols; let (left_pad, right_pad) = match alignment { Alignment::Left => (0, diff), Alignment::Right => (diff, 0), Alignment::Middle => (diff / 2, diff - diff / 2), Alignment::MiddleRight => (diff - diff / 2, diff / 2), }; let mut s = String::new(); for _ in 0..left_pad { s.push(pad_char) } s.push_str(self); for _ in 0..right_pad { s.push(pad_char) } s } } #[cfg(test)] mod test { use super::PadStr; use super::Alignment::*; macro_rules! test { ($name: ident: $input: expr => $result: expr) => { #[test] fn $name() { assert_eq!($result.to_string(), $input) } }; } test!(zero: "".pad_to_width(0) => ""); test!(simple: "hello".pad_to_width(10) => "hello "); test!(spaces: "".pad_to_width(6) => " "); test!(too_long: "hello".pad_to_width(2) => "hello"); test!(still_to_long: "hi there".pad_to_width(0) => "hi there"); test!(exact_length: "greetings".pad_to_width(9) => "greetings"); test!(one_more: "greetings".pad_to_width(10) => "greetings "); test!(one_less: "greetings".pad_to_width(8) => "greetings"); test!(left: "left align".pad_to_width_with_alignment(13, Left) => "left align "); test!(right: "right align".pad_to_width_with_alignment(13, Right) => " right align"); test!(centre_even: "good day".pad_to_width_with_alignment(12, Middle) => " good day "); test!(centre_odd: "salutations".pad_to_width_with_alignment(13, Middle) => " salutations "); test!(centre_offset: "odd".pad_to_width_with_alignment(6, Middle) => " odd "); test!(centre_offset_2: "odd".pad_to_width_with_alignment(6, MiddleRight) => " odd "); test!(character: "testing".pad_to_width_with_char(10, '_') => "testing___"); test!(accent: "pâté".pad_to_width(6) => "pâté "); test!(truncate: "this song is just six words long".with_exact_width(7) => "this so"); test!(too_short: "stormclouds".with_exact_width(15) => "stormclouds "); }