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:
A | src/colors.rs | | | 180 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | src/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))
}
}