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
#![crate_name = "pad"]
#![crate_type = "rlib"]
#![crate_type = "dylib"]

#![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.
//!
//! The routines in `std::fmt` only work with formatting strings provided at
//! compile-time, making them unsuitable for padding to a custom or
//! user-defined value. Rather than re-implement all of `std::fmt`, padding is
//! probably the most common use case, which is written in a more
//! runtime-friendly fashion here.
//!
//! 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.
//!
//! Examples
//! --------
//!
//! You can pad a string to have a minimum width with the `pad_to_width`
//! method:
//!
//! ```rust
//! 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:
//!
//! ```rust
//! 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:
//!
//! ```rust
//! 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.
//!
//! ```rust
//! 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:
//!
//! ```rust
//! use pad::{PadStr, Alignment};
//! let s = "12345".pad(10, '0', Alignment::Right, true);
//! ```
//!
//! (The `true` at the end could just as easily be `false`. It's 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 self::Alignment::*;

        // 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 {
            Left        => (0, diff),
            Right       => (diff, 0),
            Middle      => (diff / 2, diff - diff / 2),
            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    ");
}