pub mod client;
pub mod server;
pub mod util;
pub use client::Sentences as ClientSentences;
pub use server::Sentences as ServerSentences;
macro_rules! impl_words {
(
$(
$(#[$attr:meta])+
$name:ident($word:tt),
)*
) => {
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) enum Word {
Arg,
EOL,
$(
/// Protocol word.
$(#[$attr])*
#[allow(dead_code)]
$name,
)*
}
impl Word {
pub(crate) fn decode(raw: Option<&str>) -> Option<Self> {
if let Some(raw) = raw {
match raw {
$($word => Some(Self::$name),)*
_ => None
}
} else {
Some(Self::EOL)
}
}
pub(crate) fn decode_words<T: AsRef<str>>(raw: &[T]) -> Vec<Option<Self>> {
let mut words = Vec::new();
for r in raw.iter() {
words.push(Self::decode(Some(r.as_ref())));
}
words.push(Some(Self::EOL));
words
}
pub(crate) fn encode(&self) -> Option<&str> {
match self {
Self::Arg | Self::EOL => None,
$(Self::$name => Some($word),)*
}
}
pub(crate) fn matches(&self, other: Option<&Option<Self>>) -> bool {
if let Some(other) = other {
if self == &Word::Arg {
true
} else if let Some(other) = other {
self == other
} else {
self == &Word::EOL
}
} else {
false
}
}
pub(crate) fn matches_until_end(&self, start: usize, others: &[Option<Self>]) -> bool {
for i in start..others.len() {
if i == others.len() {
return others[i] == Some(Self::EOL);
}
if !self.matches(others.get(i)) {
return false;
}
}
true
}
}
};
}
macro_rules! impl_sentences {
(
$(
$(#[$attr:meta])+
$name:ident(
{
$($wordidx:tt: $word:ident,)*
},
{
$(
$(#[$argattr:meta])+
$argidx:tt: $arg:ident,
)*
}
$(
,{
$(#[$varargattr:meta])+
$varargidx:tt...: $vararg:ident
}
)?
),
)*
) => {
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Sentences {
$(
$(#[$attr])*
$name {
$(
$(#[$argattr])*
$arg: String,
)*
$(
$(#[$varargattr])*
$vararg: Vec<String>,
)*
},
)*
}
impl Sentences {
pub(crate) fn decode(raw: Vec<String>) -> Option<Sentences> {
use super::{Word::*, *};
use Sentences::*;
let words = Word::decode_words(raw.as_slice());
$(
if true
$(&& $word.matches(words.get($wordidx)))*
$(&& Arg.matches_until_end($varargidx, &words))*
{
return Some($name {
$($arg: raw[$argidx].to_owned(),)*
$($vararg: raw[$varargidx..].to_owned(),)*
})
}
)*
None
}
pub(crate) fn encode(&self) -> Vec<&str> {
use super::Word::*;
match self {
$(
Self::$name {
$($arg,)*
$($vararg,)*
} => {
#[allow(unused_mut)]
let mut words = vec![
$(
$word.encode(),
)*
];
$(
words[$argidx] = Some($arg);
)*
$(
for vararg in $vararg {
words.push(Some(vararg));
}
)*
words
.into_iter()
.flatten()
.collect()
}
)*
}
}
}
};
}
#[allow(unused_macros)]
macro_rules! test_encode_decode {
([$($word:expr$(,)?)+] <=> $expected:expr) => {
assert_eq!(
Sentences::decode(vec![
$(String::from($word),)*
]),
Some($expected)
);
assert_eq!(
vec![
$(String::from($word),)*
],
$expected.encode()
);
};
}
impl_words! {
Begin("BEGIN"),
Client("CLIENT"),
Cmd("CMD"),
CmdDesc("CMDDESC"),
Desc("DESC"),
End("END"),
Enum("ENUM"),
Err("ERR"),
Fsd("FSD"),
FsdSet("FSD-SET"),
Get("GET"),
Goodbye("Goodbye"),
Help("HELP"),
InstCmd("INSTCMD"),
List("LIST"),
Login("LOGIN"),
Logout("LOGOUT"),
Master("MASTER"),
NetworkVersion("NETVER"),
NumLogins("NUMLOGINS"),
Ok("OK"),
Password("PASSWORD"),
Range("RANGE"),
Rw("RW"),
Set("SET"),
StartTLS("STARTTLS"),
Type("TYPE"),
Ups("UPS"),
UpsDesc("UPSDESC"),
Username("USERNAME"),
Var("VAR"),
Version("VERSION"),
}
pub(crate) use impl_sentences;
#[cfg(test)]
pub(crate) use test_encode_decode;