From 899accaa2fad3d5e7baea5bd130437300dcb1b4e Mon Sep 17 00:00:00 2001 From: Aaron Weiss Date: Thu, 16 Nov 2017 15:53:19 +0100 Subject: [PATCH] Improved the documentation for irc::client::server. --- src/client/server/mod.rs | 240 ++++++++++++++++++++++++++++++++++--- src/client/server/utils.rs | 43 ++++++- 2 files changed, 261 insertions(+), 22 deletions(-) diff --git a/src/client/server/mod.rs b/src/client/server/mod.rs index b72cee0..e516bce 100644 --- a/src/client/server/mod.rs +++ b/src/client/server/mod.rs @@ -1,4 +1,50 @@ -//! Interface for working with IRC Servers. +//! The primary API for communicating with an IRC server. +//! +//! This API provides the ability to connect to an IRC server via the +//! [IrcServer](struct.IrcServer.html) type. The [Server](trait.Server.html) trait that +//! [IrcServer](struct.IrcServer.html) implements provides methods for communicating with this +//! server. An extension trait, [ServerExt](utils/trait.ServerExt.html), provides short-hand for +//! sending a variety of important messages without referring to their entries in +//! [proto::command](../../proto/command/enum.Command.html). +//! +//! # Examples +//! +//! Using these APIs, we can connect to a server and send a one-off message (in this case, +//! identifying with the server). +//! +//! ```no_run +//! # extern crate irc; +//! use irc::client::prelude::{IrcServer, ServerExt}; +//! +//! # fn main() { +//! let server = IrcServer::new("config.toml").unwrap(); +//! // identify comes from `ServerExt` +//! server.identify().unwrap(); +//! # } +//! ``` +//! +//! We can then use functions from [Server](trait.Server.html) to receive messages from the +//! server in a blocking fashion and perform any desired actions in response. The following code +//! performs a simple call-and-response when the bot's name is mentioned in a channel. +//! +//! ```no_run +//! # extern crate irc; +//! # use irc::client::prelude::{IrcServer, ServerExt}; +//! use irc::client::prelude::{Server, Command}; +//! +//! # fn main() { +//! # let server = IrcServer::new("config.toml").unwrap(); +//! # server.identify().unwrap(); +//! server.for_each_incoming(|irc_msg| { +//! match irc_msg.command { +//! Command::PRIVMSG(channel, message) => if message.contains(server.current_nickname()) { +//! server.send_privmsg(&channel, "beep boop").unwrap(); +//! } +//! _ => () +//! } +//! }).unwrap(); +//! # } +//! ``` #[cfg(feature = "ctcp")] use std::ascii::AsciiExt; use std::collections::HashMap; @@ -26,6 +72,31 @@ use proto::Command::{JOIN, NICK, NICKSERV, PART, PRIVMSG, ChannelMODE, QUIT}; pub mod utils; /// Trait extending all IRC streams with `for_each_incoming` convenience function. +/// +/// This is typically used in conjunction with [Server::stream](trait.Server.html#tymethod.stream) +/// in order to use an API akin to +/// [Server::for_each_incoming](trait.Server.html#method.for_each_incoming). +/// +/// # Example +/// +/// ```no_run +/// # extern crate irc; +/// # use irc::client::prelude::{IrcServer, Server, Command, ServerExt}; +/// use irc::client::prelude::EachIncomingExt; +/// +/// # fn main() { +/// # let server = IrcServer::new("config.toml").unwrap(); +/// # server.identify().unwrap(); +/// server.stream().for_each_incoming(|irc_msg| { +/// match irc_msg.command { +/// Command::PRIVMSG(channel, message) => if message.contains(server.current_nickname()) { +/// server.send_privmsg(&channel, "beep boop").unwrap(); +/// } +/// _ => () +/// } +/// }).unwrap(); +/// # } +/// ``` pub trait EachIncomingExt: Stream { /// Blocks on the stream, running the given function on each incoming message as they arrive. fn for_each_incoming(self, mut f: F) -> error::Result<()> @@ -42,21 +113,58 @@ pub trait EachIncomingExt: Stream { impl EachIncomingExt for T where T: Stream {} -/// An interface for interacting with an IRC server. +/// An interface for communicating with an IRC server. pub trait Server { /// Gets the configuration being used with this Server. fn config(&self) -> &Config; - /// Sends a Command to this Server. + /// Sends a [Command](../../proto/command/enum.Command.html) to this Server. This is the core + /// primitive for sending messages to the server. In practice, it's often more pleasant (and + /// more idiomatic) to use the functions defined on [ServerExt](util/trait.ServerExt.html). They + /// capture a lot of the more repetitive aspects of sending messages. + /// + /// # Example + /// ```no_run + /// # extern crate irc; + /// # use irc::client::prelude::*; + /// # fn main() { + /// # let server = IrcServer::new("config.toml").unwrap(); + /// server.send(Command::NICK("example".to_owned())).unwrap(); + /// server.send(Command::USER("user".to_owned(), "0".to_owned(), "name".to_owned())).unwrap(); + /// # } + /// ``` fn send>(&self, message: M) -> error::Result<()> where Self: Sized; - /// Gets a stream of incoming messages from the Server. - /// Note: The stream can only be gotten once. Subsequent attempts will panic. + /// Gets a stream of incoming messages from the Server. This is only necessary when trying to + /// set up more complex clients, and requires use of the `futures` crate. Most IRC bots should + /// be able to get by using only `for_each_incoming` to handle received messages. You can find + /// some examples of more complex setups using `stream` in the + /// [GitHub repository](https://github.com/aatxe/irc/tree/master/examples). + /// + /// **Note**: The stream can only be returned once. Subsequent attempts will cause a panic. fn stream(&self) -> ServerStream; /// Blocks on the stream, running the given function on each incoming message as they arrive. + /// + /// # Example + /// ```no_run + /// # extern crate irc; + /// # use irc::client::prelude::{IrcServer, ServerExt, Server, Command}; + /// # fn main() { + /// # let server = IrcServer::new("config.toml").unwrap(); + /// # server.identify().unwrap(); + /// server.for_each_incoming(|irc_msg| { + /// match irc_msg.command { + /// Command::PRIVMSG(channel, message) => if message.contains(server.current_nickname()) { + /// server.send_privmsg(&channel, "beep boop").unwrap(); + /// } + /// _ => () + /// } + /// }).unwrap(); + /// # } + /// ``` fn for_each_incoming(&self, f: F) -> error::Result<()> where F: FnMut(Message) -> (), @@ -64,18 +172,37 @@ pub trait Server { self.stream().for_each_incoming(f) } - /// Gets a list of currently joined channels. This will be none if tracking is not supported - /// altogether (such as when the `nochanlists` feature is enabled). + /// Gets a list of currently joined channels. This will be `None` if tracking is disabled + /// altogether via the `nochanlists` feature. fn list_channels(&self) -> Option>; - /// Gets a list of Users in the specified channel. This will be none if the channel is not - /// being tracked, or if tracking is not supported altogether. For best results, be sure to - /// request `multi-prefix` support from the server. + /// Gets a list of [Users](../data/user/struct.User.html) in the specified channel. If the + /// specified channel hasn't been joined or the `nochanlists` feature is enabled, this function + /// will return `None`. + /// + /// For best results, be sure to request `multi-prefix` support from the server. This will allow + /// for more accurate tracking of user rank (e.g. oper, half-op, etc.). + /// # Requesting multi-prefix support + /// ```no_run + /// # extern crate irc; + /// # use irc::client::prelude::{IrcServer, ServerExt, Server, Command}; + /// use irc::proto::caps::Capability; + /// + /// # fn main() { + /// # let server = IrcServer::new("config.toml").unwrap(); + /// server.send_cap_req(&[Capability::MultiPrefix]).unwrap(); + /// server.identify().unwrap(); + /// # } + /// ``` fn list_users(&self, channel: &str) -> Option>; } -/// A stream of `Messages` from the `IrcServer`. Interaction with this stream relies on the -/// `futures` API. +/// A stream of `Messages` from the `IrcServer`. +/// +/// Interaction with this stream relies on the `futures` API, but is only expected for less +/// traditional use cases. To learn more, you can view the documentation for the +/// [futures](https://docs.rs/futures/) crate, or the tutorials for +/// [tokio](https://tokio.rs/docs/getting-started/futures/). pub struct ServerStream { state: Arc, stream: SplitStream, @@ -457,7 +584,14 @@ impl ServerState { } } -/// A thread-safe implementation of an IRC Server connection. +/// The canonical implementation of a connection to an IRC server. +/// +/// The type itself provides a number of methods to create new connections, but most of the API +/// surface is in the form of the [Server](trait.Server.html) and +/// [ServerExt](util/trait.ServerExt.html) traits that provide methods of communicating with the +/// server after connection. Cloning an `IrcServer` is relatively cheap, as it's equivalent to +/// cloning a single `Arc`. This may be useful for setting up multiple threads with access to one +/// connection. #[derive(Clone)] pub struct IrcServer { /// The internal, thread-safe server state. @@ -522,14 +656,42 @@ impl Server for IrcServer { } impl IrcServer { - /// Creates a new IRC Server connection from the configuration at the specified path, - /// connecting immediately. + /// Creates a new IRC Server connection from the configuration at the specified path, connecting + /// immediately. This function is short-hand for loading the configuration and then calling + /// `IrcServer::from_config` and subsequently inherits its behaviors. + /// + /// # Example + /// ```no_run + /// # extern crate irc; + /// # use irc::client::prelude::*; + /// # fn main() { + /// let server = IrcServer::new("config.toml").unwrap(); + /// # } + /// ``` pub fn new>(config: P) -> error::Result { IrcServer::from_config(Config::load(config)?) } /// Creates a new IRC server connection from the specified configuration, connecting - /// immediately. + /// immediately. Due to current design limitations, error handling here is somewhat limited. In + /// particular, failed connections will cause the program to panic because the connection + /// attempt is made on a freshly created thread. If you need to avoid this behavior and handle + /// errors more gracefully, it is recommended that you use `IrcServer::new_future` instead. + /// + /// # Example + /// ```no_run + /// # extern crate irc; + /// # use std::default::Default; + /// # use irc::client::prelude::*; + /// # fn main() { + /// let config = Config { + /// nickname: Some("example".to_owned()), + /// server: Some("irc.example.com".to_owned()), + /// .. Default::default() + /// }; + /// let server = IrcServer::from_config(config).unwrap(); + /// # } + /// ``` pub fn from_config(config: Config) -> error::Result { // Setting up a remote reactor running for the length of the connection. let (tx_outgoing, rx_outgoing) = mpsc::unbounded(); @@ -566,9 +728,42 @@ impl IrcServer { }) } - /// Creates a new IRC server connection from the specified configration and on event loop + /// Creates a new IRC server connection from the specified configration and on the event loop /// corresponding to the given handle. This can be used to set up a number of IrcServers on a - /// single, shared event loop. Connection will not occur until the event loop is run. + /// single, shared event loop. It can also be used to take more control over execution and error + /// handling. Connection will not occur until the event loop is run. + /// + /// Proper usage requires familiarity with `tokio` and `futures`. You can find more information + /// in the crate documentation for [tokio-core](http://docs.rs/tokio-core) or + /// [futures](http://docs.rs/futures). Additionally, you can find detailed tutorials on using + /// both libraries on the [tokio website](https://tokio.rs/docs/getting-started/tokio/). + /// + /// # Example + /// ```no_run + /// # extern crate irc; + /// # extern crate tokio_core; + /// # use std::default::Default; + /// # use irc::client::prelude::*; + /// # use irc::error; + /// # use tokio_core::reactor::Core; + /// # fn main() { + /// # let config = Config { + /// # nickname: Some("example".to_owned()), + /// # server: Some("irc.example.com".to_owned()), + /// # .. Default::default() + /// # }; + /// let mut reactor = Core::new().unwrap(); + /// let future = IrcServer::new_future(reactor.handle(), &config).unwrap(); + /// // immediate connection errors (like no internet) will turn up here... + /// let server = reactor.run(future).unwrap(); + /// // runtime errors (like disconnections and so forth) will turn up here... + /// reactor.run(server.stream().for_each(move |irc_msg| { + /// // processing messages works like usual + /// process_msg(&server, irc_msg) + /// })).unwrap(); + /// # } + /// # fn process_msg(server: &IrcServer, message: Message) -> error::Result<()> { Ok(()) } + /// ``` pub fn new_future<'a>(handle: Handle, config: &'a Config) -> error::Result> { let (tx_outgoing, rx_outgoing) = mpsc::unbounded(); @@ -581,7 +776,9 @@ impl IrcServer { }) } - /// Gets the current nickname in use. + /// Gets the current nickname in use. This may be the primary username set in the configuration, + /// or it could be any of the alternative nicknames listed as well. As a result, this is the + /// preferred way to refer to the client's nickname. pub fn current_nickname(&self) -> &str { self.state.current_nickname() } @@ -594,6 +791,11 @@ impl IrcServer { } /// A future representing the eventual creation of an `IrcServer`. +/// +/// Interaction with this future relies on the `futures` API, but is only expected for more advanced +/// use cases. To learn more, you can view the documentation for the +/// [futures](https://docs.rs/futures/) crate, or the tutorials for +/// [tokio](https://tokio.rs/docs/getting-started/futures/). pub struct IrcServerFuture<'a> { conn: ConnectionFuture<'a>, handle: Handle, diff --git a/src/client/server/utils.rs b/src/client/server/utils.rs index 87ef559..e5cd779 100644 --- a/src/client/server/utils.rs +++ b/src/client/server/utils.rs @@ -1,4 +1,41 @@ //! Utilities and shortcuts for working with IRC servers. +//! +//! This module provides the [ServerExt](trait.ServerExt.html) trait which is the idiomatic way of +//! sending messages to an IRC server. This trait is automatically implemented for everything that +//! implements [Server](../trait.Server.html) and is designed to provide important functionality +//! without clutter. +//! +//! # Examples +//! +//! Using these APIs, we can connect to a server and send a one-off message (in this case, +//! identifying with the server). +//! +//! ```no_run +//! # extern crate irc; +//! use irc::client::prelude::{IrcServer, ServerExt}; +//! +//! # fn main() { +//! let server = IrcServer::new("config.toml").unwrap(); +//! // identify and send_privmsg both come from `ServerExt` +//! server.identify().unwrap(); +//! server.send_privmsg("#example", "Hello, world!").unwrap(); +//! # } +//! ``` +//! +//! `ServerExt::identify` also plays an important role in performing IRCv3 capability negotiations. +//! In particular, calling `identify` will close the negotiations (and otherwise indicate IRCv3 +//! compatibility). This means that all IRCv3 capability requests should be performed before calling +//! `identify`. For example: +//! +//! ```no_run +//! # extern crate irc; +//! # use irc::client::prelude::*; +//! # fn main() { +//! # let server = IrcServer::new("config.toml").unwrap(); +//! server.send_cap_req(&[Capability::MultiPrefix, Capability::UserhostInNames]).unwrap(); +//! server.identify().unwrap(); +//! # } +//! ``` use std::borrow::ToOwned; #[cfg(feature = "ctcp")] @@ -11,7 +48,7 @@ use proto::command::Command::*; use proto::mode::ModeType; use client::server::Server; -/// Extensions for Server capabilities that make it easier to work directly with the protocol. +/// Idiomatic extensions for sending messages to an IRC server. pub trait ServerExt: Server { /// Sends a request for a list of server capabilities for a specific IRCv3 version. fn send_cap_ls(&self, version: NegotiationVersion) -> Result<()> @@ -361,7 +398,7 @@ mod test { assert_eq!( &get_server_value(server)[..], "CAP END\r\nNICK :test\r\n\ - USER test 0 * :test\r\n" + USER test 0 * :test\r\n" ); } @@ -376,7 +413,7 @@ mod test { assert_eq!( &get_server_value(server)[..], "CAP END\r\nPASS :password\r\nNICK :test\r\n\ - USER test 0 * :test\r\n" + USER test 0 * :test\r\n" ); }