blob: 1eda868379afc908841137462564b7b091939cc3 [file] [log] [blame] [edit]
#![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 ");
}