#![deny(rust_2018_idioms, unused, unused_import_braces, unused_qualifications, warnings, missing_docs)]
use {
std::{
collections::BTreeMap,
convert::{
TryFrom,
TryInto
},
fmt,
iter::FromIterator
},
css_color_parser::{
Color,
ColorParseError
},
url::Url
};
#[cfg(all(feature = "base64", feature = "image"))]
use {
image::{
DynamicImage,
ImageError,
ImageOutputFormat::PNG,
ImageResult
}
};
#[cfg(feature = "url1")]
use url1::Url as Url1;
#[derive(Debug)]
pub enum Extra {
Alternate(Box<ContentItem>),
Submenu(Menu)
}
pub trait IntoColor {
fn into_color(self) -> Result<Color, ColorParseError>;
}
impl IntoColor for &str {
fn into_color(self) -> Result<Color, ColorParseError> {
Ok(self.parse()?)
}
}
impl IntoColor for Color {
fn into_color(self) -> Result<Color, ColorParseError> {
Ok(self)
}
}
#[cfg(feature = "css-colors")]
macro_rules! impl_into_color_for_css_color {
($t:ty) => {
impl IntoColor for $t {
fn into_color(self) -> Result<Color, ColorParseError> {
Ok(self.to_string().parse()?)
}
}
};
}
#[cfg(feature = "css-colors")] impl_into_color_for_css_color!(css_colors::RGB);
#[cfg(feature = "css-colors")] impl_into_color_for_css_color!(css_colors::RGBA);
#[cfg(feature = "css-colors")] impl_into_color_for_css_color!(css_colors::HSL);
#[cfg(feature = "css-colors")] impl_into_color_for_css_color!(css_colors::HSLA);
#[cfg(feature = "serenity")]
impl IntoColor for serenity::utils::Colour {
fn into_color(self) -> Result<Color, ColorParseError> {
Ok(Color {
r: self.r(),
g: self.g(),
b: self.b(),
a: 1.0
})
}
}
pub trait IntoUrl {
fn into_url(self) -> Result<Url, url::ParseError>;
}
impl IntoUrl for Url {
fn into_url(self) -> Result<Url, url::ParseError> {
Ok(self)
}
}
impl IntoUrl for String {
fn into_url(self) -> Result<Url, url::ParseError> {
Url::parse(&self)
}
}
impl<'a> IntoUrl for &'a str {
fn into_url(self) -> Result<Url, url::ParseError> {
Url::parse(self)
}
}
#[cfg(feature = "url1")]
impl IntoUrl for Url1 {
fn into_url(self) -> Result<Url, url::ParseError> {
Url::parse(self.as_str())
}
}
#[derive(Debug)]
pub enum Params {
Zero([String; 1]),
One([String; 2]),
Two([String; 3]),
Three([String; 4]),
Four([String; 5]),
Five([String; 6])
}
impl Params {
pub fn iter(&self) -> impl Iterator<Item = &String> {
match self {
Params::Zero(a) => a.iter(),
Params::One(a) => a.iter(),
Params::Two(a) => a.iter(),
Params::Three(a) => a.iter(),
Params::Four(a) => a.iter(),
Params::Five(a) => a.iter(),
}
}
}
macro_rules! params_from {
($n:literal, $variant:ident, $($elt:ident: $t:ident),+) => {
impl<T: ToString> From<[T; $n]> for Params {
fn from([$($elt),+]: [T; $n]) -> Params {
Params::$variant([$($elt.to_string()),+])
}
}
impl<$($t: ToString),+> From<($($t,)+)> for Params {
fn from(($($elt,)+): ($($t,)+)) -> Params {
Params::$variant([$($elt.to_string()),+])
}
}
};
}
params_from!(1, Zero, cmd: A);
params_from!(2, One, cmd: A, param1: B);
params_from!(3, Two, cmd: A, param1: B, param2: C);
params_from!(4, Three, cmd: A, param1: B, param2: C, param3: D);
params_from!(5, Four, cmd: A, param1: B, param2: C, param3: D, param4: E);
params_from!(6, Five, cmd: A, param1: B, param2: C, param3: D, param4: E, param5: F);
impl<'a, T: ToString> TryFrom<&'a [T]> for Params {
type Error = &'a [T];
fn try_from(slice: &[T]) -> Result<Params, &[T]> {
match slice {
[cmd] => Ok(Params::Zero([cmd.to_string()])),
[cmd, param1] => Ok(Params::One([cmd.to_string(), param1.to_string()])),
[cmd, param1, param2] => Ok(Params::Two([cmd.to_string(), param1.to_string(), param2.to_string()])),
[cmd, param1, param2, param3] => Ok(Params::Three([cmd.to_string(), param1.to_string(), param2.to_string(), param3.to_string()])),
[cmd, param1, param2, param3, param4] => Ok(Params::Four([cmd.to_string(), param1.to_string(), param2.to_string(), param3.to_string(), param4.to_string()])),
[cmd, param1, param2, param3, param4, param5] => Ok(Params::Five([cmd.to_string(), param1.to_string(), param2.to_string(), param3.to_string(), param4.to_string(), param5.to_string()])),
slice => Err(slice)
}
}
}
impl<T: ToString> TryFrom<Vec<T>> for Params {
type Error = Vec<T>;
fn try_from(mut v: Vec<T>) -> Result<Params, Vec<T>> {
match v.len() {
1 => Ok(Params::Zero([v.remove(0).to_string()])),
2 => Ok(Params::One([v.remove(0).to_string(), v.remove(0).to_string()])),
3 => Ok(Params::Two([v.remove(0).to_string(), v.remove(0).to_string(), v.remove(0).to_string()])),
4 => Ok(Params::Three([v.remove(0).to_string(), v.remove(0).to_string(), v.remove(0).to_string(), v.remove(0).to_string()])),
5 => Ok(Params::Four([v.remove(0).to_string(), v.remove(0).to_string(), v.remove(0).to_string(), v.remove(0).to_string(), v.remove(0).to_string()])),
6 => Ok(Params::Five([v.remove(0).to_string(), v.remove(0).to_string(), v.remove(0).to_string(), v.remove(0).to_string(), v.remove(0).to_string(), v.remove(0).to_string()])),
_ => Err(v)
}
}
}
#[derive(Debug)]
pub struct Command {
params: Params,
terminal: bool
}
impl Command {
pub fn terminal(args: impl Into<Params>) -> Command {
Command {
params: args.into(),
terminal: true
}
}
pub fn try_from<P: TryInto<Params>>(args: P) -> Result<Command, P::Error> {
Ok(Command {
params: args.try_into()?,
terminal: false
})
}
pub fn try_terminal<P: TryInto<Params>>(args: P) -> Result<Command, P::Error> {
Ok(Command {
params: args.try_into()?,
terminal: true
})
}
}
impl<P: Into<Params>> From<P> for Command {
fn from(args: P) -> Command {
Command {
params: args.into(),
terminal: false
}
}
}
#[derive(Debug)]
pub struct Image {
pub base64_data: String,
pub is_template: bool
}
impl Image {
pub fn template<T: TryInto<Image>>(img: T) -> Result<Image, T::Error> {
let mut result = img.try_into()?;
result.is_template = true;
Ok(result)
}
}
impl From<String> for Image {
fn from(base64_data: String) -> Image {
Image {
base64_data,
is_template: false
}
}
}
#[cfg(feature = "base64")]
impl From<Vec<u8>> for Image {
fn from(input: Vec<u8>) -> Image {
Image {
base64_data: base64::encode(&input),
is_template: false
}
}
}
#[cfg(feature = "base64")]
impl<T: ?Sized + AsRef<[u8]>> From<&T> for Image {
fn from(input: &T) -> Image {
Image {
base64_data: base64::encode(input),
is_template: false
}
}
}
#[cfg(all(feature = "base64", feature = "image"))]
impl TryFrom<DynamicImage> for Image {
type Error = ImageError;
fn try_from(img: DynamicImage) -> ImageResult<Image> {
let mut buf = Vec::default();
img.write_to(&mut buf, PNG)?;
Ok(Image::from(&buf))
}
}
#[derive(Debug, Default)]
pub struct ContentItem {
pub text: String,
pub extra: Option<Extra>,
pub href: Option<Url>,
pub color: Option<Color>,
pub font: Option<String>,
pub command: Option<Command>,
pub refresh: bool,
pub image: Option<Image>
}
impl ContentItem {
pub fn new(text: impl ToString) -> ContentItem {
ContentItem {
text: text.to_string(),
..ContentItem::default()
}
}
pub fn sub(mut self, items: impl IntoIterator<Item = MenuItem>) -> Self {
self.extra = Some(Extra::Submenu(Menu::from_iter(items)));
self
}
pub fn href(mut self, href: impl IntoUrl) -> Result<Self, url::ParseError> {
self.href = Some(href.into_url()?);
Ok(self)
}
pub fn color(mut self, color: impl IntoColor) -> Result<Self, ColorParseError> {
self.color = Some(color.into_color()?);
Ok(self)
}
pub fn font(mut self, font: impl ToString) -> Self {
self.font = Some(font.to_string());
self
}
pub fn command(mut self, cmd: impl Into<Command>) -> Self {
self.command = Some(cmd.into());
self
}
pub fn refresh(mut self) -> Self {
self.refresh = true;
self
}
pub fn alt(mut self, alt: impl Into<ContentItem>) -> Self {
self.extra = Some(Extra::Alternate(Box::new(alt.into())));
self
}
pub fn template_image<T: TryInto<Image>>(mut self, img: T) -> Result<Self, T::Error> {
self.image = Some(Image::template(img)?);
Ok(self)
}
pub fn image<T: TryInto<Image>>(mut self, img: T) -> Result<Self, T::Error> {
self.image = Some(img.try_into()?);
Ok(self)
}
fn render(&self, f: &mut fmt::Formatter<'_>, is_alt: bool) -> fmt::Result {
write!(f, "{}", self.text.replace('|', "¦").replace('\n', " "))?;
let mut rendered_params = BTreeMap::default();
if let Some(ref href) = self.href {
rendered_params.insert("href".into(), href.to_string());
}
if let Some(ref color) = self.color {
rendered_params.insert("color".into(), format!("#{:02x}{:02x}{:02x}", color.r, color.g, color.b));
}
if let Some(ref font) = self.font {
rendered_params.insert("font".into(), font.clone());
}
if let Some(ref cmd) = self.command {
for (i, param) in cmd.params.iter().enumerate() {
rendered_params.insert(if i == 0 { "bash".into() } else { format!("param{}", i) }, param.clone());
}
if !cmd.terminal {
rendered_params.insert("terminal".into(), "false".into());
}
}
if self.refresh {
rendered_params.insert("refresh".into(), "true".into());
}
if is_alt {
rendered_params.insert("alternate".into(), "true".into());
}
if let Some(ref img) = self.image {
rendered_params.insert(if img.is_template { "templateImage" } else { "image" }.into(), img.base64_data.clone());
}
if !rendered_params.is_empty() {
write!(f, " |")?;
for (name, value) in rendered_params {
let quoted_value = if value.contains(' ') {
format!("\"{}\"", value)
} else {
value
};
write!(f, " {}={}", name, quoted_value)?;
}
}
writeln!(f)?;
match &self.extra {
Some(Extra::Alternate(ref alt)) => { alt.render(f, true)?; }
Some(Extra::Submenu(ref sub)) => {
let sub_fmt = format!("{}", sub);
for line in sub_fmt.lines() {
writeln!(f, "--{}", line)?;
}
}
None => {}
}
Ok(())
}
}
impl fmt::Display for ContentItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.render(f, false)
}
}
#[derive(Debug)]
pub enum MenuItem {
Content(ContentItem),
Sep
}
impl MenuItem {
pub fn new(text: impl fmt::Display) -> MenuItem {
MenuItem::Content(ContentItem::new(text))
}
}
impl Default for MenuItem {
fn default() -> MenuItem {
MenuItem::Content(ContentItem::default())
}
}
impl From<ContentItem> for MenuItem {
fn from(i: ContentItem) -> MenuItem {
MenuItem::Content(i)
}
}
impl fmt::Display for MenuItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MenuItem::Content(content) => write!(f, "{}", content),
MenuItem::Sep => writeln!(f, "---")
}
}
}
#[derive(Debug, Default)]
pub struct Menu(pub Vec<MenuItem>);
impl<A: Into<MenuItem>> FromIterator<A> for Menu {
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Menu {
Menu(iter.into_iter().map(Into::into).collect())
}
}
impl fmt::Display for Menu {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for menu_item in &self.0 {
write!(f, "{}", menu_item)?;
}
Ok(())
}
}