| 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 |  | 
|---|
| 139 | extern crate unicode_width; | 
|---|
| 140 | use unicode_width::UnicodeWidthStr; | 
|---|
| 141 |  | 
|---|
| 142 |  | 
|---|
| 143 | /// An **alignment** tells the padder where to put the spaces. | 
|---|
| 144 | #[ derive(PartialEq, Eq, Debug, Copy, Clone)] | 
|---|
| 145 | pub 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. | 
|---|
| 161 | pub 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 |  | 
|---|
| 190 | impl 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)] | 
|---|
| 223 | mod 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 |  | 
|---|