diff --git a/src/conn.rs b/src/conn.rs index 9961f7a..b057c50 100644 --- a/src/conn.rs +++ b/src/conn.rs @@ -1,14 +1,20 @@ +//! Thread-safe connections on any IrcWriters and IrcReaders +#![experimental] use std::sync::Mutex; use std::io::{BufferedReader, BufferedWriter, IoResult, TcpStream}; use data::kinds::{IrcWriter, IrcReader}; use data::message::Message; +/// A thread-safe connection +#[experimental] pub struct Connection where T: IrcWriter, U: IrcReader { writer: Mutex, reader: Mutex, } impl Connection, BufferedReader> { + /// Creates a thread-safe TCP connection to the specified server + #[experimental] pub fn connect(host: &str, port: u16) -> IoResult, BufferedReader>> { let socket = try!(TcpStream::connect(host, port)); Ok(Connection::new(BufferedWriter::new(socket.clone()), BufferedReader::new(socket))) @@ -16,6 +22,8 @@ impl Connection, BufferedReader> { } impl Connection where T: IrcWriter, U: IrcReader { + /// Creates a new connection from any arbitrary IrcWriter and IrcReader + #[experimental] pub fn new(writer: T, reader: U) -> Connection { Connection { writer: Mutex::new(writer), @@ -23,12 +31,16 @@ impl Connection where T: IrcWriter, U: IrcReader { } } + /// Sends a Message over this connection + #[experimental] pub fn send(&self, message: Message) -> IoResult<()> { let mut send = self.writer.lock(); try!(send.write_str(message.into_string()[])); send.flush() } + /// Receives a single line from this connection + #[experimental] pub fn recv(&self) -> IoResult { self.reader.lock().read_line() } diff --git a/src/data/command.rs b/src/data/command.rs index 2c15378..1bcaa07 100644 --- a/src/data/command.rs +++ b/src/data/command.rs @@ -1,101 +1,104 @@ +//! Enumeration of all available client commands +#![stable] use std::io::{InvalidInput, IoError, IoResult}; use data::message::Message; -/// List of all client commands as defined in RFC 2812. +/// List of all client commands as defined in [RFC 2812](http://tools.ietf.org/html/rfc2812) +#[stable] #[deriving(Show, PartialEq)] pub enum Command { // 3.1 Connection Registration - /// PASS + /// PASS password PASS(String), - /// NICK + /// NICK nickname NICK(String), - /// USER * + /// USER user mode * realname USER(String, String, String), - /// OPER + /// OPER name password OPER(String, String), - /// MODE - /// MODE [] + /// MODE nickname modes + /// MODE channel modes [modeparams] MODE(String, String, Option), - /// SERVICE + /// SERVICE nickname reserved distribution type reserved info SERVICE(String, String, String, String, String, String), - /// QUIT + /// QUIT Quit Message QUIT(Option), - /// SQUIT + /// SQUIT server comment SQUIT(String, String), // 3.2 Channel operations - /// JOIN [] + /// JOIN chanlist [chankeys] JOIN(String, Option), - /// PART [] + /// PART chanlist [Part Message] PART(String, Option), // MODE is already defined. // MODE(String, String, Option), - /// TOPIC [] + /// TOPIC channel [topic] TOPIC(String, Option), - /// NAMES [ []] + /// NAMES [chanlist [target]] NAMES(Option, Option), - /// LIST [ []] + /// LIST [chanlist [target]] LIST(Option, Option), - /// INVITE + /// INVITE nickname channel INVITE(String, String), - /// KICK [] + /// KICK chanlist userlist [comment] KICK(String, String, Option), // 3.3 Sending messages - /// PRIVMSG + /// PRIVMSG msgtarget text to be sent PRIVMSG(String, String), - /// NOTICE + /// NOTICE msgtarget text NOTICE(String, String), // 3.4 Server queries and commands - /// MOTD [] + /// MOTD [target] MOTD(Option), - /// LUSERS [ []] + /// LUSERS [mask [target]] LUSERS(Option, Option), - /// VERSION [] + /// VERSION [target] VERSION(Option), - /// STATS [ []] + /// STATS [query [target]] STATS(Option, Option), - /// LINKS [[] ] + /// LINKS [[remote server] server mask] LINKS(Option, Option), - /// TIME [] + /// TIME [target] TIME(Option), - /// CONNECT [] + /// CONNECT target server port [remote server] CONNECT(String, String, Option), - /// TRACE [] + /// TRACE [target] TRACE(Option), - /// ADMIN [] + /// ADMIN [target] ADMIN(Option), - /// INFO [] + /// INFO [target] INFO(Option), // 3.5 Service Query and Commands - /// SERVLIST [ []] + /// SERVLIST [mask [type]] SERVLIST(Option, Option), - /// SQUERY + /// SQUERY servicename text SQUERY(String, String), // 3.6 User based queries - /// WHO [ ["o"]] + /// WHO [mask ["o"]] WHO(Option, Option), - /// WHOIS [] + /// WHOIS [target] masklist WHOIS(Option, String), - /// WHOWAS [ []] + /// WHOWAS nicklist [count [target]] WHOWAS(String, Option, Option), // 3.7 Miscellaneous messages - /// KILL + /// KILL nickname comment KILL(String, String), - /// PING [] + /// PING server1 [server2] PING(String, Option), - /// PONG [] + /// PONG server [server2] PONG(String, Option), - /// ERROR + /// ERROR error message ERROR(String), // 4 Optional Features - /// AWAY [] + /// AWAY [text] AWAY(Option), /// REHASH REHASH, @@ -103,31 +106,33 @@ pub enum Command { DIE, /// RESTART RESTART, - /// SUMMON [ []] + /// SUMMON user [target [channel]] SUMMON(String, Option, Option), - /// USERS [] + /// USERS [target] USERS(Option), - /// WALLOPS + /// WALLOPS Text to be sent WALLOPS(String), - /// USERHOST + /// USERHOST space-separated nicklist USERHOST(Vec), - /// ISON + /// ISON space-separated nicklist ISON(Vec), // Non-RFC commands from InspIRCd - /// SAJOIN + /// SAJOIN nickname channel SAJOIN(String, String), - /// SAMODE [] + /// SAMODE target modes [modeparams] SAMODE(String, String, Option), - /// SANICK + /// SANICK old nickname new nickname SANICK(String, String), - /// SAPART + /// SAPART nickname reason SAPART(String, String), - /// SAQUIT + /// SAQUIT nickname reason SAQUIT(String, String), } impl Command { + /// Converts a Command into a Message + #[stable] pub fn to_message(self) -> Message { match self { PASS(p) => Message::new(None, "PASS", None, Some(p[])), @@ -221,6 +226,8 @@ impl Command { } } + /// Converts a Message into a Command + #[stable] pub fn from_message(m: Message) -> IoResult { Ok(if let "PASS" = m.command[] { if m.suffix.is_some() { @@ -738,6 +745,8 @@ impl Command { } } +/// Produces an invalid_input IoError +#[stable] fn invalid_input() -> IoError { IoError { kind: InvalidInput, diff --git a/src/data/config.rs b/src/data/config.rs index 5fd3d0d..3b65f26 100644 --- a/src/data/config.rs +++ b/src/data/config.rs @@ -1,22 +1,37 @@ +//! JSON configuration files using libserialize +#![stable] use std::collections::HashMap; use std::io::fs::File; use std::io::{InvalidInput, IoError, IoResult}; use serialize::json::decode; +/// Configuration data #[deriving(Clone, Decodable)] +#[unstable] pub struct Config { + /// A list of the owners of the bot by nickname pub owners: Vec, + /// The bot's nickname pub nickname: String, + /// The bot's username pub username: String, + /// The bot's real name pub realname: String, + /// The bot's password pub password: String, + /// The server to connect to pub server: String, + /// The port to connect on pub port: u16, + /// A list of channels to join on connection pub channels: Vec, + /// A map of additional options to be stored in config pub options: HashMap, } impl Config { + /// Loads a JSON configuration from the desired path. + #[stable] pub fn load(path: Path) -> IoResult { let mut file = try!(File::open(&path)); let data = try!(file.read_to_string()); @@ -27,10 +42,14 @@ impl Config { }) } + /// Loads a JSON configuration using the string as a UTF-8 path. + #[stable] pub fn load_utf8(path: &str) -> IoResult { Config::load(Path::new(path)) } + /// Determines whether or not the nickname provided is the owner of the bot. + #[stable] pub fn is_owner(&self, nickname: &str) -> bool { self.owners[].contains(&String::from_str(nickname)) } diff --git a/src/data/message.rs b/src/data/message.rs index 9665cd5..dac1b8a 100644 --- a/src/data/message.rs +++ b/src/data/message.rs @@ -1,14 +1,25 @@ +//! Messages to and from the server +#![experimental] use std::from_str::FromStr; +/// IRC Message data +#[experimental] #[deriving(Clone, PartialEq, Show)] pub struct Message { + /// The message prefix (or source) as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812) pub prefix: Option, + /// The IRC command as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812) pub command: String, + /// The command arguments pub args: Vec, + /// The message suffix as defined by [RFC 2812](http://tools.ietf.org/html/rfc2812) + /// This is the only part of the message that is allowed to contain spaces. pub suffix: Option, } impl Message { + /// Creates a new Message + #[experimental] pub fn new(prefix: Option<&str>, command: &str, args: Option>, suffix: Option<&str>) -> Message { Message { @@ -19,6 +30,8 @@ impl Message { } } + /// Converts a Message into a String according to the IRC protocol + #[experimental] pub fn into_string(&self) -> String { let mut ret = String::new(); if let Some(ref prefix) = self.prefix { diff --git a/src/data/mod.rs b/src/data/mod.rs index 4d4343f..474ba64 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,6 +1,16 @@ +//! Data related to IRC functionality +#![experimental] + pub mod kinds { + //! Trait definitions of appropriate Writers and Buffers for use with this library + #![unstable] + + /// Trait describing all possible Writers for this library + #[unstable] pub trait IrcWriter: Writer + Sized + Send + 'static {} impl IrcWriter for T where T: Writer + Sized + Send + 'static {} + /// Trait describing all possible Readers for this library + #[unstable] pub trait IrcReader: Buffer + Sized + Send + 'static {} impl IrcReader for T where T: Buffer + Sized + Send + 'static {} } diff --git a/src/lib.rs b/src/lib.rs index 2de2166..8c902f9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ //! A simple, thread-safe IRC client library. #![crate_name = "irc"] #![crate_type = "lib"] +#![unstable] #![feature(if_let)] #![feature(slicing_syntax)] diff --git a/src/server.rs b/src/server.rs index 0120c7c..06b735f 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,3 +1,5 @@ +//! Interface for working with IRC Servers +#![experimental] use std::io::{BufferedReader, BufferedWriter, IoResult, TcpStream}; use conn::Connection; use data::command::Command; @@ -5,18 +7,30 @@ use data::config::Config; use data::kinds::{IrcReader, IrcWriter}; use data::message::Message; +/// Trait describing core Server functionality +#[experimental] pub trait Server<'a, T, U> { + /// Gets the configuration being used with this Server + fn config(&self) -> &Config; + /// Sends a Command to this Server fn send(&self, _: Command) -> IoResult<()>; + /// Gets an Iterator over Messages received by this Server fn iter(&'a self) -> ServerIterator<'a, T, U>; } +/// A thread-safe implementation of an IRC Server connection +#[experimental] pub struct IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader { - pub conn: Connection, - pub config: Config + /// The thread-safe IRC connection + conn: Connection, + /// The configuration used with this connection + config: Config } impl<'a> IrcServer<'a, BufferedWriter, BufferedReader> { - pub fn new(config: &str) -> IoResult, BufferedReader>> { + /// Creates a new IRC Server connection from the specified configuration, connecting immediately. + #[experimental] + pub fn new(config: &str) -> IoResult, BufferedReader>> { let config = try!(Config::load_utf8(config)); let conn = try!(Connection::connect(config.server[], config.port)); Ok(IrcServer { @@ -26,7 +40,11 @@ impl<'a> IrcServer<'a, BufferedWriter, BufferedReader> { } } -impl<'a, T, U> Server<'a, T, U> for IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader{ +impl<'a, T, U> Server<'a, T, U> for IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader { + fn config(&self) -> &Config { + &self.config + } + fn send(&self, command: Command) -> IoResult<()> { self.conn.send(command.to_message()) } @@ -37,6 +55,8 @@ impl<'a, T, U> Server<'a, T, U> for IrcServer<'a, T, U> where T: IrcWriter, U: I } impl<'a, T, U> IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader { + /// Creates an IRC server from the specified configuration, and any arbitrary Connection + #[experimental] pub fn from_connection(config: &str, conn: Connection) -> IoResult> { Ok(IrcServer { conn: conn, @@ -45,11 +65,15 @@ impl<'a, T, U> IrcServer<'a, T, U> where T: IrcWriter, U: IrcReader { } } +/// An Iterator over an IrcServer's incoming Messages +#[experimental] pub struct ServerIterator<'a, T, U> where T: IrcWriter, U: IrcReader { pub server: &'a IrcServer<'a, T, U> } impl<'a, T, U> ServerIterator<'a, T, U> where T: IrcWriter, U: IrcReader { + /// Creates a new ServerIterator for the desired IrcServer + #[experimental] pub fn new(server: &'a IrcServer<'a, T, U>) -> ServerIterator<'a, T, U> { ServerIterator { server: server