<commit_before>use buffer::Buffer;
use std::path::PathBuf;

pub struct Workspace {
    path: PathBuf,
    buffers: Vec<Buffer>,
    current_buffer_index: Option<usize>,
}

impl Workspace {
    pub fn add_buffer(&mut self, buf: Buffer) {
        self.buffers.push(buf);
        self.current_buffer_index = Some(self.buffers.len()-1);
    }

    pub fn current_buffer(&mut self) -> Option<&mut Buffer> {
        match self.current_buffer_index {
            Some(index) => Some(&mut self.buffers[index]),
            None => None,
        }
    }

    pub fn close_current_buffer(&mut self) {
        match self.current_buffer_index {
            Some(index) => {
                self.buffers.remove(index);

                if self.buffers.is_empty() {
                    self.current_buffer_index = None;
                } else {
                    self.current_buffer_index = Some(self.buffers.len()-1);
                }
            }
            None => return,
        };
    }

    pub fn previous_buffer(&mut self) {
        match self.current_buffer_index {
            Some(index) => {
                if index > 0 {
                    self.current_buffer_index = Some(index-1);
                } else {
                    self.current_buffer_index = Some(self.buffers.len()-1);
                }
            },
            None => return,
        }
    }

    pub fn next_buffer(&mut self) {
        match self.current_buffer_index {
            Some(index) => {
                if index == self.buffers.len()-1 {
                    self.current_buffer_index = Some(0);
                } else {
                    self.current_buffer_index = Some(index+1);
                }
            },
            None => return,
        }
    }
}

pub fn new(path: PathBuf) -> Workspace {
    Workspace{ path: path, buffers: Vec::new(), current_buffer_index: None }
}
#[cfg(test)]
mod tests {
    use super::new;
    use buffer;
    use std::path::PathBuf;

    #[test]
    fn add_buffer_adds_and_selects_the_passed_buffer() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        let buf = buffer::from_file(PathBuf::from("tests/sample/file")).unwrap();
        workspace.add_buffer(buf);

        assert_eq!(workspace.buffers.len(), 1);
        assert_eq!(workspace.current_buffer().unwrap().data(), "it works!\n");
    }

    #[test]
    fn current_buffer_returns_none_when_there_are_no_buffers() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        assert!(workspace.current_buffer().is_none());
    }

    #[test]
    fn current_buffer_returns_one_when_there_are_buffers() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        let buf = buffer::from_file(PathBuf::from("tests/sample/file")).unwrap();
        workspace.add_buffer(buf);
        assert!(workspace.current_buffer().is_some());
    }

    #[test]
    fn close_current_buffer_does_nothing_when_none_are_open() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.close_current_buffer();
        assert!(workspace.current_buffer().is_none());
    }

    #[test]
    fn close_current_buffer_cleans_up_when_only_one_buffer_is_open() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.add_buffer(buffer::new());
        workspace.close_current_buffer();
        assert!(workspace.current_buffer().is_none());
        assert!(workspace.current_buffer_index.is_none());
    }

    #[test]
    fn close_current_buffer_when_two_are_open_selects_the_other() {
        let mut workspace = new(PathBuf::from("tests/sample"));

        // Create two buffers and add them to the workspace.
        let mut first_buffer = buffer::new();
        let mut second_buffer = buffer::new();
        first_buffer.insert("first buffer");
        second_buffer.insert("second buffer");
        workspace.add_buffer(first_buffer);
        workspace.add_buffer(second_buffer);

        // Ensure that the second buffer is currently selected.
        assert_eq!(workspace.current_buffer().unwrap().data(), "second buffer");

        workspace.close_current_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "first buffer");
    }

    #[test]
    fn previous_buffer_does_nothing_when_no_buffers_are_open() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.previous_buffer();
        assert!(workspace.current_buffer().is_none());
    }

    #[test]
    fn previous_buffer_when_three_are_open_selects_previous_wrapping_to_last() {
        let mut workspace = new(PathBuf::from("tests/sample"));

        // Create two buffers and add them to the workspace.
        let mut first_buffer = buffer::new();
        let mut second_buffer = buffer::new();
        let mut third_buffer = buffer::new();
        first_buffer.insert("first buffer");
        second_buffer.insert("second buffer");
        third_buffer.insert("third buffer");
        workspace.add_buffer(first_buffer);
        workspace.add_buffer(second_buffer);
        workspace.add_buffer(third_buffer);

        // Ensure that the third buffer is currently selected.
        assert_eq!(workspace.current_buffer().unwrap().data(), "third buffer");

        // Ensure that the second buffer is returned.
        workspace.previous_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "second buffer");

        // Ensure that the first buffer is returned.
        workspace.previous_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "first buffer");

        // Ensure that it wraps back to the third buffer.
        workspace.previous_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "third buffer");
    }

    #[test]
    fn next_buffer_does_nothing_when_no_buffers_are_open() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.next_buffer();
        assert!(workspace.current_buffer().is_none());
    }

    #[test]
    fn next_buffer_when_three_are_open_selects_next_wrapping_to_first() {
        let mut workspace = new(PathBuf::from("tests/sample"));

        // Create two buffers and add them to the workspace.
        let mut first_buffer = buffer::new();
        let mut second_buffer = buffer::new();
        let mut third_buffer = buffer::new();
        first_buffer.insert("first buffer");
        second_buffer.insert("second buffer");
        third_buffer.insert("third buffer");
        workspace.add_buffer(first_buffer);
        workspace.add_buffer(second_buffer);
        workspace.add_buffer(third_buffer);

        // Ensure that the third buffer is currently selected.
        assert_eq!(workspace.current_buffer().unwrap().data(), "third buffer");

        // Ensure that it wraps back to the first buffer.
        workspace.next_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "first buffer");

        // Ensure that the second buffer is returned.
        workspace.next_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "second buffer");

        // Ensure that the third buffer is returned.
        workspace.next_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "third buffer");
    }
}
<commit_msg>Expose workspace path field.<commit_after>use buffer::Buffer;
use std::path::PathBuf;

pub struct Workspace {
    pub path: PathBuf,
    buffers: Vec<Buffer>,
    current_buffer_index: Option<usize>,
}

impl Workspace {
    pub fn add_buffer(&mut self, buf: Buffer) {
        self.buffers.push(buf);
        self.current_buffer_index = Some(self.buffers.len()-1);
    }

    pub fn current_buffer(&mut self) -> Option<&mut Buffer> {
        match self.current_buffer_index {
            Some(index) => Some(&mut self.buffers[index]),
            None => None,
        }
    }

    pub fn close_current_buffer(&mut self) {
        match self.current_buffer_index {
            Some(index) => {
                self.buffers.remove(index);

                if self.buffers.is_empty() {
                    self.current_buffer_index = None;
                } else {
                    self.current_buffer_index = Some(self.buffers.len()-1);
                }
            }
            None => return,
        };
    }

    pub fn previous_buffer(&mut self) {
        match self.current_buffer_index {
            Some(index) => {
                if index > 0 {
                    self.current_buffer_index = Some(index-1);
                } else {
                    self.current_buffer_index = Some(self.buffers.len()-1);
                }
            },
            None => return,
        }
    }

    pub fn next_buffer(&mut self) {
        match self.current_buffer_index {
            Some(index) => {
                if index == self.buffers.len()-1 {
                    self.current_buffer_index = Some(0);
                } else {
                    self.current_buffer_index = Some(index+1);
                }
            },
            None => return,
        }
    }
}

pub fn new(path: PathBuf) -> Workspace {
    Workspace{ path: path, buffers: Vec::new(), current_buffer_index: None }
}
#[cfg(test)]
mod tests {
    use super::new;
    use buffer;
    use std::path::PathBuf;

    #[test]
    fn add_buffer_adds_and_selects_the_passed_buffer() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        let buf = buffer::from_file(PathBuf::from("tests/sample/file")).unwrap();
        workspace.add_buffer(buf);

        assert_eq!(workspace.buffers.len(), 1);
        assert_eq!(workspace.current_buffer().unwrap().data(), "it works!\n");
    }

    #[test]
    fn current_buffer_returns_none_when_there_are_no_buffers() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        assert!(workspace.current_buffer().is_none());
    }

    #[test]
    fn current_buffer_returns_one_when_there_are_buffers() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        let buf = buffer::from_file(PathBuf::from("tests/sample/file")).unwrap();
        workspace.add_buffer(buf);
        assert!(workspace.current_buffer().is_some());
    }

    #[test]
    fn close_current_buffer_does_nothing_when_none_are_open() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.close_current_buffer();
        assert!(workspace.current_buffer().is_none());
    }

    #[test]
    fn close_current_buffer_cleans_up_when_only_one_buffer_is_open() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.add_buffer(buffer::new());
        workspace.close_current_buffer();
        assert!(workspace.current_buffer().is_none());
        assert!(workspace.current_buffer_index.is_none());
    }

    #[test]
    fn close_current_buffer_when_two_are_open_selects_the_other() {
        let mut workspace = new(PathBuf::from("tests/sample"));

        // Create two buffers and add them to the workspace.
        let mut first_buffer = buffer::new();
        let mut second_buffer = buffer::new();
        first_buffer.insert("first buffer");
        second_buffer.insert("second buffer");
        workspace.add_buffer(first_buffer);
        workspace.add_buffer(second_buffer);

        // Ensure that the second buffer is currently selected.
        assert_eq!(workspace.current_buffer().unwrap().data(), "second buffer");

        workspace.close_current_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "first buffer");
    }

    #[test]
    fn previous_buffer_does_nothing_when_no_buffers_are_open() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.previous_buffer();
        assert!(workspace.current_buffer().is_none());
    }

    #[test]
    fn previous_buffer_when_three_are_open_selects_previous_wrapping_to_last() {
        let mut workspace = new(PathBuf::from("tests/sample"));

        // Create two buffers and add them to the workspace.
        let mut first_buffer = buffer::new();
        let mut second_buffer = buffer::new();
        let mut third_buffer = buffer::new();
        first_buffer.insert("first buffer");
        second_buffer.insert("second buffer");
        third_buffer.insert("third buffer");
        workspace.add_buffer(first_buffer);
        workspace.add_buffer(second_buffer);
        workspace.add_buffer(third_buffer);

        // Ensure that the third buffer is currently selected.
        assert_eq!(workspace.current_buffer().unwrap().data(), "third buffer");

        // Ensure that the second buffer is returned.
        workspace.previous_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "second buffer");

        // Ensure that the first buffer is returned.
        workspace.previous_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "first buffer");

        // Ensure that it wraps back to the third buffer.
        workspace.previous_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "third buffer");
    }

    #[test]
    fn next_buffer_does_nothing_when_no_buffers_are_open() {
        let mut workspace = new(PathBuf::from("tests/sample"));
        workspace.next_buffer();
        assert!(workspace.current_buffer().is_none());
    }

    #[test]
    fn next_buffer_when_three_are_open_selects_next_wrapping_to_first() {
        let mut workspace = new(PathBuf::from("tests/sample"));

        // Create two buffers and add them to the workspace.
        let mut first_buffer = buffer::new();
        let mut second_buffer = buffer::new();
        let mut third_buffer = buffer::new();
        first_buffer.insert("first buffer");
        second_buffer.insert("second buffer");
        third_buffer.insert("third buffer");
        workspace.add_buffer(first_buffer);
        workspace.add_buffer(second_buffer);
        workspace.add_buffer(third_buffer);

        // Ensure that the third buffer is currently selected.
        assert_eq!(workspace.current_buffer().unwrap().data(), "third buffer");

        // Ensure that it wraps back to the first buffer.
        workspace.next_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "first buffer");

        // Ensure that the second buffer is returned.
        workspace.next_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "second buffer");

        // Ensure that the third buffer is returned.
        workspace.next_buffer();
        assert_eq!(workspace.current_buffer().unwrap().data(), "third buffer");
    }
}
<|endoftext|>


