vortaroboto

Log | Files | Refs | README

commit 5c041add51536fb4d0fddb57185d0e34f4ccbe0c
parent 9e87d551efc57c7b10fef4be1504aa77423839c1
Author: tomvig38@gmail.com <tomvig38@gmail.com>
Date:   Wed, 20 Oct 2021 07:37:38 +0000

Aldonu koloron al la help'komando
Diffstat:
Asrc/colors.rs | 180+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.rs | 101++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
2 files changed, 270 insertions(+), 11 deletions(-)

diff --git a/src/colors.rs b/src/colors.rs @@ -0,0 +1,180 @@ +use irc::proto::Message; +use std::fmt::Display; +use std::str::FromStr; + +#[derive(Clone,Copy)] +pub enum ColorKind { + White, + Black, + Blue, + Green, + Red, + Brown, + Magenta, + Orange, + Yellow, + LightGreen, + Cyan, + LightCyan, + LightBlue, + Pink, + Grey, + LightGrey, +} + +impl ColorKind { + fn as_str(&self) -> &'static str { + match self { + Self::White => "00", + Self::Black => "01", + Self::Blue => "02", + Self::Green => "03", + Self::Red => "04", + Self::Brown => "05", + Self::Magenta => "06", + Self::Orange => "07", + Self::Yellow => "08", + Self::LightGreen => "09", + Self::Cyan => "10", + Self::LightCyan => "11", + Self::LightBlue => "12", + Self::Pink => "13", + Self::Grey => "14", + Self::LightGrey => "15", + } + } +} + +impl Display for ColorKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.as_str()) + } +} + +pub enum FormattingKind { + Bold, + Italics, + Underline, + Strikethrough, + Monospace, + Color { + fg: ColorKind, + bg: Option<ColorKind>, + }, + ReverseColor, + None, +} + +impl FormattingKind { + pub fn is_none(&self) -> bool { + match self { + Self::None => true, + _ => false + } + } +} + +impl Display for FormattingKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Self::Color { fg, bg } = self { + write!(f, "\u{0003}{}", fg)?; + if let Some(b) = bg { + write!(f, ",{}", b) + } else { + Ok(()) + } + } else { + write!( + f, + "{}", + match self { + Self::Bold => "\u{0002}", + Self::Italics => "\u{001D}", + Self::Underline => "\u{001F}", + Self::Strikethrough => "\u{001E}", + Self::Monospace => "\u{0011}", + Self::ReverseColor => "\u{0016}", + Self::None => "\u{000F}", + Self::Color { .. } => unreachable!(), + } + ) + } + } +} + +pub struct Formatted<T: Display> { + kinds: Vec<FormattingKind>, + content: T, +} + +impl<T: Display> Display for Formatted<T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.kinds.len() == 1 && self.kinds.get(0).unwrap().is_none() { + return self.content.fmt(f); + } + + for (i, k) in self.kinds.iter().enumerate() { + write!(f, "{}", k)?; + if let FormattingKind::Color { bg: None, .. } = k { + // Avoid eating , here + if self.content.to_string().starts_with(',') && i == self.kinds.len() - 1 { + write!( + f, + "{}{}", + FormattingKind::ReverseColor, + FormattingKind::ReverseColor + )?; + } + } + } + write!(f, "{}{}", self.content, FormattingKind::None) + } +} + +impl<T: Display> Formatted<T> { + pub fn new(kinds: Vec<FormattingKind>, content: T) -> Self { + Formatted { kinds, content } + } + + pub fn simple(kind: FormattingKind, content: T) -> Self { + Formatted { kinds: vec![kind], content} + } +} + +impl<'a> Formatted<&'a str> { + pub fn from_str<'b: 'a>(s: &'b str) -> Self { + Self::new(vec![FormattingKind::None], s) + } +} + +macro_rules! irc_fmt { + ($fmt:expr => $inner:expr) => { + Formatted::simple($fmt, $inner) + }; + ($($fmts:expr),+ => $inner:expr) => { + Formatted::new(vec![$($fmts),+], $inner) + }; +} + +#[cfg(test)] +mod test { + use super::{ColorKind, Formatted, FormattingKind}; + + #[test] + fn basic() { + let f = Formatted::from_str("abc"); + assert_eq!(f.to_string(), String::from("abc")) + } + + #[test] + fn does_not_eat() { + let f = irc_fmt!(FormattingKind::Color { fg: ColorKind::White, bg: None } => ",02"); + assert_ne!(f.to_string(), String::from("\u{0003}00,02\u{000F}")) + } + + #[test] + fn nested() { + let f = irc_fmt!(FormattingKind::Bold, FormattingKind::ReverseColor => "abc"); + assert_eq!(f.to_string(), String::from("\u{0002}\u{0016}abc\u{000F}")) + } +} diff --git a/src/main.rs b/src/main.rs @@ -6,6 +6,10 @@ use std::str::FromStr; use log::{debug, info}; use simple_logger::SimpleLogger; +#[macro_use] +mod colors; +use colors::{ColorKind, Formatted, FormattingKind}; + const NICK: &'static str = "vortaroboto"; const TROVI_URL: &'static str = "http://www.simplavortaro.org/api/v1/trovi"; const VORTO_URL: &'static str = "http://www.simplavortaro.org/api/v1/vorto"; @@ -139,7 +143,7 @@ async fn main() -> irc::error::Result<()> { } else if msg.contains(client.current_nickname()) { sender.send_privmsg(target, format!( "Saluton ! Mi estas {} la roboto kiun vi povas demandi pri vortoj kaj tradukoj ! Tajpu !helpu por pli'sciiĝi !", - client.current_nickname() + irc_fmt!(FormattingKind::Bold => client.current_nickname()) ))?; } } @@ -172,9 +176,7 @@ macro_rules! wrap_handler { async fn handle_command(cmd: &str) -> Option<String> { let mut splitted = cmd.split_ascii_whitespace(); match splitted.next() { - Some("helpu") => Some(String::from( - "helpu: montri ĉi tio mesaĝo\r\ndifinu [vorto] [numero]: difini [vorto], uzas la ebla [numero]\r\ntraduku [vorto] [lingvo]: traduko [vorto] al la (ebla) [lingvo]\r\nvortfarado [vorto] [numero]: Donu la vortfarado [numero] por [vorto]", - )), + Some("helpu" | "h") => Some(helpu()), Some("difinu" | "d") => { if let Some(w) = splitted.next() { let index = parse_or_default!(NumerSelektilo, splitted); @@ -203,6 +205,84 @@ async fn handle_command(cmd: &str) -> Option<String> { } } +fn helpu() -> String { + const KOMANDOJ: &[(&'static str, &[&'static str], &[&'static str], &'static str)] = &[ + ("helpu", &[], &[], "montri ĉi tio mesaĝo"), + ( + "difinu", + &["vorto"], + &["numero"], + "difini {vorto}, uzas la ebla [numero] difino", + ), + ( + "traduku", + &["vorto"], + &["lingvokodo"], + "traduki {vorto}, uzas la ebla [numero]", + ), + ( + "vortfarado", + &["vorto"], + &["numero"], + "doni la vortfarado [numero] por {vorto}", + ), + ]; + + fn format_args(am: &[&str], left: char, right: char, c: ColorKind) -> String { + am.iter() + .map(|a| { + format!( + "{}{}{}", + left, + irc_fmt!(FormattingKind::Color { fg: c, bg: None } => a), + right, + ) + }) + .collect::<Vec<String>>() + .join(" ") + } + + let lineoj: Vec<String> = KOMANDOJ + .iter() + .map(|e| match e { + (k, &[], &[], h) => { + format!( + "{}: {}", + irc_fmt!(FormattingKind::Color {fg: ColorKind::Green, bg: None} => k), + h + ) + } + (k, am, &[], h) => { + format!( + "{} {}: {}", + irc_fmt!(FormattingKind::Color {fg: ColorKind::Green, bg: None} => k), + format_args(am, '{', '}', ColorKind::Red), + h + ) + } + (k, &[], ao, h) => { + format!( + "{} {}: {}", + irc_fmt!(FormattingKind::Color {fg: ColorKind::Green, bg: None} => k), + format_args(ao, '[', ']', ColorKind::Blue), + h + ) + } + (k, am, ao, h) => { + format!( + "{} {} {}: {}", + irc_fmt!(FormattingKind::Color {fg: ColorKind::Green, bg: None} => k), + format_args(am, '{', '}', ColorKind::Red), + format_args(ao, '[', ']', ColorKind::Blue), + h + ) + } + }) + .collect(); + + lineoj.join(LINEO_SEP) +} + async fn define_word(vorto: &str, difino: NumerSelektilo) -> Result<String, String> { let res: Vorto = trovu(vorto).await?; @@ -329,13 +409,12 @@ async fn vortfarado(vorto: &str, index: NumerSelektilo) -> Result<String, String } } else { // Donu cxiuj trancxajxoj - let vj = vf.iter().enumerate().map(|(index, v)| { - format!( - "{}: {}", - index + 1, - make_partoj(v) - ) - }).collect::<Vec<String>>().join(LINEO_SEP); + let vj = vf + .iter() + .enumerate() + .map(|(index, v)| format!("{}: {}", index + 1, make_partoj(v))) + .collect::<Vec<String>>() + .join(LINEO_SEP); Ok(format!("Vortfaradoj por \"{}\":\r\n{}", vorto, vj)) } }