mx-sanebot: add command to search for torrents
This commit is contained in:
parent
7c1961eba8
commit
fa5bc18721
|
@ -10,21 +10,107 @@ mod tt {
|
||||||
pub(super) use super::parsing::{
|
pub(super) use super::parsing::{
|
||||||
Either,
|
Either,
|
||||||
Lit,
|
Lit,
|
||||||
|
Maybe,
|
||||||
|
Nul,
|
||||||
|
OneOrMore,
|
||||||
Then,
|
Then,
|
||||||
};
|
};
|
||||||
use crate::ilit;
|
use crate::{ilit, lit};
|
||||||
|
|
||||||
// grammar:
|
// grammar:
|
||||||
// REQUEST = <!> (HELP | BT | BT-ADD)
|
// REQUEST = <!> (HELP | BT-REQ)
|
||||||
|
//
|
||||||
// HELP = <help>
|
// HELP = <help>
|
||||||
// BT = <bt>
|
// BT-REQ = <bt> [(BT-SEARCH | BT-ADD)]
|
||||||
// BT-ADD = <bt-add> MAYBE_ARGS
|
//
|
||||||
|
// BT-SEARCH = <search> ARGS
|
||||||
|
// BT-ADD = <add> ARGS
|
||||||
|
//
|
||||||
// MAYBE_ARGS = [SPACE [ARGS]]
|
// MAYBE_ARGS = [SPACE [ARGS]]
|
||||||
// ARGS = ARG MAYBE_ARGS
|
// ARGS = ARG MAYBE_ARGS
|
||||||
// ARG = (not SPACE) [ARG]
|
// ARG = NOT-SPACE [ARG]
|
||||||
pub(super) type Request = Then<Bang, Either<Help, Bt>>;
|
//
|
||||||
|
// NOT-SPACE = (ALPHA | NUM | SPECIAL)
|
||||||
|
pub(super) type Request = Then<Bang, Either<Help, BtReq>>;
|
||||||
|
|
||||||
pub(super) type Bang = Lit<{ '!' as u8 }>;
|
pub(super) type Bang = Lit<{ '!' as u8 }>;
|
||||||
|
pub(super) type Space = Lit<{ ' ' as u8 }>;
|
||||||
|
pub(super) type Alpha = Either<
|
||||||
|
ilit!('A'), Either<
|
||||||
|
ilit!('B'), Either<
|
||||||
|
ilit!('C'), Either<
|
||||||
|
ilit!('D'), Either<
|
||||||
|
ilit!('E'), Either<
|
||||||
|
ilit!('F'), Either<
|
||||||
|
ilit!('G'), Either<
|
||||||
|
ilit!('H'), Either<
|
||||||
|
ilit!('I'), Either<
|
||||||
|
ilit!('J'), Either<
|
||||||
|
ilit!('K'), Either<
|
||||||
|
ilit!('L'), Either<
|
||||||
|
ilit!('M'), Either<
|
||||||
|
ilit!('N'), Either<
|
||||||
|
ilit!('O'), Either<
|
||||||
|
ilit!('P'), Either<
|
||||||
|
ilit!('Q'), Either<
|
||||||
|
ilit!('R'), Either<
|
||||||
|
ilit!('S'), Either<
|
||||||
|
ilit!('T'), Either<
|
||||||
|
ilit!('U'), Either<
|
||||||
|
ilit!('V'), Either<
|
||||||
|
ilit!('W'), Either<
|
||||||
|
ilit!('X'), Either<
|
||||||
|
ilit!('Y'),
|
||||||
|
ilit!('Z'),
|
||||||
|
>>>>>>>>>>>>>>>>>>>>>>>>>;
|
||||||
|
pub(super) type Dig = Either<
|
||||||
|
lit!('0'), Either<
|
||||||
|
lit!('1'), Either<
|
||||||
|
lit!('2'), Either<
|
||||||
|
lit!('3'), Either<
|
||||||
|
lit!('4'), Either<
|
||||||
|
lit!('5'), Either<
|
||||||
|
lit!('6'), Either<
|
||||||
|
lit!('7'), Either<
|
||||||
|
lit!('8'),
|
||||||
|
lit!('9'),
|
||||||
|
>>>>>>>>>;
|
||||||
|
pub(super) type Symbol = Either<
|
||||||
|
Bang, Either<
|
||||||
|
lit!('@'), Either<
|
||||||
|
lit!('#'), Either<
|
||||||
|
lit!('$'), Either<
|
||||||
|
lit!('%'), Either<
|
||||||
|
lit!('^'), Either<
|
||||||
|
lit!('&'), Either<
|
||||||
|
lit!('*'), Either<
|
||||||
|
lit!('('), Either<
|
||||||
|
lit!(')'), Either<
|
||||||
|
lit!('-'), Either<
|
||||||
|
lit!('_'), Either<
|
||||||
|
lit!('+'), Either<
|
||||||
|
lit!('='), Either<
|
||||||
|
lit!('['), Either<
|
||||||
|
lit!('{'), Either<
|
||||||
|
lit!(']'), Either<
|
||||||
|
lit!('}'), Either<
|
||||||
|
lit!('\\'), Either<
|
||||||
|
lit!('|'), Either<
|
||||||
|
lit!(';'), Either<
|
||||||
|
lit!(':'), Either<
|
||||||
|
lit!('\''), Either<
|
||||||
|
lit!('"'), Either<
|
||||||
|
lit!(','), Either<
|
||||||
|
lit!('<'), Either<
|
||||||
|
lit!('.'), Either<
|
||||||
|
lit!('>'), Either<
|
||||||
|
lit!('/'), Either<
|
||||||
|
lit!('?'), Either<
|
||||||
|
lit!('`'),
|
||||||
|
lit!('~'),
|
||||||
|
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;
|
||||||
|
// pub(super) type Whitespace = Then<Space, Maybe<Box<Whitespace>>>;
|
||||||
|
pub(super) type Whitespace = OneOrMore<Space>;
|
||||||
|
|
||||||
pub(super) type Help = Then<
|
pub(super) type Help = Then<
|
||||||
ilit!('H'), Then<
|
ilit!('H'), Then<
|
||||||
|
@ -33,10 +119,30 @@ mod tt {
|
||||||
ilit!('P'),
|
ilit!('P'),
|
||||||
>>>;
|
>>>;
|
||||||
|
|
||||||
|
pub(super) type BtReq = Then<Bt, Maybe<Then<Whitespace, BtSearch>>>;
|
||||||
|
|
||||||
pub(super) type Bt = Then<
|
pub(super) type Bt = Then<
|
||||||
ilit!('B'),
|
ilit!('B'),
|
||||||
ilit!('T'),
|
ilit!('T'),
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
pub(super) type BtSearch = Then<Search, OneOrMore<SpacedArg>>;
|
||||||
|
|
||||||
|
pub(super) type Search = Then<
|
||||||
|
ilit!('S'), Then<
|
||||||
|
ilit!('E'), Then<
|
||||||
|
ilit!('A'), Then<
|
||||||
|
ilit!('R'), Then<
|
||||||
|
ilit!('C'),
|
||||||
|
ilit!('H'),
|
||||||
|
>>>>>;
|
||||||
|
|
||||||
|
pub(super) type SpacedArg = Then<Whitespace, Arg>;
|
||||||
|
pub(super) type Arg = OneOrMore<Either<
|
||||||
|
Alpha, Either<
|
||||||
|
Dig,
|
||||||
|
Symbol
|
||||||
|
>>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct MessageHandler;
|
pub struct MessageHandler;
|
||||||
|
@ -63,13 +169,20 @@ impl MessageHandler {
|
||||||
enum Request {
|
enum Request {
|
||||||
Help,
|
Help,
|
||||||
Bt,
|
Bt,
|
||||||
|
BtSearch(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<tt::Request> for Request {
|
impl From<tt::Request> for Request {
|
||||||
fn from(t: tt::Request) -> Self {
|
fn from(t: tt::Request) -> Self {
|
||||||
match t {
|
match t {
|
||||||
tt::Then(_bang, tt::Either::A(_help)) => Self::Help,
|
tt::Then(_bang, tt::Either::A(_help)) => Self::Help,
|
||||||
tt::Then(_bang, tt::Either::B(_bt)) => Self::Bt,
|
tt::Then(_bang, tt::Either::B(bt_req)) => match bt_req {
|
||||||
|
tt::Then(_bt, tt::Either::A(tt::Then(_ws, bt_search))) => {
|
||||||
|
let tt::Then(_, spaced_args) = bt_search;
|
||||||
|
Self::BtSearch(spaced_args.to_string())
|
||||||
|
},
|
||||||
|
tt::Then(_bt, tt::Either::B(tt::Nul)) => Self::Bt,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,6 +200,16 @@ impl Request {
|
||||||
).unwrap_or_else(||
|
).unwrap_or_else(||
|
||||||
"failed to retrieve torrent status".to_owned())
|
"failed to retrieve torrent status".to_owned())
|
||||||
),
|
),
|
||||||
|
Request::BtSearch(phrase) => Response::BtSearch(
|
||||||
|
process::Command::new("sane-bt-search")
|
||||||
|
.args([&*phrase])
|
||||||
|
.output()
|
||||||
|
.ok()
|
||||||
|
.and_then(|output|
|
||||||
|
str::from_utf8(&output.stdout).ok().map(ToOwned::to_owned)
|
||||||
|
).unwrap_or_else(||
|
||||||
|
"failed to complete torrent search".to_owned())
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,6 +217,7 @@ impl Request {
|
||||||
enum Response {
|
enum Response {
|
||||||
Help,
|
Help,
|
||||||
Bt(String),
|
Bt(String),
|
||||||
|
BtSearch(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Response {
|
impl fmt::Display for Response {
|
||||||
|
@ -103,8 +227,10 @@ impl fmt::Display for Response {
|
||||||
write!(f, "commands:\n")?;
|
write!(f, "commands:\n")?;
|
||||||
write!(f, " !help => show this message\n")?;
|
write!(f, " !help => show this message\n")?;
|
||||||
write!(f, " !bt => show torrent statuses\n")?;
|
write!(f, " !bt => show torrent statuses\n")?;
|
||||||
|
write!(f, " !bt search <phrase> => search for torrents\n")?;
|
||||||
},
|
},
|
||||||
Response::Bt(stdout) => write!(f, "{}", stdout)?
|
Response::Bt(stdout) => write!(f, "{}", stdout)?,
|
||||||
|
Response::BtSearch(stdout) => write!(f, "{}", stdout)?,
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
/// for internal use.
|
/// for internal use.
|
||||||
/// parses only if the parser has no more bytes to yield.
|
/// parses only if the parser has no more bytes to yield.
|
||||||
struct Eof;
|
struct Eof;
|
||||||
|
@ -5,6 +7,10 @@ struct Eof;
|
||||||
/// literal byte (character).
|
/// literal byte (character).
|
||||||
pub struct Lit<const BYTE: u8>;
|
pub struct Lit<const BYTE: u8>;
|
||||||
|
|
||||||
|
/// parses without consuming any bytes from the parser.
|
||||||
|
/// used to construct strictly optional constructs.
|
||||||
|
pub struct Nul;
|
||||||
|
|
||||||
/// the two-item sequence of A followed by B.
|
/// the two-item sequence of A followed by B.
|
||||||
pub struct Then<A, B>(pub A, pub B);
|
pub struct Then<A, B>(pub A, pub B);
|
||||||
|
|
||||||
|
@ -14,6 +20,23 @@ pub enum Either<A, B> {
|
||||||
B(B),
|
B(B),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// parse A if possible, but don't error if it isn't present.
|
||||||
|
pub type Maybe<A> = Either<A, Nul>;
|
||||||
|
|
||||||
|
/// exists because Rust doesn't allow recursive type *aliases*.
|
||||||
|
pub struct OneOrMore<A>(Then<
|
||||||
|
A,
|
||||||
|
Maybe<Box<OneOrMore<A>>>
|
||||||
|
>);
|
||||||
|
|
||||||
|
// case-sensitive u8 character.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! lit {
|
||||||
|
($BYTE:literal) => {
|
||||||
|
Lit<{ $BYTE as u8 }>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// case-insensitive u8 character.
|
// case-insensitive u8 character.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! ilit {
|
macro_rules! ilit {
|
||||||
|
@ -72,6 +95,18 @@ impl Parse for Eof {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Parse for Nul {
|
||||||
|
fn consume<P: Parser>(p: P) -> PResult<P, Self> {
|
||||||
|
Ok((Self, p))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Nul {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<const BYTE: u8> Parse for Lit<BYTE> {
|
impl<const BYTE: u8> Parse for Lit<BYTE> {
|
||||||
fn consume<P: Parser>(p: P) -> PResult<P, Self> {
|
fn consume<P: Parser>(p: P) -> PResult<P, Self> {
|
||||||
let (_, p) = p.expect_byte(Some(BYTE))?;
|
let (_, p) = p.expect_byte(Some(BYTE))?;
|
||||||
|
@ -79,6 +114,12 @@ impl<const BYTE: u8> Parse for Lit<BYTE> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<const BYTE: u8> fmt::Display for Lit<BYTE> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", BYTE as char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<A: Parse, B: Parse> Parse for Then<A, B> {
|
impl<A: Parse, B: Parse> Parse for Then<A, B> {
|
||||||
fn consume<P: Parser>(p: P) -> PResult<P, Self> {
|
fn consume<P: Parser>(p: P) -> PResult<P, Self> {
|
||||||
let (a, p) = p.expect()?;
|
let (a, p) = p.expect()?;
|
||||||
|
@ -87,6 +128,12 @@ impl<A: Parse, B: Parse> Parse for Then<A, B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<A: fmt::Display, B: fmt::Display> fmt::Display for Then<A, B> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}{}", self.0, self.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<A: Parse, B: Parse> Parse for Either<A, B> {
|
impl<A: Parse, B: Parse> Parse for Either<A, B> {
|
||||||
fn consume<P: Parser>(p: P) -> PResult<P, Self> {
|
fn consume<P: Parser>(p: P) -> PResult<P, Self> {
|
||||||
let p = match p.expect() {
|
let p = match p.expect() {
|
||||||
|
@ -101,3 +148,36 @@ impl<A: Parse, B: Parse> Parse for Either<A, B> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<A: fmt::Display, B: fmt::Display> fmt::Display for Either<A, B> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::A(a) => write!(f, "{}", a),
|
||||||
|
Self::B(b) => write!(f, "{}", b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Parse> Parse for Box<T> {
|
||||||
|
fn consume<P: Parser>(p: P) -> PResult<P, Self> {
|
||||||
|
match T::consume(p) {
|
||||||
|
Ok((t, p)) => Ok((Box::new(t), p)),
|
||||||
|
Err(p) => Err(p),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Parse> Parse for OneOrMore<T> {
|
||||||
|
fn consume<P: Parser>(p: P) -> PResult<P, Self> {
|
||||||
|
match p.expect() {
|
||||||
|
Ok((t, p)) => Ok((Self(t), p)),
|
||||||
|
Err(p) => Err(p),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: fmt::Display> fmt::Display for OneOrMore<T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user