<commit_before>#![allow(dead_code, unused_variables)]

use std::collections::HashMap;
use std::fmt;

#[derive(Debug, PartialEq)]
pub struct Source {
    pub nick: String,
    pub ident: Option<String>,
    pub host: Option<String>,
    pub is_server: bool,
}

impl fmt::Display for Source {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.is_server {
            write!(f, ":")?;
        }
        write!(f, "{}", self.nick)?;
        if let Some(ref ident) = self.ident {
            write!(f, "!{}", ident)?;
        }
        if let Some(ref host) = self.host {
            write!(f, "@{}", host)?;
        }

        Ok(())
    }
}

//  <prefix>   ::= <nick> [ '!' <ident>  [ '@' <host> ] ]
impl Source {
    fn parse(s: &str) -> Option<Source> {
        if s.is_empty() {
            return None;
        }

        let (nick, rest) = if let Some(index) = s.find('!') {
            (&s[..index], Some(&s[index + 1..]))
        } else {
            (s, None)
        };

        let (ident, host) = rest
            .map(|r| {
                if let Some(index) = r.find('@') {
                    (Some(&r[..index]), Some(&r[index + 1..]))
                } else {
                    (rest, None)
                }
            })
            .unwrap_or((None, None));

        Some(Source {
            nick: nick.trim_start_matches(':').to_string(),
            ident: ident.map(str::to_string),
            host: host.map(str::to_string),
            is_server: nick.starts_with(':'),
        })
    }
}

#[derive(Debug, PartialEq)]
pub enum MessageKind {
    Away,
    Capability,
    Error,
    Invite,
    Join,
    Kick,
    Mode,
    Nick,
    Notice,
    Numeric(u16),
    Part,
    Pass,
    Ping,
    Pong,
    Privmsg,
    Quit,
    Topic,
    User,
    Unknown,
    Wallops,
}

impl MessageKind {
    pub fn from(msg: &RawMessage) -> MessageKind {
        if let Ok(code) = msg.command.parse::<u16>() {
            return MessageKind::Numeric(code);
        }

        match msg.command.as_str() {
            "CAP" => Self::Capability,
            "JOIN" => Self::Join,
            "KICK" => Self::Kick,
            "MODE" => Self::Mode,
            "NOTICE" => Self::Notice,
            "PART" => Self::Part,
            "PASS" => Self::Pass,
            "PING" => Self::Ping,
            "PONG" => Self::Pong,
            "PRIVMSG" => Self::Privmsg,
            "USER" => Self::User,
            "ERROR" => Self::Error,
            // TODO: Map remaining types
            _ => Self::Unknown,
        }
    }
}

// <tags>          ::= '@' <tag> [';' <tag>]*
// <tag>           ::= <key> ['=' <escaped value>]
// <key>           ::= [ <vendor> '/' ] <sequence of letters, digits, hyphens (`-`)>
// <escaped value> ::= <sequence of any characters except NUL, CR, LF, semicolon (`;`) and SPACE>
// <vendor>        ::= <host>
#[derive(Debug, PartialEq)]
pub struct Tags(HashMap<String, Tag>);

impl Tags {
    pub fn empty() -> Self {
        Self(HashMap::new())
    }

    pub fn parse(s: &str) -> Option<Self> {
        if s.starts_with('@') {
            return None;
        }

        let map = s[1..]
            .split(';')
            .map(Tag::parse)
            .map(|t| (t.key.clone(), t))
            .collect();

        Some(Tags(map))
    }
}

#[derive(Debug, PartialEq)]
pub struct Tag {
    pub key: String,
    pub value: Option<String>,
}

impl Tag {
    pub fn parse(s: &str) -> Self {
        if let Some(idx) = s.find('=') {
            Tag {
                key: s[..idx].to_string(),
                value: Some(s[idx + 1..].to_string()),
            }
        } else {
            Tag {
                key: s.to_string(),
                value: None,
            }
        }
    }
}

#[derive(Debug, PartialEq)]
pub struct RawMessage {
    pub source: Option<Source>,
    pub command: String,
    params: Vec<String>,

    pub timestamp: u64, // TODO: datetime
    pub tags: Tags,
}

/// Split `msg` at first space character or end of word if no spaces remain.
fn split_message(msg: &str) -> Option<(&str, &str)> {
    if msg.is_empty() {
        return None;
    }

    match msg.find(' ') {
        Some(index) => Some((&msg[..index], &msg[index + 1..])),
        None => Some((msg, "")),
    }
}

impl RawMessage {
    pub fn new(command: &str, params: &[&str]) -> RawMessage {
        RawMessage {
            command: command.to_string(),
            params: params.iter().map(|s| s.to_string()).collect(),

            source: None,
            timestamp: 0,
            tags: Tags::empty(),
        }
    }

    /// Expects that any trailing CR, LF have been removed.
    ///
    ///  <message>  ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
    ///  <prefix>   ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
    ///  <command>  ::= <letter> { <letter> } | <number> <number> <number>
    ///  <SPACE>    ::= ' ' { ' ' }
    ///  <params>   ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
    ///  <middle>   ::= <Any *non-empty* sequence of octets not including SPACE
    ///                 or NUL or CR or LF, the first of which may not be ':'>
    ///  <trailing> ::= <Any, possibly *empty*, sequence of octets not including
    ///                   NUL or CR or LF>
    pub fn parse(line: &str) -> Option<Self> {
        let mut line: &str = line;
        let mut msg: Self;

        let tags = if line.starts_with('@') {
            let (tags, rest) = split_message(line)?;
            line = rest;
            Tags::parse(tags)?
        } else {
            Tags::empty()
        };

        let nick = if line.starts_with(':') {
            let (nick, rest) = split_message(line)?;
            line = rest;
            Some(Source::parse(nick)?)
        } else {
            None
        };

        let (cmd, mut line) = split_message(line)?;

        let mut params: Vec<String> = Vec::new();
        while let Some((param, rest)) = split_message(line) {
            // Trailing param, this will be the end, regardless of
            // remaining whitespace.
            if param.starts_with(':') {
                params.push(line[1..].to_string());
                break;
            } else {
                params.push(param.to_string());
            }

            line = rest;
        }

        Some(Self {
            source: nick,
            command: cmd.to_string(),
            params,
            tags,

            // TODO: pull this out of the tags, if possible
            timestamp: 0,
        })
    }

    pub fn param(&self, i: usize) -> Option<&str> {
        self.params.get(i).map(String::as_ref)
    }

    pub fn trailing(&self) -> Option<&str> {
        self.params.last().map(String::as_ref)
    }
}

impl fmt::Display for RawMessage {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // TOOD: tags

        if let Some(source) = &self.source {
            write!(f, "{} ", source)?;
        }

        write!(f, "{}", self.command)?;

        for (i, param) in self.params.iter().enumerate() {
            // Last is a special case
            if param.contains(' ') {
                write!(f, " :{}", param)?;
            } else {
                write!(f, " {}", param)?;
            }
        }

        Ok(())
    }
}

/// IRC capabilities we support
#[derive(Debug, Hash, PartialEq, Eq)]
pub enum Capability {
    ServerTime,
    Sasl(Vec<String>),
}

impl Capability {
    pub fn from(s: &str) -> Option<Self> {
        let split: Vec<&str> = s.splitn(2, '=').collect();

        let cap = *split.get(0)?;
        let args = split.get(1).unwrap_or(&"").split(',');

        match cap {
            "server-time" => Some(Self::ServerTime),
            "sasl" => {
                let set = args.map(str::to_uppercase).collect();
                Some(Self::Sasl(set))
            }
            _ => None,
        }
    }
}

#[derive(PartialEq, Debug, Clone)]
pub struct ModeSet(HashMap<char, bool>);

impl ModeSet {
    pub fn empty() -> Self {
        Self(HashMap::new())
    }

    pub fn apply(&self, s: &str) -> Option<Self> {
        let mut value: Option<bool> = None;
        let mut copy = self.clone();

        for ch in s.chars() {
            match ch {
                '+' => value = Some(true),
                '-' => value = Some(false),
                _ => {
                    if let Some(value) = value {
                        copy.0.insert(ch, value);
                    } else {
                        // Invalid string format, bail early
                        return None;
                    }
                }
            }
        }

        Some(copy)
    }

    pub fn contains(&self, m: char) -> bool {
        self.0.get(&m).map(bool::clone).unwrap_or(false)
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_parse_mode_string() {
        let modes = ModeSet::empty().apply("-bB+xy");
        let expected = [('b', false), ('B', false), ('x', true), ('y', true)]
            .iter()
            .cloned()
            .collect();

        assert_eq!(modes, Some(ModeSet(expected)));
    }

    #[test]
    fn test_parse_caps() {
        assert_eq!(
            Capability::from("server-time"),
            Some(Capability::ServerTime)
        );

        assert_eq!(
            Capability::from("sasl=plain,EXTERNAL"),
            Some(Capability::Sasl(vec![
                "PLAIN".to_string(),
                "EXTERNAL".to_string()
            ]))
        );
    }

    #[test]
    fn test_parse_source() {
        assert_eq!(
            Source::parse("nick!~ident@host").unwrap(),
            Source {
                nick: "nick".to_string(),
                ident: Some("~ident".to_string()),
                host: Some("host".to_string()),
                is_server: false,
            }
        );

        assert_eq!(
            Source::parse("nick!~user").unwrap(),
            Source {
                nick: "nick".to_string(),
                ident: Some("~user".to_string()),
                host: None,
                is_server: false,
            }
        );

        assert_eq!(
            Source::parse("nick").unwrap(),
            Source {
                nick: "nick".to_string(),
                ident: None,
                host: None,
                is_server: false,
            }
        );

        assert_eq!(
            Source::parse(":irc.server!~ident@host").unwrap(),
            Source {
                nick: "irc.server".to_string(),
                ident: Some("~ident".to_string()),
                host: Some("host".to_string()),
                is_server: true,
            }
        );
    }

    #[test]
    fn test_display_source() {
        let cases = vec![
            "nick",
            "nick!~ident",
            "nick!~ident@host",
            ":irc.server!~ident@host",
        ];

        for case in cases {
            let round_trip = format!("{}", Source::parse(case).unwrap());
            assert_eq!(round_trip, case);
        }
    }

    #[test]
    fn test_parse_messages() {
        let line = "@foo=bar :server 001 abc :hello, world";
        let msg = RawMessage::parse(line).expect("failed to parse");

        assert_eq!(msg.command, "001");
        assert_eq!(msg.source.unwrap().nick, "server");
        assert_eq!(msg.params, vec!["abc", "hello, world"]);
        // TODO: wire up assert
    }

    #[test]
    fn test_split_message() {
        assert_eq!(split_message("foo bar baz"), Some(("foo", "bar baz")));
        assert_eq!(split_message("foo bar"), Some(("foo", "bar")));
        assert_eq!(split_message("foo"), Some(("foo", "")));
        assert_eq!(split_message(""), None);
        assert_eq!(split_message("trailing "), Some(("trailing", "")));
        assert_eq!(split_message(" leading"), Some(("", "leading")));
    }

    #[test]
    fn test_parse_tag() {
        let cases = vec![
            (
                "key",
                Tag {
                    key: "key".to_string(),
                    value: None,
                },
            ),
            (
                "key=",
                Tag {
                    key: "key".to_string(),
                    value: Some("".to_string()),
                },
            ),
            (
                "key=val",
                Tag {
                    key: "key".to_string(),
                    value: Some("val".to_string()),
                },
            ),
        ];

        for (input, expected) in cases {
            assert_eq!(Tag::parse(input), expected);
        }
    }

    #[test]
    fn test_parse_tags() {
        let tags = Tags::parse("@foo=bar;baz=;quux");
        let expected = vec![
            (
                "foo".to_string(),
                Tag {
                    key: "foo".to_string(),
                    value: Some("bar".to_string()),
                },
            ),
            (
                "baz".to_string(),
                Tag {
                    key: "baz".to_string(),
                    value: Some("".to_string()),
                },
            ),
            (
                "quux".to_string(),
                Tag {
                    key: "quux".to_string(),
                    value: None,
                },
            ),
        ]
        .into_iter()
        .collect();

        assert_eq!(tags, Some(Tags(expected)));
    }
}
<commit_msg>Fix tests<commit_after>#![allow(dead_code, unused_variables)]

use std::collections::HashMap;
use std::fmt;

#[derive(Debug, PartialEq)]
pub struct Source {
    pub nick: String,
    pub ident: Option<String>,
    pub host: Option<String>,
    pub is_server: bool,
}

impl fmt::Display for Source {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if self.is_server {
            write!(f, ":")?;
        }
        write!(f, "{}", self.nick)?;
        if let Some(ref ident) = self.ident {
            write!(f, "!{}", ident)?;
        }
        if let Some(ref host) = self.host {
            write!(f, "@{}", host)?;
        }

