#![deny(missing_docs, missing_debug_implementations, nonstandard_style)]
#![doc = include_str!("../README.md")]
#![forbid(unsafe_code)]
mod config;
pub use config::{Alignment, BorderColor, BorderComponents, BorderStyle, Config};
use console::{measure_text_width, pad_str, Style};
use std::{cmp, fmt::Display};
const SPACE: &str = " ";
const NEWLINE: &str = "\n";
#[derive(PartialEq, Clone, Debug)]
pub struct Billboard {
pub config: Config,
}
impl Billboard {
pub fn default() -> Billboard {
Config::default().build()
}
pub fn new(config: Config) -> Billboard {
Billboard { config }
}
pub fn builder() -> Config {
Config::default()
}
pub fn print(&self, content: impl Display) {
println!("{}", self.enclose(content));
}
pub fn eprint(&self, content: impl Display) {
eprintln!("{}", self.enclose(content));
}
pub fn enclose(&self, content: impl Display) -> String {
let content = content.to_string();
let border_color = match self.config.border_color {
Some(color) => Style::from_dotted_str(&format!("{:?}", color).to_lowercase()),
None => Style::default(),
};
let mut lines: Vec<String> = Vec::new();
lines.push(SPACE.repeat(self.config.padding.top));
for line in content.split_terminator(NEWLINE) {
lines.push(line.to_string());
}
lines.push(SPACE.repeat(self.config.padding.bottom));
let mut widest_length: usize = 0;
for line in lines.clone() {
widest_length = cmp::max(widest_length, measure_text_width(line.as_str()));
}
for (i, line) in lines.clone().iter().enumerate() {
lines[i] = pad_str(&line, widest_length, self.config.text_alignment, None).to_string();
}
let content_width = widest_length + self.config.padding.left + self.config.padding.right;
let output = if let Some((max_width, _)) = term_size::dimensions() {
if content_width > max_width {
return content.to_string();
}
let padding_left = SPACE.repeat(self.config.padding.left);
let margin_left = match &self.config.box_alignment {
Alignment::Left => SPACE.repeat(self.config.padding.left),
Alignment::Center => {
let space_width = cmp::max((max_width - content_width) / 2, 0);
SPACE.repeat(space_width)
}
Alignment::Right => {
let space_width =
cmp::max(max_width - content_width - self.config.margin.right - 2, 0);
SPACE.repeat(space_width)
}
};
let mut horizontal_str = self.config.border_components.horizontal.to_string();
let mut horizontal_str_width = measure_text_width(horizontal_str.as_str());
if horizontal_str_width == 0 {
horizontal_str.push_str(" ");
horizontal_str_width = 1;
}
let mut horizontal = horizontal_str.repeat(
(content_width % horizontal_str_width) + (content_width / horizontal_str_width),
);
while measure_text_width(horizontal.as_str()) > content_width {
horizontal.pop();
}
let top = format!(
"{}{}{}{}{}",
NEWLINE.repeat(self.config.margin.top),
margin_left,
border_color.apply_to(&self.config.border_components.top_left),
border_color.apply_to(&horizontal),
border_color.apply_to(&self.config.border_components.top_right)
);
let bottom = format!(
"{}{}{}{}{}",
margin_left,
border_color.apply_to(&self.config.border_components.bottom_left),
border_color.apply_to(&horizontal),
border_color.apply_to(&self.config.border_components.bottom_right),
NEWLINE.repeat(self.config.margin.bottom)
);
let mut middle = String::from("\n");
let mut vertical = self.config.border_components.vertical.to_string();
if measure_text_width(vertical.as_str()) == 0 {
vertical.push_str(" ");
}
let vertical = vertical.repeat(lines.len());
let mut vertical_chars = vertical.chars();
for line in lines {
let vertical = border_color
.apply_to(&vertical_chars.next().unwrap())
.to_string();
middle.push_str(&margin_left);
middle.push_str(&vertical);
middle.push_str(&padding_left);
middle.push_str(&line);
middle.push_str(&SPACE.repeat(
content_width - measure_text_width(line.as_str()) - &self.config.padding.left,
));
middle.push_str(&vertical);
middle.push_str(NEWLINE);
}
format!("{}{}{}", top, middle, bottom)
} else {
content.to_string()
};
output
}
}