aboutsummaryrefslogtreecommitdiff
path: root/src/message.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/message.rs')
-rw-r--r--src/message.rs218
1 files changed, 124 insertions, 94 deletions
diff --git a/src/message.rs b/src/message.rs
index be52090..de8766c 100644
--- a/src/message.rs
+++ b/src/message.rs
@@ -1,161 +1,191 @@
#![allow(non_camel_case_types)]
-use std::str::FromStr;
+use std::str::{ self, FromStr };
use std::string::{ ToString };
use std::borrow::{ ToOwned };
use std::ops::{ Deref, Range };
+use std::fmt;
+
+use linear_map::LinearMap;
use ::IrscError;
+use text::{ self, Text, TextSlice };
use ident::Ident;
-#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
-pub enum MsgType {
- /// Plain old IRC messages, as defined in [rfc2812][rfc]
- /// rfc: http://tools.ietf.org/html/rfc2812
- Irc,
- /// Ctcp messages, wrapped in \u{1}
- Ctcp
-}
-
/// Byte indices, be careful.
-#[derive(Debug, PartialEq, Eq, Clone)]
+/// TODO: more IRCv3 stuff
+/// TODO: have MaybeUTF8 enum, try to decode as UTF-8 first,
+/// with functions to decode as alternative charsets
+/// ircv3.net
+#[derive(Clone)]
pub struct Message {
- pub source: String,
+ pub source: Text,
prefix: Option<Range<u16>>,
command: Range<u16>,
content: Vec<Range<u16>>,
suffix: Option<Range<u16>>,
- pub msg_type: MsgType
+ // only allocates if tags are present
+ tags: LinearMap<Text, Text>
+ //pub msg_type: MsgType
+}
+
+impl fmt::Debug for Message {
+ fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+ fmt.debug_struct("Message")
+ .field("source", &self.source)
+ .finish()
+ }
}
impl Message {
- pub fn new(source: String, prefix: Option<Range<u16>>, command: Range<u16>, content: Vec<Range<u16>>, suffix: Option<Range<u16>>, msg_type: MsgType) -> Message {
+ pub fn new(source: Text, prefix: Option<Range<u16>>, command: Range<u16>, content: Vec<Range<u16>>, suffix: Option<Range<u16>>) -> Message {
Message {
source: source,
prefix: prefix,
command: command,
content: content,
suffix: suffix,
- msg_type: msg_type
+ tags: LinearMap::new()
}
}
- #[allow(unused_assignments)]
- pub fn format<T: Deref<Target=str>>(prefix: Option<T>, command: T, content: Vec<T>, suffix: Option<T>, msg_type: MsgType) -> Message {
- let mut s = String::with_capacity(512);
- let mut i = 0;
-
- let mut i_prefix = None;
- if let Some(ref p) = prefix {
- i_prefix = Some((i + 1) as u16..(i + 2 + p.len()) as u16);
- s.push(':');
- s.push_str(p);
- s.push(' ');
- i += 2 + p.len();
- }
-
- let i_command = i as u16..(i + command.len()) as u16;
- s.push_str(&*command);
- s.push(' ');
- i += 1 + command.len();
-
- let mut i_content = Vec::new();
- for part in content.iter() {
- i_content.push(i as u16..(i + part.len()) as u16);
- s.push_str(part);
- s.push(' ');
- i += 1 + part.len();
- }
-
- let mut i_suffix = None;
- if let Some(ref p) = suffix {
- s.push(':');
- if let MsgType::Ctcp = msg_type { s.push('\u{1}'); i += 1; }
- let n = i;
- s.push_str(p);
- if let MsgType::Ctcp = msg_type { s.push('\u{1}'); i += 1; }
- i_suffix = Some(n as u16..(n + p.len()) as u16);
- i += 1 + p.len();
- }
-
- s.push_str("\r\n");
- i += 2;
-
- Message::new(s, i_prefix, i_command, i_content, i_suffix, msg_type)
- }
-
- pub fn range(&self, r: &Range<u16>) -> &str {
- &self.source[r.start as usize..r.end as usize]
- }
-
- pub fn prefix(&self) -> Option<&str> { self.prefix.as_ref().map(|r| self.range(r)) }
- pub fn command(&self) -> &str { self.range(&self.command) }
- pub fn content(&self) -> Vec<&str> { self.content.iter().map(|r| self.range(&r)).collect() }
- pub fn suffix(&self) -> Option<&str> { self.suffix.as_ref().map(|r| self.range(r)) }
- pub fn elements(&self) -> Vec<&str> { let mut s = self.content(); self.suffix().map(|f| s.push(f)); s }
- pub fn ident(&self) -> Option<Ident> { self.prefix().and_then(Ident::parse) }
-}
-
-impl FromStr for Message {
- type Err = IrscError;
- fn from_str(i: &str) -> Result<Message, IrscError> {
+ fn parse(i: &[u8]) -> Result<Message, IrscError> {
let len = i.len();
+ // Use indices instead of subslices, to store
// remember, bytes, not chars
let mut s = 0;
- let prefix = if len >= 1 && i[s..].as_bytes()[0] == ':' as u8 {
- i[s..].find(' ').map(|i| { let n = 1u16..(s + i) as u16; s += i + 1; n })
+ let prefix = if len >= 1 && i[s] == b':' {
+ i[s..].iter().cloned()
+ .find(|&b| b == b' ')
+ .map(|j| { let n = 1u16..(s + j as usize) as u16; s += j as usize + 1; n })
} else { None };
- let command = i[s..].find(' ').map(|n| {
- let p = s as u16..(s + n) as u16;
- s += n;
+ let command = i[s..].iter().cloned()
+ .find(|&b| b == b' ').map(|n| {
+ let p = s as u16..(s + n as usize) as u16;
+ s += n as usize;
p
});
- let mut content = Vec::with_capacity(15);
+ let mut content = Vec::with_capacity(3);
let mut suffix = None;
while s < len - 3 {
- if i[s..].as_bytes()[0] == ':' as u8 {
+ if i[s] == b':' {
suffix = Some(s as u16 + 1 as u16..i.len() as u16);
break
}
- i[s..].find(' ').map(|i| {
- if i > 0 {
- content.push(s as u16..(s + i) as u16);
- s += i;
+ i[s..].iter().cloned()
+ .find(|&b| b == b' ').map(|j| {
+ if j > 0 {
+ content.push(s as u16..(s + j as usize) as u16);
+ s += j as usize;
}
});
// if s.chars().next() == Some(' ') { s = &s[1..] };
s += 1;
}
- let msg_type = if suffix.as_ref().map(|s| i[s.start as usize..].as_bytes()[0] == 1
+ /*let msg_type = if suffix.as_ref().map(|s| i[s.start as usize..].as_bytes()[0] == 1
&& i[(s.end - 3) as usize..].as_bytes()[0] == 1)
- == Some(true) { MsgType::Ctcp } else { MsgType::Irc };
+ == Some(true) { MsgType::Ctcp } else { MsgType::Irc };*/
command.map(move |c|
Ok(Message::new(
- i.to_owned(),
+ Text::Raw(i.to_owned()),
prefix,
c,
content,
// strip \{1} if CTCP message
// strip \r\n for each line, relying on their existence
- match msg_type {
+ suffix
+ /*match msg_type {
MsgType::Irc => suffix.map(|s| s.start..s.end - 1),
MsgType::Ctcp => suffix.map(|s| s.start + 1..s.end - 2)
},
- msg_type
+ msg_type*/
))
).unwrap()
+ }
+
+ #[allow(unused_assignments)]
+ pub fn format<T: Deref<Target=[u8]>>(prefix: Option<T>, command: T, content: Vec<T>, suffix: Option<T>) -> Message {
+ let mut s = Vec::with_capacity(512);
+ let mut i = 0;
+
+ let mut i_prefix = None;
+ if let Some(ref p) = prefix {
+ i_prefix = Some((i + 1) as u16..(i + 2 + p.len()) as u16);
+ s.push(b':');
+ s.push_all(p);
+ s.push(b' ');
+ i = s.len();
+ }
+
+ let i_command = i as u16..(i + command.len()) as u16;
+ s.push_all(&command);
+ s.push(b' ');
+ i = s.len();
+
+ let mut i_content = Vec::new();
+ for part in content.iter() {
+ i_content.push(i as u16..(i + part.len()) as u16);
+ s.push_all(part);
+ s.push(b' ');
+ i = s.len();
+ }
+
+ let mut i_suffix = None;
+ if let Some(ref p) = suffix {
+ s.push(b':');
+ //if let MsgType::Ctcp = msg_type { s.push('\u{1}'); i += 1; }
+ let n = i;
+ s.push_all(p);
+ //if let MsgType::Ctcp = msg_type { s.push('\u{1}'); i += 1; }
+ i_suffix = Some(n as u16..(n + p.len()) as u16);
+ i = s.len();
+ }
+ s.push_all(b"\r\n");
+
+ Message::new(Text::Raw(s), i_prefix, i_command, i_content, i_suffix)
}
-}
-impl ToString for Message {
- fn to_string(&self) -> String {
- self.source.clone()
+ pub fn byte_range(&self, r: &Range<u16>) -> &[u8] {
+ &self.source[r.start as usize..r.end as usize]
+ }
+
+ pub fn text_range(&self, r: &Range<u16>) -> TextSlice {
+ self.source.slice(&(r.start as usize..r.end as usize))
+ }
+
+ pub fn string_range(&self, r: &Range<u16>) -> String {
+ text::def_lossy_decode(self.byte_range(r))
+ }
+
+ pub fn str_range(&self, r: &Range<u16>) -> Option<&str> {
+ str::from_utf8(self.byte_range(r)).ok()
+ }
+
+ pub fn bytes(&self) -> &[u8] { &*self.source }
+
+ pub fn prefix<'a>(&'a self) -> Option<TextSlice<'a>> {
+ self.prefix.as_ref().map(|r| self.text_range(r)) }
+ pub fn command<'a>(&'a self) -> TextSlice<'a> {
+ self.text_range(&self.command) }
+ pub fn content<'a>(&'a self) -> Vec<TextSlice<'a>> {
+ self.content.iter().map(|r| self.text_range(&r)).collect() }
+ pub fn suffix<'a>(&'a self) -> Option<TextSlice<'a>> {
+ self.suffix.as_ref().map(|r| self.text_range(r)) }
+ pub fn last<'a>(&'a self) -> Option<TextSlice<'a>> {
+ self.suffix().or(self.content.last().map(|l| self.text_range(l))) }
+ pub fn elements<'a>(&'a self) -> Vec<TextSlice<'a>> {
+ let mut s = self.content(); self.suffix().map(|f| s.push(f)); s }
+ pub fn ident(&self) -> Option<Ident> {
+ self.prefix().and_then(|p| p.utf8()).and_then(Ident::parse) }
+ pub fn is_ctcp(&self) -> bool {
+ self.source.get(0) == Some(&1)
+ && self.source.get(self.source.length() - 3) == Some(&1)
}
}