        Ok(())
    }
}

//  <prefix>   ::= <nick> [ '!' <ident>  [ '@' <host> ] ]
impl Source {
    fn parse(s: &str) -> Option<Source> {
        if s.is_empty() {
            return None;
        }

        let (nick, rest) = if let Some(index) = s.find('!') {
            (&s[..index], Some(&s[index + 1..]))
        } else {
            (s, None)
        };

        let (ident, host) = rest
            .map(|r| {
                if let Some(index) = r.find('@') {
                    (Some(&r[..index]), Some(&r[index + 1..]))
                } else {
                    (rest, None)
                }
            })
            .unwrap_or((None, None));

        Some(Source {
            nick: nick.trim_start_matches(':').to_string(),
            ident: ident.map(str::to_string),
            host: host.map(str::to_string),
            is_server: nick.starts_with(':'),
        })
    }
}

#[derive(Debug, PartialEq)]
pub enum MessageKind {
    Away,
    Capability,
    Error,
    Invite,
    Join,
    Kick,
    Mode,
    Nick,
    Notice,
    Numeric(u16),
    Part,
    Pass,
    Ping,
    Pong,
    Privmsg,
    Quit,
    Topic,
    User,
    Unknown,
    Wallops,
}

impl MessageKind {
    pub fn from(msg: &RawMessage) -> MessageKind {
        if let Ok(code) = msg.command.parse::<u16>() {
            return MessageKind::Numeric(code);
        }

        match msg.command.as_str() {
            "CAP" => Self::Capability,
            "JOIN" => Self::Join,
            "KICK" => Self::Kick,
            "MODE" => Self::Mode,
            "NOTICE" => Self::Notice,
            "PART" => Self::Part,
            "PASS" => Self::Pass,
            "PING" => Self::Ping,
            "PONG" => Self::Pong,
            "PRIVMSG" => Self::Privmsg,
            "USER" => Self::User,
            "ERROR" => Self::Error,
            // TODO: Map remaining types
            _ => Self::Unknown,
        }
    }
}

// <tags>          ::= '@' <tag> [';' <tag>]*
// <tag>           ::= <key> ['=' <escaped value>]
// <key>           ::= [ <vendor> '/' ] <sequence of letters, digits, hyphens (`-`)>
// <escaped value> ::= <sequence of any characters except NUL, CR, LF, semicolon (`;`) and SPACE>
// <vendor>        ::= <host>
#[derive(Debug, PartialEq)]
pub struct Tags(HashMap<String, Tag>);

impl Tags {
    pub fn empty() -> Self {
        Self(HashMap::new())
    }

    pub fn parse(s: &str) -> Option<Self> {
        if !s.starts_with('@') {
            return None;
        }

        let map = s[1..]
            .split(';')
            .map(Tag::parse)
            .map(|t| (t.key.clone(), t))
            .collect();

        Some(Tags(map))
    }
}

#[derive(Debug, PartialEq)]
pub struct Tag {
    pub key: String,
    pub value: Option<String>,
}

impl Tag {
    pub fn parse(s: &str) -> Self {
        if let Some(idx) = s.find('=') {
            Tag {
                key: s[..idx].to_string(),
                value: Some(s[idx + 1..].to_string()),
            }
        } else {
            Tag {
                key: s.to_string(),
                value: None,
            }
        }
    }
}

#[derive(Debug, PartialEq)]
pub struct RawMessage {
    pub source: Option<Source>,
    pub command: String,
    params: Vec<String>,

    pub timestamp: u64, // TODO: datetime
    pub tags: Tags,
}

/// Split `msg` at first space character or end of word if no spaces remain.
fn split_message(msg: &str) -> Option<(&str, &str)> {
    if msg.is_empty() {
        return None;
    }

    match msg.find(' ') {
        Some(index) => Some((&msg[..index], &msg[index + 1..])),
        None => Some((msg, "")),
    }
}

impl RawMessage {
    pub fn new(command: &str, params: &[&str]) -> RawMessage {
        RawMessage {
            command: command.to_string(),
            params: params.iter().map(|s| s.to_string()).collect(),

            source: None,
            timestamp: 0,
            tags: Tags::empty(),
        }
    }

    /// Expects that any trailing CR, LF have been removed.
    ///
    ///  <message>  ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
    ///  <prefix>   ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
    ///  <command>  ::= <letter> { <letter> } | <number> <number> <number>
    ///  <SPACE>    ::= ' ' { ' ' }
    ///  <params>   ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
    ///  <middle>   ::= <Any *non-empty* sequence of octets not including SPACE
    ///                 or NUL or CR or LF, the first of which may not be ':'>
    ///  <trailing> ::= <Any, possibly *empty*, sequence of octets not including
    ///                   NUL or CR or LF>
    pub fn parse(line: &str) -> Option<Self> {
        let mut line: &str = line;
        let mut msg: Self;

        let tags = if line.starts_with('@') {
            let (tags, rest) = split_message(line)?;
            line = rest;
            Tags::parse(tags)?
        } else {
            Tags::empty()
        };

        let nick = if line.starts_with(':') {
            let (nick, rest) = split_message(line)?;
            line = rest;
            Some(Source::parse(nick)?)
        } else {
            None
        };

        let (cmd, mut line) = split_message(line)?;

        let mut params: Vec<String> = Vec::new();
        while let Some((param, rest)) = split_message(line) {
            // Trailing param, this will be the end, regardless of
            // remaining whitespace.
            if param.starts_with(':') {
                params.push(line[1..].to_string());
                break;
            } else {
                params.push(param.to_string());
            }

            line = rest;
        }

        Some(Self {
            source: nick,
            command: cmd.to_string(),
            params,
            tags,

            // TODO: pull this out of the tags, if possible
            timestamp: 0,
        })
    }

    pub fn param(&self, i: usize) -> Option<&str> {
        self.params.get(i).map(String::as_ref)
    }

    pub fn trailing(&self) -> Option<&str> {
        self.params.last().map(String::as_ref)
    }
}

impl fmt::Display for RawMessage {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // TOOD: tags

        if let Some(source) = &self.source {
            write!(f, "{} ", source)?;
        }

        write!(f, "{}", self.command)?;

        for (i, param) in self.params.iter().enumerate() {
            // Last is a special case
            if param.contains(' ') {
                write!(f, " :{}", param)?;
            } else {
                write!(f, " {}", param)?;
            }
        }

        Ok(())
    }
}

/// IRC capabilities we support
#[derive(Debug, Hash, PartialEq, Eq)]
pub enum Capability {
    ServerTime,
    Sasl(Vec<String>),
}

impl Capability {
    pub fn from(s: &str) -> Option<Self> {
        let split: Vec<&str> = s.splitn(2, '=').collect();

        let cap = *split.get(0)?;
        let args = split.get(1).unwrap_or(&"").split(',');

        match cap {
            "server-time" => Some(Self::ServerTime),
            "sasl" => {
                let set = args.map(str::to_uppercase).collect();
                Some(Self::Sasl(set))
            }
            _ => None,
        }
    }
}

#[derive(PartialEq, Debug, Clone)]
pub struct ModeSet(HashMap<char, bool>);

impl ModeSet {
    pub fn empty() -> Self {
        Self(HashMap::new())
    }

    pub fn apply(&self, s: &str) -> Option<Self> {
        let mut value: Option<bool> = None;
        let mut copy = self.clone();

        for ch in s.chars() {
            match ch {
                '+' => value = Some(true),
                '-' => value = Some(false),
                _ => {
                    if let Some(value) = value {
                        copy.0.insert(ch, value);
                    } else {
                        // Invalid string format, bail early
                        return None;
                    }
                }
            }
        }

        Some(copy)
    }

