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