summaryrefslogtreecommitdiffstats
path: root/vendor/pad/src/lib.rs
blob: 1eda868379afc908841137462564b7b091939cc3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
#![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    ");
}