    pub fn contains(&self, m: char) -> bool {
        self.0.get(&m).map(bool::clone).unwrap_or(false)
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn test_parse_mode_string() {
        let modes = ModeSet::empty().apply("-bB+xy");
        let expected = [('b', false), ('B', false), ('x', true), ('y', true)]
            .iter()
            .cloned()
            .collect();

        assert_eq!(modes, Some(ModeSet(expected)));
    }

    #[test]
    fn test_parse_caps() {
        assert_eq!(
            Capability::from("server-time"),
            Some(Capability::ServerTime)
        );

        assert_eq!(
            Capability::from("sasl=plain,EXTERNAL"),
            Some(Capability::Sasl(vec![
                "PLAIN".to_string(),
                "EXTERNAL".to_string()
            ]))
        );
    }

    #[test]
    fn test_parse_source() {
        assert_eq!(
            Source::parse("nick!~ident@host").unwrap(),
            Source {
                nick: "nick".to_string(),
                ident: Some("~ident".to_string()),
                host: Some("host".to_string()),
                is_server: false,
            }
        );

        assert_eq!(
            Source::parse("nick!~user").unwrap(),
            Source {
                nick: "nick".to_string(),
                ident: Some("~user".to_string()),
                host: None,
                is_server: false,
            }
        );

        assert_eq!(
            Source::parse("nick").unwrap(),
            Source {
                nick: "nick".to_string(),
                ident: None,
                host: None,
                is_server: false,
            }
        );

        assert_eq!(
            Source::parse(":irc.server!~ident@host").unwrap(),
            Source {
                nick: "irc.server".to_string(),
                ident: Some("~ident".to_string()),
                host: Some("host".to_string()),
                is_server: true,
            }
        );
    }

    #[test]
    fn test_display_source() {
        let cases = vec![
            "nick",
            "nick!~ident",
            "nick!~ident@host",
            ":irc.server!~ident@host",
        ];

        for case in cases {
            let round_trip = format!("{}", Source::parse(case).unwrap());
            assert_eq!(round_trip, case);
        }
    }

    #[test]
    fn test_parse_messages() {
        let line = "@foo=bar :server 001 abc :hello, world";
        let msg = RawMessage::parse(line).expect("failed to parse");

        assert_eq!(msg.command, "001");
        assert_eq!(msg.source.unwrap().nick, "server");
        assert_eq!(msg.params, vec!["abc", "hello, world"]);
        // TODO: wire up assert
    }

    #[test]
    fn test_split_message() {
        assert_eq!(split_message("foo bar baz"), Some(("foo", "bar baz")));
        assert_eq!(split_message("foo bar"), Some(("foo", "bar")));
        assert_eq!(split_message("foo"), Some(("foo", "")));
        assert_eq!(split_message(""), None);
        assert_eq!(split_message("trailing "), Some(("trailing", "")));
        assert_eq!(split_message(" leading"), Some(("", "leading")));
    }

    #[test]
    fn test_parse_tag() {
        let cases = vec![
            (
                "key",
                Tag {
                    key: "key".to_string(),
                    value: None,
                },
            ),
            (
                "key=",
                Tag {
                    key: "key".to_string(),
                    value: Some("".to_string()),
                },
            ),
            (
                "key=val",
                Tag {
                    key: "key".to_string(),
                    value: Some("val".to_string()),
                },
            ),
        ];

        for (input, expected) in cases {
            assert_eq!(Tag::parse(input), expected);
        }
    }

    #[test]
    fn test_parse_tags() {
        let tags = Tags::parse("@foo=bar;baz=;quux");
        let expected = vec![
            (
                "foo".to_string(),
                Tag {
                    key: "foo".to_string(),
                    value: Some("bar".to_string()),
                },
            ),
            (
                "baz".to_string(),
                Tag {
                    key: "baz".to_string(),
                    value: Some("".to_string()),
                },
            ),
            (
                "quux".to_string(),
                Tag {
                    key: "quux".to_string(),
                    value: None,
                },
            ),
        ]
        .into_iter()
        .collect();

        assert_eq!(tags, Some(Tags(expected)));
    }
}
<|endoftext|>


<commit_before>use futures_util::future::poll_fn;
use redis::{ConnectionAddr, ConnectionInfo, ErrorKind, RedisError, RedisResult, Value};
use std::collections::hash_map::{Entry, HashMap};
use std::future::Future;
use std::io::Result as IoResult;
use std::net::ToSocketAddrs;
use std::pin::Pin;
use std::task::Poll;
use tokio::io::{self, AsyncWriteExt};
use tokio::net::{TcpStream, UnixStream};
use tokio::stream::Stream;
use tokio::sync::{broadcast, mpsc, oneshot};

/// Read half of a Redis pubsub connection.
struct ReadHalf(io::BufReader<Box<dyn io::AsyncRead + Unpin + Send>>);
impl ReadHalf {
    /// Read a value from Redis.
    async fn read(&mut self) -> RedisResult<Value> {
        redis::parse_redis_value_async(&mut self.0).await
    }
}

/// Write half of a Redis pubsub connection.
struct WriteHalf(Box<dyn io::AsyncWrite + Unpin + Send>);
impl WriteHalf {
    /// Write a command on the Redis connection.
    async fn write(&mut self, cmd: &[&[u8]]) -> IoResult<()> {
        let mut data = format!("*{}\r\n", cmd.len()).into_bytes();
        for part in cmd {
            data.append(&mut format!("${}\r\n", part.len()).into_bytes());
            data.extend_from_slice(part);
            data.extend_from_slice(b"\r\n");
        }
        self.0.write_all(&data).await
    }
}

/// Channel type used to receive pubsub messages.
pub type RecvChan = broadcast::Receiver<Vec<u8>>;

/// Channel type used to reply to `Cmd`.
type ReplyChan = oneshot::Sender<RecvChan>;

/// Command type sent to the connection loop.
struct Cmd {
    /// Channel to subscribe to.
    chan: Vec<u8>,
    /// Reply channel for the command.
    reply: ReplyChan,
}

/// Tracks an active subscription on the Redis server.
struct Sub {
    /// Channel sender used to notify all logical subscribers.
    tx: broadcast::Sender<Vec<u8>>,
    /// Reply channels that are awaiting confirmation.
    pending: Option<Vec<ReplyChan>>,
}

/// Polling events that can happen in the connection loop.
enum LoopEvent {
    Cmd(Cmd),
    CmdClosed,
    Interval,
    Read((RedisResult<Value>, ReadHalf)),
}

/// The Redis pubsub connection loop.
async fn conn_loop(mut rx: ReadHalf, mut tx: WriteHalf, mut cmd: mpsc::Receiver<Cmd>) {
    let interval = tokio::time::interval(tokio::time::Duration::from_secs(20));
    tokio::pin!(interval);

    // TODO: The redis crate has a ValueCodec, but doesn't expose it. This is a workaround.
    let mut read_fut: Pin<Box<dyn Future<Output = _> + Send>> = Box::pin(async move {
        let res = rx.read().await;
        (res, rx)
    });

    let mut subs: HashMap<Vec<u8>, Sub> = HashMap::new();

    loop {
        // This specifically prioritizes processing commands over receiving.
        match poll_fn(|cx| {
            if let Poll::Ready(res) = cmd.poll_recv(cx) {
                match res {
                    Some(cmd) => Poll::Ready(LoopEvent::Cmd(cmd)),
                    None => Poll::Ready(LoopEvent::CmdClosed),
                }
            } else if let Poll::Ready(_) = interval.as_mut().poll_next(cx) {
                Poll::Ready(LoopEvent::Interval)
            } else if let Poll::Ready(res) = read_fut.as_mut().poll(cx) {
                Poll::Ready(LoopEvent::Read(res))
            } else {
                Poll::Pending
            }
        })
        .await
        {
            LoopEvent::Cmd(Cmd { chan, reply }) => match subs.entry(chan.clone()) {
                Entry::Occupied(mut entry) => {
                    let sub = entry.get_mut();
                    if let Some(ref mut pending) = sub.pending {
                        pending.push(reply);
                    } else {
                        let _ = reply.send(sub.tx.subscribe());
                    }
                }
                Entry::Vacant(entry) => {
                    entry.insert(Sub {
                        tx: broadcast::channel(8).0,
                        pending: Some(vec![reply]),
                    });
                    tx.write(&[b"SUBSCRIBE", &chan])
                        .await
                        .expect("Failed to send subscribe command to Redis");
                }
            },
            LoopEvent::CmdClosed => {
                // TODO: Stop reading from the command channel.
                // This, plus an empty `subs`, means we can exit.
                unimplemented!();
            }
            LoopEvent::Interval => {
                // Unsubscribe from channels that no longer have subscribers, or send a ping.
                let to_unsub: Vec<Vec<u8>> = subs
                    .iter()
                    .filter_map(|(chan, sub)| {
                        if sub.tx.receiver_count() == 0 {
                            Some(chan.clone())
                        } else {
                            None
                        }
                    })
                    .collect();
                if to_unsub.is_empty() {
                    tx.write(&[b"PING"])
                        .await
                        .expect("Failed to send ping command to Redis");
                } else {
                    for chan in &to_unsub {
                        subs.remove(chan);
                    }
                    let mut unsub_cmd: Vec<&[u8]> = vec![b"UNSUBSCRIBE"];
                    unsub_cmd.extend(to_unsub.iter().map(|chan| &chan[..]));
                    tx.write(&unsub_cmd)
                        .await
                        .expect("Failed to send unsubscribe command to Redis");
                }
            }
            LoopEvent::Read((res, mut rx)) => {
                read_fut = Box::pin(async move {
                    let res = rx.read().await;
                    (res, rx)
                });
                let value = res.expect("Failed to read from Redis");
                let vec = match value {
                    // Note: If we have no subscriptions at all, we receive pongs as regular
                    // replies instead of events.
                    Value::Status(status) if status == "PONG" => continue,
                    Value::Bulk(ref vec) if vec.len() >= 2 => vec,
                    _ => panic!("Unexpected value from Redis: {:?}", value),
                };
                match (&vec[0], &vec[1], vec.get(2)) {
                    // Handle a message event by sending on the broadcast channel.
                    (
                        &Value::Data(ref ev),
                        &Value::Data(ref chan),
                        Some(&Value::Data(ref data)),
                    ) if ev == b"message" => {
                        if let Some(ref sub) = subs.get(&chan[..]) {
                            let _ = sub.tx.send(data.to_vec());
                        }
                    }
                    // Handle subscription confirmation by sending out pending replies.
                    (&Value::Data(ref ev), &Value::Data(ref chan), _) if ev == b"subscribe" => {
                        if let Some(ref mut sub) = subs.get_mut(&chan[..]) {
                            if let Some(pending) = sub.pending.take() {
                                for reply in pending {
                                    let _ = reply.send(sub.tx.subscribe());
                                }
                            }
                        }
                    }
                    // Some other events are ok, but we do nothing with them.
                    (&Value::Data(ref ev), _, _) if ev == b"unsubscribe" || ev == b"pong" => {}
                    _ => panic!("Unexpected value from Redis: {:?}", value),
                }
            }
        }
    }
}

/// A Subscriber can be used to subscribe to Redis pubsub channels.
///
/// This struct can be cheaply cloned. It is simply a client of the connection loop which is
/// running in another task.
#[derive(Clone)]
pub struct Subscriber {
    cmd: mpsc::Sender<Cmd>,
}

impl Subscriber {
    /// Subscribe to a channel.
    ///
    /// This function does not complete until the server has confirmed the subscription.
    pub async fn subscribe(&mut self, chan: Vec<u8>) -> broadcast::Receiver<Vec<u8>> {
        let (reply_tx, reply_rx) = oneshot::channel();
        let cmd = Cmd {
            chan,
            reply: reply_tx,
        };
        if self.cmd.send(cmd).await.is_ok() {
            if let Ok(rx) = reply_rx.await {
                return rx;
            }
        }
        panic!("Tried to subscribe on closed pubsub connection");
    }
}

/// Make a pubsub connection to Redis.
pub async fn connect(info: &ConnectionInfo) -> RedisResult<Subscriber> {
    // Note: This code is borrowed from the redis crate.
    let (rx, tx): (
        Box<dyn io::AsyncRead + Unpin + Send>,
        Box<dyn io::AsyncWrite + Unpin + Send>,
    ) = match *info.addr {
        ConnectionAddr::Tcp(ref host, port) => {
            let socket_addr = {
                let mut socket_addrs = (&host[..], port).to_socket_addrs()?;
                match socket_addrs.next() {
                    Some(socket_addr) => socket_addr,
                    None => {
                        return Err(RedisError::from((
                            ErrorKind::InvalidClientConfig,
                            "No address found for host",
                        )));
                    }
                }
            };

            let (rx, tx) = io::split(TcpStream::connect(&socket_addr).await?);
            (Box::new(rx), Box::new(tx))
        }

        #[cfg(unix)]
        ConnectionAddr::Unix(ref path) => {
            let (rx, tx) = io::split(UnixStream::connect(path).await?);
            (Box::new(rx), Box::new(tx))
        }

        #[cfg(not(unix))]
        ConnectionAddr::Unix(_) => {
            return Err(RedisError::from((
                ErrorKind::InvalidClientConfig,
                "Cannot connect to unix sockets \
                 on this platform",
            )))
        }
    };

    let mut rx = ReadHalf(io::BufReader::new(rx));
    let mut tx = WriteHalf(tx);

    if let Some(ref passwd) = info.passwd {
        tx.write(&[b"AUTH", passwd.as_bytes()]).await?;
        match rx.read().await {
            Ok(Value::Okay) => (),
            _ => {
                return Err((
                    ErrorKind::AuthenticationFailed,
                    "Password authentication failed",
                )
                    .into());
            }
        }
    }

    // Note: Pubsub ignores database ID, so we don't need to send `SELECT`.

    let (cmd_tx, cmd_rx) = mpsc::channel(8);
    tokio::spawn(conn_loop(rx, tx, cmd_rx));
    Ok(Subscriber { cmd: cmd_tx })
}
<commit_msg>Fix windows build<commit_after>use futures_util::future::poll_fn;
use redis::{ConnectionAddr, ConnectionInfo, ErrorKind, RedisError, RedisResult, Value};
use std::collections::hash_map::{Entry, HashMap};
use std::future::Future;
use std::io::Result as IoResult;
use std::net::ToSocketAddrs;
use std::pin::Pin;
use std::task::Poll;
use tokio::io::{self, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::stream::Stream;
use tokio::sync::{broadcast, mpsc, oneshot};

#[cfg(unix)]
use tokio::net::UnixStream;

/// Read half of a Redis pubsub connection.
struct ReadHalf(io::BufReader<Box<dyn io::AsyncRead + Unpin + Send>>);
impl ReadHalf {
    /// Read a value from Redis.
    async fn read(&mut self) -> RedisResult<Value> {
        redis::parse_redis_value_async(&mut self.0).await
    }
}

/// Write half of a Redis pubsub connection.
struct WriteHalf(Box<dyn io::AsyncWrite + Unpin + Send>);
impl WriteHalf {
    /// Write a command on the Redis connection.
    async fn write(&mut self, cmd: &[&[u8]]) -> IoResult<()> {
        let mut data = format!("*{}\r\n", cmd.len()).into_bytes();
        for part in cmd {
            data.append(&mut format!("${}\r\n", part.len()).into_bytes());
            data.extend_from_slice(part);
            data.extend_from_slice(b"\r\n");
        }
        self.0.write_all(&data).await
    }
}

/// Channel type used to receive pubsub messages.
pub type RecvChan = broadcast::Receiver<Vec<u8>>;

/// Channel type used to reply to `Cmd`.
type ReplyChan = oneshot::Sender<RecvChan>;

/// Command type sent to the connection loop.
struct Cmd {
    /// Channel to subscribe to.
    chan: Vec<u8>,
    /// Reply channel for the command.
    reply: ReplyChan,
}

/// Tracks an active subscription on the Redis server.
struct Sub {
    /// Channel sender used to notify all logical subscribers.
    tx: broadcast::Sender<Vec<u8>>,
    /// Reply channels that are awaiting confirmation.
    pending: Option<Vec<ReplyChan>>,
}

/// Polling events that can happen in the connection loop.
enum LoopEvent {
    Cmd(Cmd),
    CmdClosed,
    Interval,
    Read((RedisResult<Value>, ReadHalf)),
}

/// The Redis pubsub connection loop.
async fn conn_loop(mut rx: ReadHalf, mut tx: WriteHalf, mut cmd: mpsc::Receiver<Cmd>) {
    let interval = tokio::time::interval(tokio::time::Duration::from_secs(20));
    tokio::pin!(interval);

    // TODO: The redis crate has a ValueCodec, but doesn't expose it. This is a workaround.
    let mut read_fut: Pin<Box<dyn Future<Output = _> + Send>> = Box::pin(async move {
        let res = rx.read().await;
        (res, rx)
    });

    let mut subs: HashMap<Vec<u8>, Sub> = HashMap::new();

    loop {
        // This specifically prioritizes processing commands over receiving.
        match poll_fn(|cx| {
            if let Poll::Ready(res) = cmd.poll_recv(cx) {
                match res {
                    Some(cmd) => Poll::Ready(LoopEvent::Cmd(cmd)),
                    None => Poll::Ready(LoopEvent::CmdClosed),
                }
            } else if let Poll::Ready(_) = interval.as_mut().poll_next(cx) {
                Poll::Ready(LoopEvent::Interval)
            } else if let Poll::Ready(res) = read_fut.as_mut().poll(cx) {
                Poll::Ready(LoopEvent::Read(res))
            } else {
                Poll::Pending
            }
        })
        .await
        {
            LoopEvent::Cmd(Cmd { chan, reply }) => match subs.entry(chan.clone()) {
                Entry::Occupied(mut entry) => {
                    let sub = entry.get_mut();
                    if let Some(ref mut pending) = sub.pending {
                        pending.push(reply);
                    } else {
                        let _ = reply.send(sub.tx.subscribe());
                    }
                }
                Entry::Vacant(entry) => {
                    entry.insert(Sub {
                        tx: broadcast::channel(8).0,
                        pending: Some(vec![reply]),
                    });
                    tx.write(&[b"SUBSCRIBE", &chan])
                        .await
                        .expect("Failed to send subscribe command to Redis");
                }
            },
            LoopEvent::CmdClosed => {
                // TODO: Stop reading from the command channel.
                // This, plus an empty `subs`, means we can exit.
                unimplemented!();
            }
            LoopEvent::Interval => {
                // Unsubscribe from channels that no longer have subscribers, or send a ping.
                let to_unsub: Vec<Vec<u8>> = subs
                    .iter()
                    .filter_map(|(chan, sub)| {
                        if sub.tx.receiver_count() == 0 {
                            Some(chan.clone())
                        } else {
                            None
                        }
                    })
                    .collect();
                if to_unsub.is_empty() {
                    tx.write(&[b"PING"])
                        .await
                        .expect("Failed to send ping command to Redis");
                } else {
                    for chan in &to_unsub {
                        subs.remove(chan);
                    }
                    let mut unsub_cmd: Vec<&[u8]> = vec![b"UNSUBSCRIBE"];
                    unsub_cmd.extend(to_unsub.iter().map(|chan| &chan[..]));
                    tx.write(&unsub_cmd)
                        .await
                        .expect("Failed to send unsubscribe command to Redis");
                }
            }
            LoopEvent::Read((res, mut rx)) => {
                read_fut = Box::pin(async move {
                    let res = rx.read().await;
                    (res, rx)
                });
                let value = res.expect("Failed to read from Redis");
                let vec = match value {
                    // Note: If we have no subscriptions at all, we receive pongs as regular
                    // replies instead of events.
                    Value::Status(status) if status == "PONG" => continue,
                    Value::Bulk(ref vec) if vec.len() >= 2 => vec,
                    _ => panic!("Unexpected value from Redis: {:?}", value),
                };
                match (&vec[0], &vec[1], vec.get(2)) {
                    // Handle a message event by sending on the broadcast channel.
                    (
                        &Value::Data(ref ev),
                        &Value::Data(ref chan),
                        Some(&Value::Data(ref data)),
                    ) if ev == b"message" => {
                        if let Some(ref sub) = subs.get(&chan[..]) {
                            let _ = sub.tx.send(data.to_vec());
                        }
                    }
                    // Handle subscription confirmation by sending out pending replies.
                    (&Value::Data(ref ev), &Value::Data(ref chan), _) if ev == b"subscribe" => {
                        if let Some(ref mut sub) = subs.get_mut(&chan[..]) {
                            if let Some(pending) = sub.pending.take() {
                                for reply in pending {
                                    let _ = reply.send(sub.tx.subscribe());
                                }
                            }
                        }
                    }
                    // Some other events are ok, but we do nothing with them.
                    (&Value::Data(ref ev), _, _) if ev == b"unsubscribe" || ev == b"pong" => {}
                    _ => panic!("Unexpected value from Redis: {:?}", value),
                }
            }
        }
    }
}

/// A Subscriber can be used to subscribe to Redis pubsub channels.
///
/// This struct can be cheaply cloned. It is simply a client of the connection loop which is
/// running in another task.
#[derive(Clone)]
pub struct Subscriber {
    cmd: mpsc::Sender<Cmd>,
}

impl Subscriber {
    /// Subscribe to a channel.
    ///
    /// This function does not complete until the server has confirmed the subscription.
    pub async fn subscribe(&mut self, chan: Vec<u8>) -> broadcast::Receiver<Vec<u8>> {
        let (reply_tx, reply_rx) = oneshot::channel();
        let cmd = Cmd {
            chan,
            reply: reply_tx,
        };
        if self.cmd.send(cmd).await.is_ok() {
            if let Ok(rx) = reply_rx.await {
                return rx;
            }
        }
        panic!("Tried to subscribe on closed pubsub connection");
    }
}

/// Make a pubsub connection to Redis.
pub async fn connect(info: &ConnectionInfo) -> RedisResult<Subscriber> {
    // Note: This code is borrowed from the redis crate.
    let (rx, tx): (
        Box<dyn io::AsyncRead + Unpin + Send>,
        Box<dyn io::AsyncWrite + Unpin + Send>,
    ) = match *info.addr {
        ConnectionAddr::Tcp(ref host, port) => {
            let socket_addr = {
                let mut socket_addrs = (&host[..], port).to_socket_addrs()?;
                match socket_addrs.next() {
                    Some(socket_addr) => socket_addr,
                    None => {
                        return Err(RedisError::from((
                            ErrorKind::InvalidClientConfig,
                            "No address found for host",
                        )));
                    }
                }
            };

            let (rx, tx) = io::split(TcpStream::connect(&socket_addr).await?);
            (Box::new(rx), Box::new(tx))
        }

        #[cfg(unix)]
        ConnectionAddr::Unix(ref path) => {
            let (rx, tx) = io::split(UnixStream::connect(path).await?);
            (Box::new(rx), Box::new(tx))
        }

        #[cfg(not(unix))]
        ConnectionAddr::Unix(_) => {
            return Err(RedisError::from((
                ErrorKind::InvalidClientConfig,
                "Cannot connect to unix sockets \
                 on this platform",
            )))
        }
    };

    let mut rx = ReadHalf(io::BufReader::new(rx));
    let mut tx = WriteHalf(tx);

    if let Some(ref passwd) = info.passwd {
        tx.write(&[b"AUTH", passwd.as_bytes()]).await?;
        match rx.read().await {
            Ok(Value::Okay) => (),
            _ => {
                return Err((
                    ErrorKind::AuthenticationFailed,
                    "Password authentication failed",
                )
                    .into());
            }
        }
    }

    // Note: Pubsub ignores database ID, so we don't need to send `SELECT`.

    let (cmd_tx, cmd_rx) = mpsc::channel(8);
    tokio::spawn(conn_loop(rx, tx, cmd_rx));
    Ok(Subscriber { cmd: cmd_tx })
}
<|endoftext|>


<commit_before>fn main() {
    settings := {
        background_color: [1; 4],
        reload_key: 1073741882, // F1
    }
    source := "source/piston_window/square.rs"
    m := unwrap(load(source))

    set(title: call_ret(m, "title", []))

    time := 0
    last_reload := 0
    reload_interval := 0.25
    got_error := false

    loop {
        if !next_event() { break }
        if render() {
            call(m, "render", [settings])
        }
        if update() {
            dt := unwrap(update_dt())
            time += dt
            if !got_error && ((last_reload + reload_interval) < time) {
                last_reload = clone(time)
                new_m := load(source)
                if is_err(new_m) {
                    got_error = true
                    println(unwrap_err(new_m))
                    println(" ~~~ Hit F1 to reload ~~~ ")
                } else {
                    got_error = false
                    m = unwrap(new_m)
                }
            }
        }
        if press() {
            key := press_keyboard_key()
            if key == some(settings.reload_key) {
                println(" ~~~ Reloading ~~~ ")
                got_error = false
            }
        }
    }
}
<commit_msg>Refactored loader logic<commit_after>fn main() {
    settings := {
        background_color: [1; 4],
        reload_key: 1073741882, // F1
    }
    loader := new_loader(interval: 0.25)
    source := "source/piston_window/square.rs"
    m := unwrap(load(source))
    set(title: call_ret(m, "title", []))
    loop {
        if !next_event() { break }
        if render() {
            call(m, "render", [settings])
        }
        event(loader: loader, source: source, settings: settings, module: m)
    }
}

fn new_loader_interval(interval) -> {
    return {
        time: 0,
        last_reload: 0,
        reload_interval: clone(interval),
        got_error: false
    }
}

fn should_reload(loader) -> {
    return !loader.got_error
        && ((loader.last_reload + loader.reload_interval) < loader.time)
}

fn event_loader_source_settings_module(loader, source, settings, m) {
    if update() {
        dt := unwrap(update_dt())
        loader.time += dt
        if should_reload(loader) {
            loader.last_reload = loader.time
            new_m := load(source)
            if is_err(new_m) {
                loader.got_error = true
                println(unwrap_err(new_m))
                println(" ~~~ Hit F1 to reload ~~~ ")
            } else {
                loader.got_error = false
                m = unwrap(new_m)
            }
        }
    }
    if press() {
        key := press_keyboard_key()
        if key == some(settings.reload_key) {
            println(" ~~~ Reloading ~~~ ")
            loader.got_error = false
        }
    }
}
<|endoftext|>


<commit_before>extern crate translator;

fn main()
{
	let args: Vec<String> = std::env::args().collect();
	if 2 != args.len()
	{
		println!("Usage:");
		println!("{} <text to translate>", args[0]);
		return;
	}

	let translator = translator::Translator::new();
	println!("{}", translator.translate(args[1].as_slice()));
}
<commit_msg>as_slice has been removed<commit_after>extern crate translator;

fn main()
{
	let args: Vec<String> = std::env::args().collect();
	if args.len() < 2
	{
		println!("Usage:");
		println!("{} <text to translate>", args[0]);
		return;
	}

	let translator = translator::Translator::new();
	println!("{}", translator.translate(&args[1]));
}
<|endoftext|>


<commit_before>use core::rand::*;
use core::f64::sqrt;
use core::f64::pow;

#[deriving(Eq)]
pub struct Point {
    x : f64,
    y : f64
}

pub impl Point {
    fn new() -> Point {
        Point{x:0.0, y:0.0}
    }

    fn dist(&self, other : &Point) -> f64 {
        sqrt(pow(self.x-other.x,2.0)+pow(self.y-other.y,2.0))
    }
}
//
// * Génération aléatoire
//
impl Rand for Point {
    fn rand<R: Rng>(rng: &R) -> Point {
        Point { x: rng.gen::<f64>(), y: rng.gen::<f64>()}
    }
}

//
// * Opérateurs arithmétiques
//
// Addition
trait RhsOfAdd<Result> {
  fn add_lhs_to(&self, lhs: &Point) -> Result;
}
impl RhsOfAdd<Point> for Point {
  fn add_lhs_to(&self, lhs: &Point) -> Point {
    Point{x: (self.x+lhs.x), y:(self.y+lhs.y)}
   }
}
impl<Result, Rhs: RhsOfAdd<Result> > ops::Add<Rhs, Result> for Point {
  fn add(&self, rhs: &Rhs) -> Result {
    rhs.add_lhs_to(self)
  }
}
// Soustraction
trait RhsOfSub<Result> {
  fn sub_lhs_to(&self, lhs: &Point) -> Result;
}
impl RhsOfSub<Point> for Point {
  fn sub_lhs_to(&self, lhs: &Point) -> Point {
    Point{x: (-self.x+lhs.x), y:(-self.y+lhs.y)}
   }
}
impl<Result, Rhs: RhsOfSub<Result> > ops::Sub<Rhs, Result> for Point {
  fn sub(&self, rhs: &Rhs) -> Result {
    rhs.sub_lhs_to(self)
  }
}
// Multiplication
trait RhsOfMul<Result> {
  fn mul_lhs_to(&self, lhs: &Point) -> Result;
}
impl RhsOfMul<Point> for Point {
  fn mul_lhs_to(&self, lhs: &Point) -> Point {
    Point{x: (self.x*lhs.x), y:(self.y*lhs.y)}
   }
}
impl RhsOfMul<Point> for f64 {
  fn mul_lhs_to(&self, lhs: &Point) -> Point {
    Point{x: (self*lhs.x), y:(self*lhs.y)}
   }
}

impl<Result, Rhs: RhsOfMul<Result> > ops::Mul<Rhs, Result> for Point {
  fn mul(&self, rhs: &Rhs) -> Result {
    rhs.mul_lhs_to(self)
  }
}
// Division
trait RhsOfDiv<Result> {
  fn div_lhs_to(&self, lhs: &Point) -> Result;
}
impl RhsOfDiv<Point> for Point {
  fn div_lhs_to(&self, lhs: &Point) -> Point {
    Point{x: (lhs.x/self.x), y:(lhs.y/self.y)}
   }
}
impl RhsOfDiv<Point> for f64 {
  fn div_lhs_to(&self, lhs: &Point) -> Point {
    Point{x: (lhs.x/ *self), y:(lhs.y/ *self)}
   }
}
impl<Result, Rhs: RhsOfDiv<Result> > ops::Div<Rhs, Result> for Point {
  fn div(&self, rhs: &Rhs) -> Result {
    rhs.div_lhs_to(self)
  }
}

//
// * Test
//
#[test]
fn test_dist() {
    let pt1=@Point{ x: 1.0, y: 2.0};
    let pt2=@Point{ x: 2.0, y: 3.0};
    let distance=pt1.dist(pt2);

    assert!(distance==f64::sqrt(2.0));
}

#[test]
fn test_add() {
    let pt1=@Point{ x: 1.0, y: 2.0};
    let pt2=@Point{ x: -1.0, y: 3.0};
    let pt3=*pt1+*pt2;

    assert!((pt3.x==0.0)&&(pt3.y==5.0));
}

#[test]
fn test_sub() {
    let pt1=@Point{ x: 1.0, y: 2.0};
    let pt2=@Point{ x: -1.0, y: 3.0};
    let pt3=*pt1-*pt2;
    assert!((pt3.x==2.0)&&(pt3.y==-1.0));
}

#[test]
fn test_mult() {
    let pt1=@Point{ x: 1.0, y: 2.0};
    let pt2=@Point{ x: -1.0, y: 3.0};
    let pt3=(*pt1)*(*pt2);
    assert!((pt3.x==-1.0)&&(pt3.y==6.0));
    let pt4=(*pt1)*(2.0 as f64);
    assert!((pt4.x==2.0)&&(pt4.y==4.0));
}

#[test]
fn test_div() {
    let pt1=@Point{ x: 1.0, y: 2.0};
    let pt2=@Point{ x: -1.0, y: 3.0};
    let pt3=(*pt1)/(*pt2);
    assert!(pt3.dist(&Point{x:-1.0, y:2.0/3.0})==0.0);
    let pt4=(*pt1)/(2.0 as f64);
    assert!((pt4.x==0.5)&&(pt4.y==1.0));
}

#[test]
fn test_rand() {
    let pt1 : Point = rand::random();
    let pt2 : Point = rand::random();

    assert!(pt1 != pt2);
}
<commit_msg>On implémente Point - f64<commit_after>use core::rand::*;
use core::f64::sqrt;
use core::f64::pow;

#[deriving(Eq)]
pub struct Point {
    x : f64,
    y : f64
}

pub impl Point {
    fn new() -> Point {
        Point{x:0.0, y:0.0}
    }

    fn dist(&self, other : &Point) -> f64 {
        sqrt(pow(self.x-other.x,2.0)+pow(self.y-other.y,2.0))
    }
}
//
// * Génération aléatoire
//
impl Rand for Point {
    fn rand<R: Rng>(rng: &R) -> Point {
        Point { x: rng.gen::<f64>(), y: rng.gen::<f64>()}
    }
}

//
// * Opérateurs arithmétiques
//
// Addition
trait RhsOfAdd<Result> {
  fn add_lhs_to(&self, lhs: &Point) -> Result;
}
impl RhsOfAdd<Point> for Point {
  fn add_lhs_to(&self, lhs: &Point) -> Point {
    Point{x: (self.x+lhs.x), y:(self.y+lhs.y)}
   }
}
impl<Result, Rhs: RhsOfAdd<Result> > ops::Add<Rhs, Result> for Point {
  fn add(&self, rhs: &Rhs) -> Result {
    rhs.add_lhs_to(self)
  }
}
// Soustraction
trait RhsOfSub<Result> {
  fn sub_lhs_to(&self, lhs: &Point) -> Result;
}
impl RhsOfSub<Point> for Point {
  fn sub_lhs_to(&self, lhs: &Point) -> Point {
    Point{x: (-self.x+lhs.x), y:(-self.y+lhs.y)}
   }
}
impl RhsOfSub<Point> for f64 {
    fn sub_lhs_to(&self, lhs:&Point) -> Point {
        Point{x: lhs.x - *self, y: lhs.y-*self}
    }
}
impl<Result, Rhs: RhsOfSub<Result> > ops::Sub<Rhs, Result> for Point {
  fn sub(&self, rhs: &Rhs) -> Result {
    rhs.sub_lhs_to(self)
  }
}
// Multiplication
trait RhsOfMul<Result> {
  fn mul_lhs_to(&self, lhs: &Point) -> Result;
}
impl RhsOfMul<Point> for Point {
  fn mul_lhs_to(&self, lhs: &Point) -> Point {
    Point{x: (self.x*lhs.x), y:(self.y*lhs.y)}
   }
}
impl RhsOfMul<Point> for f64 {
  fn mul_lhs_to(&self, lhs: &Point) -> Point {
    Point{x: (self*lhs.x), y:(self*lhs.y)}
   }
}

impl<Result, Rhs: RhsOfMul<Result> > ops::Mul<Rhs, Result> for Point {
  fn mul(&self, rhs: &Rhs) -> Result {
    rhs.mul_lhs_to(self)
  }
}
// Division
trait RhsOfDiv<Result> {
  fn div_lhs_to(&self, lhs: &Point) -> Result;
}
impl RhsOfDiv<Point> for Point {
  fn div_lhs_to(&self, lhs: &Point) -> Point {
    Point{x: (lhs.x/self.x), y:(lhs.y/self.y)}
   }
}
impl RhsOfDiv<Point> for f64 {
  fn div_lhs_to(&self, lhs: &Point) -> Point {
    Point{x: (lhs.x/ *self), y:(lhs.y/ *self)}
   }
}
impl<Result, Rhs: RhsOfDiv<Result> > ops::Div<Rhs, Result> for Point {
  fn div(&self, rhs: &Rhs) -> Result {
    rhs.div_lhs_to(self)
  }
}

//
// * Test
//
#[test]
fn test_dist() {
    let pt1=@Point{ x: 1.0, y: 2.0};
    let pt2=@Point{ x: 2.0, y: 3.0};
    let distance=pt1.dist(pt2);

    assert!(distance==f64::sqrt(2.0));
}

#[test]
fn test_add() {
    let pt1=@Point{ x: 1.0, y: 2.0};
    let pt2=@Point{ x: -1.0, y: 3.0};
    let pt3=*pt1+*pt2;

    assert!((pt3.x==0.0)&&(pt3.y==5.0));
}

#[test]
fn test_sub() {
    let pt1=@Point{ x: 1.0, y: 2.0};
    let pt2=@Point{ x: -1.0, y: 3.0};
    let pt3=*pt1-*pt2;
    assert!((pt3.x==2.0)&&(pt3.y==-1.0));
}
fn test_sub_float() {
    let pt1=Point{ x: 1.0, y: 2.0};
    let pt3=pt1-0.5 as f64;
    assert!((pt3.x==0.5)&&(pt3.y==1.5));
}

#[test]
fn test_mult() {
    let pt1=@Point{ x: 1.0, y: 2.0};
    let pt2=@Point{ x: -1.0, y: 3.0};
    let pt3=(*pt1)*(*pt2);
    assert!((pt3.x==-1.0)&&(pt3.y==6.0));
    let pt4=(*pt1)*(2.0 as f64);
    assert!((pt4.x==2.0)&&(pt4.y==4.0));
}

#[test]
fn test_div() {
    let pt1=@Point{ x: 1.0, y: 2.0};
    let pt2=@Point{ x: -1.0, y: 3.0};
    let pt3=(*pt1)/(*pt2);
    assert!(pt3.dist(&Point{x:-1.0, y:2.0/3.0})==0.0);
    let pt4=(*pt1)/(2.0 as f64);
    assert!((pt4.x==0.5)&&(pt4.y==1.0));
}

#[test]
fn test_rand() {
    let pt1 : Point = rand::random();
    let pt2 : Point = rand::random();

    assert!(pt1 != pt2);
}
<|endoftext|>


<commit_before>use std::os;
use std::old_io::fs::PathExtensions;
use std::borrow::ToOwned;

#[cfg(target_os="macos")]
fn main() {
    /*
    let cef_dir = os::getenv("CEF_PATH")
        .expect("CEF_PATH needs to point to the directory containing the CEF DLL.");

    let cef_dir = Path::new(cef_dir);

    let cef_lib = "Chromium Embedded Framework.framework".to_owned();

    let cef_path = cef_dir.join(cef_lib.clone());
    if !cef_path.exists() {
        panic!("Unable to find {} in {}", cef_lib, cef_dir.as_str().unwrap());
    }
    println!("cargo:rustc-flags= -L {}",
             cef_dir.as_str().unwrap());*/
}

#[cfg(target_os="linux")]
fn main() {
    let cef_dir = os::getenv("CEF_PATH")
        .expect("CEF_PATH needs to point to the directory containing the CEF DLL.");

    let cef_dir = Path::new(cef_dir);

    let cef_lib = os::dll_filename("cef");

    let cef_path = cef_dir.join(cef_lib.clone());
    if !cef_path.exists() {
        panic!("Unable to find {} in {}", cef_lib, cef_dir.as_str().unwrap());
    }
    println!("cargo:rustc-flags=-l cef -L {}", cef_dir.as_str().unwrap());
}

#[cfg(target_os="windows")]
fn main() {
    let cef_dir = os::getenv("CEF_PATH")
        .expect("CEF_PATH needs to point to the directory containing the CEF DLL.");

    let cef_dir = Path::new(cef_dir);

    let cef_lib = os::dll_filename("libcef");

    let cef_path = cef_dir.join(cef_lib.clone());
    if !cef_path.exists() {
        panic!("Unable to find {} in {}", cef_lib, cef_dir.as_str().unwrap());
    }
    println!("cargo:rustc-flags=-l libcef -L {}", cef_dir.as_str().unwrap());
}
<commit_msg>Removed unused import.<commit_after>#![feature(os, io, path)]

use std::os;
use std::old_io::fs::PathExtensions;

#[cfg(target_os="macos")]
fn main() {
    /*
    let cef_dir = os::getenv("CEF_PATH")
        .expect("CEF_PATH needs to point to the directory containing the CEF DLL.");

    let cef_dir = Path::new(cef_dir);

    let cef_lib = "Chromium Embedded Framework.framework".to_owned();

    let cef_path = cef_dir.join(cef_lib.clone());
    if !cef_path.exists() {
        panic!("Unable to find {} in {}", cef_lib, cef_dir.as_str().unwrap());
    }
    println!("cargo:rustc-flags= -L {}",
             cef_dir.as_str().unwrap());*/
}

#[cfg(target_os="linux")]
fn main() {
    let cef_dir = os::getenv("CEF_PATH")
        .expect("CEF_PATH needs to point to the directory containing the CEF DLL.");

    let cef_dir = Path::new(cef_dir);

    let cef_lib = os::dll_filename("cef");

    let cef_path = cef_dir.join(cef_lib.clone());
    if !cef_path.exists() {
        panic!("Unable to find {} in {}", cef_lib, cef_dir.as_str().unwrap());
    }
    println!("cargo:rustc-flags=-l cef -L {}", cef_dir.as_str().unwrap());
}

#[cfg(target_os="windows")]
fn main() {
    let cef_dir = os::getenv("CEF_PATH")
        .expect("CEF_PATH needs to point to the directory containing the CEF DLL.");

    let cef_dir = Path::new(cef_dir);

    let cef_lib = os::dll_filename("libcef");

    let cef_path = cef_dir.join(cef_lib.clone());
    if !cef_path.exists() {
        panic!("Unable to find {} in {}", cef_lib, cef_dir.as_str().unwrap());
    }
    println!("cargo:rustc-flags=-l libcef -L {}", cef_dir.as_str().unwrap());
}
<|endoftext|>


<commit_before>use std::convert::TryInto;
use std::fmt;
use std::ops;

mod strftime;

use super::Date;

/// Liquid's native date + time type.
#[derive(
    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
#[serde(transparent)]
#[repr(transparent)]
pub struct DateTime {
    #[serde(with = "friendly_date_time")]
    inner: DateTimeImpl,
}

type DateTimeImpl = time::OffsetDateTime;

impl DateTime {
    /// Create a `DateTime` from the current moment.
    pub fn now() -> Self {
        Self {
            inner: DateTimeImpl::now_utc(),
        }
    }

    /// Makes a new NaiveDate from the calendar date (year, month and day).
    ///
    /// Panics on the out-of-range date, invalid month and/or day.
    pub fn from_ymd(year: i32, month: u8, day: u8) -> Self {
        Self {
            inner: time::Date::from_calendar_date(
                year,
                month.try_into().expect("the month is out of range"),
                day,
            )
            .expect("one or more components were invalid")
            .with_hms(0, 0, 0)
            .expect("one or more components were invalid")
            .assume_offset(time::macros::offset!(UTC)),
        }
    }

    /// Convert a `str` to `Self`
    #[allow(clippy::should_implement_trait)]
    pub fn from_str(other: &str) -> Option<Self> {
        parse_date_time(other).map(|d| Self { inner: d })
    }

    /// Replace date with `other`.
    pub fn with_date(self, other: Date) -> Self {
        Self {
            inner: self.inner.replace_date(other.inner),
        }
    }

    /// Changes the associated time zone. This does not change the actual DateTime (but will change the string representation).
    pub fn with_offset(self, offset: time::UtcOffset) -> Self {
        Self {
            inner: self.inner.to_offset(offset),
        }
    }

    /// Retrieves a date component.
    pub fn date(self) -> Date {
        Date {
            inner: self.inner.date(),
        }
    }

    /// Formats the combined date and time with the specified format string.
    ///
    /// See the [chrono::format::strftime](https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
    /// module on the supported escape sequences.
    #[inline]
    pub fn format(&self, fmt: &str) -> Result<String, strftime::DateFormatError> {
        strftime::strftime(self.inner, fmt)
    }

    /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`.
    pub fn to_rfc2822(&self) -> String {
        self.inner
            .format(&time::format_description::well_known::Rfc2822)
            .expect("always valid")
    }
}

impl DateTime {
    /// Get the year of the date.
    #[inline]
    pub fn year(&self) -> i32 {
        self.inner.year()
    }
    /// Get the month.
    #[inline]
    pub fn month(&self) -> u8 {
        self.inner.month() as u8
    }
    /// Get the day of the month.
    ///
    //// The returned value will always be in the range 1..=31.
    #[inline]
    pub fn day(&self) -> u8 {
        self.inner.day()
    }
    /// Get the day of the year.
    ///
    /// The returned value will always be in the range 1..=366 (1..=365 for common years).
    #[inline]
    pub fn ordinal(&self) -> u16 {
        self.inner.ordinal()
    }
    /// Get the ISO week number.
    ///
    /// The returned value will always be in the range 1..=53.
    #[inline]
    pub fn iso_week(&self) -> u8 {
        self.inner.iso_week()
    }
}

impl Default for DateTime {
    fn default() -> Self {
        Self {
            inner: DateTimeImpl::UNIX_EPOCH,
        }
    }
}

const DATE_TIME_FORMAT: &[time::format_description::FormatItem<'static>] = time::macros::format_description!(
    "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour sign:mandatory][offset_minute]"
);

impl fmt::Display for DateTime {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}",
            self.inner
                .format(DATE_TIME_FORMAT)
                .map_err(|_e| fmt::Error)?
        )
    }
}

impl ops::Deref for DateTime {
    type Target = DateTimeImpl;
    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl ops::DerefMut for DateTime {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.inner
    }
}

mod friendly_date_time {
    use super::*;
    use serde::{self, Deserialize, Deserializer, Serializer};

    pub(crate) fn serialize<S>(date: &DateTimeImpl, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let s = date
            .format(DATE_TIME_FORMAT)
            .map_err(serde::ser::Error::custom)?;
        serializer.serialize_str(&s)
    }

    pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<DateTimeImpl, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s: std::borrow::Cow<'_, str> = Deserialize::deserialize(deserializer)?;
        DateTimeImpl::parse(&s, DATE_TIME_FORMAT).map_err(serde::de::Error::custom)
    }
}

fn parse_date_time(s: &str) -> Option<DateTimeImpl> {
    const USER_FORMATS: &[&[time::format_description::FormatItem<'_>]] = &[
        time::macros::format_description!("[day] [month repr:long] [year] [hour]:[minute]:[second] [offset_hour sign:mandatory][offset_minute]"),
        time::macros::format_description!("[day] [month repr:short] [year] [hour]:[minute]:[second] [offset_hour sign:mandatory][offset_minute]"),
        DATE_TIME_FORMAT,
    ];

    match s {
        "now" => Some(DateTimeImpl::now_utc()),
        _ => USER_FORMATS
            .iter()
            .filter_map(|f| DateTimeImpl::parse(s, f).ok())
            .next(),
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn parse_date_time_empty_is_bad() {
        let input = "";
        let actual = parse_date_time(input);
        assert!(actual.is_none());
    }

    #[test]
    fn parse_date_time_bad() {
        let input = "aaaaa";
        let actual = parse_date_time(input);
        assert!(actual.is_none());
    }

    #[test]
    fn parse_date_time_now() {
        let input = "now";
        let actual = parse_date_time(input);
        assert!(actual.is_some());
    }

    #[test]
    fn parse_date_time_serialized_format() {
        let input = "2016-02-16 10:00:00 +0100";
        let actual = parse_date_time(input);
        assert!(actual.is_some());
    }

    #[test]
    fn parse_date_time_to_string() {
        let date = DateTime::now();
        let input = date.to_string();
        let actual = parse_date_time(&input);
        assert!(actual.is_some());
    }
}
<commit_msg>feat(core): Add two new date/time formats and tests<commit_after>use std::convert::TryInto;
use std::fmt;
use std::ops;

mod strftime;

use super::Date;

/// Liquid's native date + time type.
#[derive(
    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
#[serde(transparent)]
#[repr(transparent)]
pub struct DateTime {
    #[serde(with = "friendly_date_time")]
    inner: DateTimeImpl,
}

type DateTimeImpl = time::OffsetDateTime;

impl DateTime {
    /// Create a `DateTime` from the current moment.
    pub fn now() -> Self {
        Self {
            inner: DateTimeImpl::now_utc(),
        }
    }

    /// Makes a new NaiveDate from the calendar date (year, month and day).
    ///
    /// Panics on the out-of-range date, invalid month and/or day.
    pub fn from_ymd(year: i32, month: u8, day: u8) -> Self {
        Self {
            inner: time::Date::from_calendar_date(
                year,
                month.try_into().expect("the month is out of range"),
                day,
            )
            .expect("one or more components were invalid")
            .with_hms(0, 0, 0)
            .expect("one or more components were invalid")
            .assume_offset(time::macros::offset!(UTC)),
        }
    }

    /// Convert a `str` to `Self`
    #[allow(clippy::should_implement_trait)]
    pub fn from_str(other: &str) -> Option<Self> {
        parse_date_time(other).map(|d| Self { inner: d })
    }

    /// Replace date with `other`.
    pub fn with_date(self, other: Date) -> Self {
        Self {
            inner: self.inner.replace_date(other.inner),
        }
    }

    /// Changes the associated time zone. This does not change the actual DateTime (but will change the string representation).
    pub fn with_offset(self, offset: time::UtcOffset) -> Self {
        Self {
            inner: self.inner.to_offset(offset),
        }
    }

    /// Retrieves a date component.
    pub fn date(self) -> Date {
        Date {
            inner: self.inner.date(),
        }
    }

    /// Formats the combined date and time with the specified format string.
    ///
    /// See the [chrono::format::strftime](https://docs.rs/chrono/latest/chrono/format/strftime/index.html)
    /// module on the supported escape sequences.
    #[inline]
    pub fn format(&self, fmt: &str) -> Result<String, strftime::DateFormatError> {
        strftime::strftime(self.inner, fmt)
    }

    /// Returns an RFC 2822 date and time string such as `Tue, 1 Jul 2003 10:52:37 +0200`.
    pub fn to_rfc2822(&self) -> String {
        self.inner
            .format(&time::format_description::well_known::Rfc2822)
            .expect("always valid")
    }
}

impl DateTime {
    /// Get the year of the date.
    #[inline]
    pub fn year(&self) -> i32 {
        self.inner.year()
    }
    /// Get the month.
    #[inline]
    pub fn month(&self) -> u8 {
        self.inner.month() as u8
    }
    /// Get the day of the month.
    ///
    //// The returned value will always be in the range 1..=31.
    #[inline]
    pub fn day(&self) -> u8 {
        self.inner.day()
    }
    /// Get the day of the year.
    ///
    /// The returned value will always be in the range 1..=366 (1..=365 for common years).
    #[inline]
    pub fn ordinal(&self) -> u16 {
        self.inner.ordinal()
    }
    /// Get the ISO week number.
    ///
    /// The returned value will always be in the range 1..=53.
    #[inline]
    pub fn iso_week(&self) -> u8 {
        self.inner.iso_week()
    }
}

impl Default for DateTime {
    fn default() -> Self {
        Self {
            inner: DateTimeImpl::UNIX_EPOCH,
        }
    }
}

const DATE_TIME_FORMAT: &[time::format_description::FormatItem<'static>] = time::macros::format_description!(
    "[year]-[month]-[day] [hour]:[minute]:[second] [offset_hour sign:mandatory][offset_minute]"
);

impl fmt::Display for DateTime {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}",
            self.inner
                .format(DATE_TIME_FORMAT)
                .map_err(|_e| fmt::Error)?
        )
    }
}

impl ops::Deref for DateTime {
    type Target = DateTimeImpl;
    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl ops::DerefMut for DateTime {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.inner
    }
}

mod friendly_date_time {
    use super::*;
    use serde::{self, Deserialize, Deserializer, Serializer};

    pub(crate) fn serialize<S>(date: &DateTimeImpl, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let s = date
            .format(DATE_TIME_FORMAT)
            .map_err(serde::ser::Error::custom)?;
        serializer.serialize_str(&s)
    }

    pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result<DateTimeImpl, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s: std::borrow::Cow<'_, str> = Deserialize::deserialize(deserializer)?;
        DateTimeImpl::parse(&s, DATE_TIME_FORMAT).map_err(serde::de::Error::custom)
    }
}

// Parse a string representing the date and time.
//
// Accepts any of the formats listed below and builds return an `Option`
// containing a `DateTimeImpl`.
//
// Supported formats:
//
// * `default` - `YYYY-MM-DD HH:MM:SS`
// * `day_month` - `DD Month YYYY HH:MM:SS`
// * `day_mon` - `DD Mon YYYY HH:MM:SS`
// * `mdy` -  `MM/DD/YYYY HH:MM:SS`
// * `dow_mon` - `Dow Mon DD HH:MM:SS YYYY`
//
// Offsets in one of the following forms, and are catenated with any of
// the above formats.
//
// * `+HHMM`
// * `-HHMM`
//
// Example:
//
// * `dow_mon` format with an offset: "Tue Feb 16 10:00:00 2016 +0100"
fn parse_date_time(s: &str) -> Option<DateTimeImpl> {
    use time::macros::format_description;

    const USER_FORMATS: &[&[time::format_description::FormatItem<'_>]] = &[
        DATE_TIME_FORMAT,
        format_description!("[day] [month repr:long] [year] [hour]:[minute]:[second] [offset_hour sign:mandatory][offset_minute]"),
        format_description!("[day] [month repr:short] [year] [hour]:[minute]:[second] [offset_hour sign:mandatory][offset_minute]"),
        format_description!("[month]/[day]/[year] [hour]:[minute]:[second] [offset_hour sign:mandatory][offset_minute]"),
        format_description!("[weekday repr:short] [month repr:short] [day padding:none] [hour]:[minute]:[second] [year] [offset_hour sign:mandatory][offset_minute]"),
    ];

    if let "" = s {
        None
    } else if let "now" | "today" | "Today" = s {
        Some(DateTimeImpl::now_utc())
    } else {
        USER_FORMATS
            .iter()
            .find_map(|f| DateTimeImpl::parse(s.as_str(), f).ok())
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn parse_date_time_empty_is_bad() {
        let input = "";
        let actual = parse_date_time(input);
        assert!(actual.is_none());
    }

    #[test]
    fn parse_date_time_bad() {
        let input = "aaaaa";
        let actual = parse_date_time(input);
        assert!(actual.is_none());
    }

    #[test]
    fn parse_date_time_now() {
        let input = "now";
        let actual = parse_date_time(input);
        assert!(actual.is_some());
    }

    #[test]
    fn parse_date_time_today() {
        let input = "today";
        let actual = parse_date_time(input);
        assert!(actual.is_some());

        let input = "Today";
        let actual = parse_date_time(input);
        assert!(actual.is_some());
    }

    #[test]
    fn parse_date_time_serialized_format() {
        let input = "2016-02-16 10:00:00 +0100"; // default format with offset
        let actual = parse_date_time(input);
        assert!(actual.unwrap().unix_timestamp() == 1455613200);

        let input = "2016-02-16 10:00:00 +0000"; // default format UTC
        let actual = parse_date_time(input);
        assert!(actual.unwrap().unix_timestamp() == 1455616800);
    }

    #[test]
    fn parse_date_time_day_month_format() {
        let input = "16 February 2016 10:00:00 +0100"; // day_month format with offset
        let actual = parse_date_time(input);
        assert!(actual.unwrap().unix_timestamp() == 1455613200);

        let input = "16 February 2016 10:00:00 +0000"; // day_month format UTC
        let actual = parse_date_time(input);
        assert!(actual.unwrap().unix_timestamp() == 1455616800);
    }

    #[test]
    fn parse_date_time_day_mon_format() {
        let input = "16 Feb 2016 10:00:00 +0100"; // day_mon format with offset
        let actual = parse_date_time(input);
        assert!(actual.unwrap().unix_timestamp() == 1455613200);

        let input = "16 Feb 2016 10:00:00 +0000"; // day_mon format UTC
        let actual = parse_date_time(input);
        assert!(actual.unwrap().unix_timestamp() == 1455616800);
    }

    #[test]
    fn parse_date_time_mdy_format() {
        let input = "02/16/2016 10:00:00 +0100"; // mdy format with offset
        let actual = parse_date_time(input);
        assert!(actual.unwrap().unix_timestamp() == 1455613200);

        let input = "02/16/2016 10:00:00 +0000"; // mdy format UTC
        let actual = parse_date_time(input);
        assert!(actual.unwrap().unix_timestamp() == 1455616800);
    }

    #[test]
    fn parse_date_time_dow_mon_format() {
        let input = "Tue Feb 16 10:00:00 2016 +0100"; // dow_mon format with offset
        let actual = parse_date_time(input);
        assert!(actual.unwrap().unix_timestamp() == 1455613200);

        let input = "Tue Feb 16 10:00:00 2016 +0000"; // dow_mon format UTC
        let actual = parse_date_time(input);
        assert!(actual.unwrap().unix_timestamp() == 1455616800);
    }

    #[test]
    fn parse_date_time_to_string() {
        let date = DateTime::now();
        let input = date.to_string();
        let actual = parse_date_time(&input);
        assert!(actual.is_some());
    }
}
<|endoftext|>