#![feature(conservative_impl_trait)]
extern crate irc;
extern crate clap;
extern crate futures;
extern crate tokio_core;
use irc::client::prelude as ircp;
use irc::client::prelude::{Client, ClientExt};
use irc::client::{PackedIrcClient};
use tokio_core::reactor::Handle;
use futures::{Future, Stream};
use std::borrow::Cow;
mod irc_util;
pub trait IrclapResponseStream {
fn send_message(&self, msg: &str) -> Result<(), irc::error::IrcError>;
}
pub trait IrclapContextMapper {
fn prepare_command_args<'a>(&'a self, args: Vec<&'a str>, msg: &'a ircp::Message) -> Vec<Cow<'a, str>>;
}
pub trait IrclapCommandProcessor {
fn process_matches<'a, RS>(&self, matches: clap::ArgMatches<'a>, resp: RS)
where RS: IrclapResponseStream + 'a;
}
impl<F> IrclapCommandProcessor for F where F: for<'af> Fn(clap::ArgMatches<'af>, Box<IrclapResponseStream + 'af>) {
fn process_matches<'a, RS>(&self, matches: clap::ArgMatches<'a>, resp: RS)
where RS: IrclapResponseStream + 'a{
let rstream = Box::new(resp) as Box<IrclapResponseStream + 'a>;
(self)(matches, rstream)
}
}
pub struct IrclapSimpleContextMapping {
pub channel: Option<String>,
pub username: Option<String>,
}
impl IrclapSimpleContextMapping {
pub fn none() -> IrclapSimpleContextMapping {
IrclapSimpleContextMapping{
channel: None,
username: None,
}
}
pub fn user_only(username: String) -> IrclapSimpleContextMapping {
IrclapSimpleContextMapping {
channel: None,
username: Some(username),
}
}
}
fn arg_tuple_opt<'a>(arg: &'a Option<String>, value: Option<&'a str>) -> Option<(&'a str, &'a str)> {
arg.as_ref().map(String::as_str).and_then(|a| value.map(|v| (a, v)))
}
fn push_arg_tuple<'a>(args: &mut Vec<&'a str>, arg: &'a Option<String>, value: Option<&'a str>) {
for (a, v) in arg_tuple_opt(arg, value) {
args.push(a);
args.push(v);
}
}
impl IrclapContextMapper for IrclapSimpleContextMapping {
fn prepare_command_args<'a>(&'a self, mut args: Vec<&'a str>, msg: &'a ircp::Message) -> Vec<Cow<'a, str>> {
push_arg_tuple(&mut args, &self.channel, msg.response_target());
push_arg_tuple(&mut args, &self.username, msg.source_nickname());
args.into_iter().map(Cow::from).collect()
}
}
struct IrclapProcessor<CM, CP> {
mapper: CM,
processor: CP,
}
impl<CM, CP> IrclapProcessor<CM, CP> {
fn new(mapper: CM, processor: CP) -> IrclapProcessor<CM, CP> {
IrclapProcessor {
mapper: mapper,
processor: processor,
}
}
}
fn process_single_message<'a, CM, CP>(
app: clap::App<'a, 'a>,
context: &IrclapProcessor<CM, CP>,
client: &ircp::IrcClient,
msg: ircp::Message)
where CM: IrclapContextMapper,
CP: IrclapCommandProcessor {
if let Some(command) = irc_util::extract_command(client.current_nickname(), &msg) {
let args:Vec<&str> = command.split_whitespace().collect();
let args = context.mapper.prepare_command_args(args, &msg);
let out_stream = irc_util::IrcResponseStream::new(&client, msg.response_target().unwrap());
match app.get_matches_from_safe(args.iter().map(Cow::as_ref)) {
Ok(matches) => context.processor.process_matches(matches, out_stream),
Err(e) => {
let _ = out_stream.send_message(&format!("Argument error: {:?}", e));
}
}
}
}
fn process_message_streams<'a, CM, CP>(
app: clap::App<'a, 'a>,
context: IrclapProcessor<CM, CP>,
client: ircp::IrcClient)
-> impl Future<Item=(), Error=irc::error::IrcError> + 'a
where CM: IrclapContextMapper + 'a,
CP: IrclapCommandProcessor + 'a {
client
.stream()
.filter(|m| {println!("{:?}", m); m.response_target().is_some()})
.for_each(move |msg| {
process_single_message(app.clone(), &context, &client, msg);
Ok(())
})
}
pub fn new_irclap_future<'a, CM, CP>(
handle: Handle,
cfg: &'a ircp::Config,
app: clap::App<'a, 'a>,
mapper: CM,
processor: CP)
-> impl Future<Item=(), Error=irc::error::IrcError> + 'a
where CM: IrclapContextMapper + 'a,
CP: IrclapCommandProcessor + 'a{
let ctxt = IrclapProcessor::new(mapper, processor);
let irc_client_creator = ircp::IrcClient::new_future(handle, cfg).unwrap();
let complete_app = app.setting(clap::AppSettings::NoBinaryName);
irc_client_creator
.and_then(|packed_client| packed_client.0.identify().map(|_| packed_client))
.and_then(move |PackedIrcClient(client, future)| {
future.join(process_message_streams(complete_app, ctxt, client))
}).map(|_| ())
}