<commit_before>// Copyright 2017-2019 int08h LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//!
//! Facilities for tracking client requests to the server
//!

use hashbrown::HashMap;
use hashbrown::hash_map::Iter;

use std::net::IpAddr;

///
/// Implementations of this trait record client activity
///
pub trait ClientStats {
    fn add_valid_request(&mut self, addr: &IpAddr);

    fn add_invalid_request(&mut self, addr: &IpAddr);

    fn add_health_check(&mut self, addr: &IpAddr);

    fn add_response(&mut self, addr: &IpAddr, bytes_sent: usize);

    fn total_valid_requests(&self) -> u64;

    fn total_invalid_requests(&self) -> u64;

    fn total_health_checks(&self) -> u64;

    fn total_responses_sent(&self) -> u64;

    fn total_bytes_sent(&self) -> usize;

    fn total_unique_clients(&self) -> u64;

    fn get_stats(&self, addr: &IpAddr) -> Option<&StatEntry>;

    fn iter(&self) -> Iter<IpAddr, StatEntry>;

    fn clear(&mut self);
}

///
/// Specific metrics tracked per each client
///
#[derive(Debug, Clone, Copy)]
pub struct StatEntry {
    pub valid_requests: u64,
    pub invalid_requests: u64,
    pub health_checks: u64,
    pub responses_sent: u64,
    pub bytes_sent: usize,
}

impl StatEntry {
    fn new() -> Self {
        StatEntry {
            valid_requests: 0,
            invalid_requests: 0,
            health_checks: 0,
            responses_sent: 0,
            bytes_sent: 0,
        }
    }
}

///
/// Implementation of `ClientStats` backed by a hashmap.
///
/// Maintains a maximum of `MAX_CLIENTS` unique entries to bound memory use. Excess
/// entries beyond `MAX_CLIENTS` are ignored and `num_overflows` is incremented.
///
pub struct SimpleStats {
    clients: HashMap<IpAddr, StatEntry>,
    num_overflows: u64,
    max_clients: usize,
}

impl SimpleStats {

    /// Maximum number of stats entries to maintain to prevent
    /// unbounded memory growth.
    pub const MAX_CLIENTS: usize = 1_000_000;

    pub fn new() -> Self {
        SimpleStats {
            clients: HashMap::with_capacity(128),
            num_overflows: 0,
            max_clients: SimpleStats::MAX_CLIENTS,
        }
    }

    // visible for testing
    #[cfg(test)]
    fn with_limits(limit: usize) -> Self {
        SimpleStats {
            clients: HashMap::with_capacity(128),
            num_overflows: 0,
            max_clients: limit,
        }
    }

    #[inline]
    fn too_many_entries(&mut self) -> bool {
        let too_big = self.clients.len() >= self.max_clients;

        if too_big {
            self.num_overflows += 1;
        }

        return too_big;
    }

    #[allow(dead_code)]
    pub fn num_overflows(&self) -> u64 {
        self.num_overflows
    }
}

impl ClientStats for SimpleStats {
    fn add_valid_request(&mut self, addr: &IpAddr) {
        if self.too_many_entries() {
            return;
        }
        self.clients
            .entry(*addr)
            .or_insert_with(StatEntry::new)
            .valid_requests += 1;
    }

    fn add_invalid_request(&mut self, addr: &IpAddr) {
        if self.too_many_entries() {
            return;
        }
        self.clients
            .entry(*addr)
            .or_insert_with(StatEntry::new)
            .invalid_requests += 1;
    }

    fn add_health_check(&mut self, addr: &IpAddr) {
        if self.too_many_entries() {
            return;
        }
        self.clients
            .entry(*addr)
            .or_insert_with(StatEntry::new)
            .health_checks += 1;
    }

    fn add_response(&mut self, addr: &IpAddr, bytes_sent: usize) {
        if self.too_many_entries() {
            return;
        }
        let entry = self.clients
            .entry(*addr)
            .or_insert_with(StatEntry::new);

        entry.responses_sent += 1;
        entry.bytes_sent += bytes_sent;
    }

    fn total_valid_requests(&self) -> u64 {
        self.clients
            .values()
            .map(|&v| v.valid_requests)
            .sum()
    }

    fn total_invalid_requests(&self) -> u64 {
        self.clients
            .values()
            .map(|&v| v.invalid_requests)
            .sum()
    }

    fn total_health_checks(&self) -> u64 {
        self.clients
            .values()
            .map(|&v| v.health_checks)
            .sum()
    }

    fn total_responses_sent(&self) -> u64 {
        self.clients
            .values()
            .map(|&v| v.responses_sent)
            .sum()
    }

    fn total_bytes_sent(&self) -> usize {
        self.clients
            .values()
            .map(|&v| v.bytes_sent)
            .sum()
    }

    fn total_unique_clients(&self) -> u64 {
        self.clients.len() as u64
    }

    fn get_stats(&self, addr: &IpAddr) -> Option<&StatEntry> {
        self.clients.get(addr)
    }

    fn iter(&self) -> Iter<IpAddr, StatEntry> {
        self.clients.iter()
    }

    fn clear(&mut self) {
        self.clients.clear();
        self.num_overflows = 0;
    }
}

#[cfg(test)]
mod test {
    use crate::stats::{ClientStats, SimpleStats};
    use std::net::{IpAddr, Ipv4Addr};

    #[test]
    fn simple_stats_starts_empty() {
        let stats = SimpleStats::new();

        assert_eq!(stats.total_valid_requests(), 0);
        assert_eq!(stats.total_invalid_requests(), 0);
        assert_eq!(stats.total_health_checks(), 0);
        assert_eq!(stats.total_responses_sent(), 0);
        assert_eq!(stats.total_bytes_sent(), 0);
        assert_eq!(stats.total_unique_clients(), 0);
        assert_eq!(stats.num_overflows(), 0);
    }

    #[test]
    fn client_requests_are_tracked() {
        let mut stats = SimpleStats::new();

        let ip1 = "127.0.0.1".parse().unwrap();
        let ip2 = "127.0.0.2".parse().unwrap();
        let ip3 = "127.0.0.3".parse().unwrap();

        stats.add_valid_request(&ip1);
        stats.add_valid_request(&ip2);
        stats.add_valid_request(&ip3);
        assert_eq!(stats.total_valid_requests(), 3);

        stats.add_invalid_request(&ip2);
        assert_eq!(stats.total_invalid_requests(), 1);

        stats.add_response(&ip2, 8192);
        assert_eq!(stats.total_bytes_sent(), 8192);

        assert_eq!(stats.total_unique_clients(), 3);
    }

    #[test]
    fn per_client_stats() {
        let mut stats = SimpleStats::new();
        let ip = "127.0.0.3".parse().unwrap();

        stats.add_valid_request(&ip);
        stats.add_response(&ip, 2048);
        stats.add_response(&ip, 1024);

        let entry = stats.get_stats(&ip).unwrap();
        assert_eq!(entry.valid_requests, 1);
        assert_eq!(entry.invalid_requests, 0);
        assert_eq!(entry.responses_sent, 2);
        assert_eq!(entry.bytes_sent, 3072);
    }

    #[test]
    fn overflow_max_entries() {
        let mut stats = SimpleStats::with_limits(100);

        for i in 0..201 {
            let ipv4 = Ipv4Addr::from(i as u32);
            let addr = IpAddr::from(ipv4);

            stats.add_valid_request(&addr);
        };

        assert_eq!(stats.total_unique_clients(), 100);
        assert_eq!(stats.num_overflows(), 101);
    }
}

<commit_msg>Add a noop implementation of stats tracking<commit_after>// Copyright 2017-2019 int08h LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//!
//! Facilities for tracking client requests to the server
//!

use hashbrown::HashMap;
use hashbrown::hash_map::Iter;

use std::net::IpAddr;

///
/// Implementations of this trait record client activity
///
pub trait ClientStats {
    fn add_valid_request(&mut self, addr: &IpAddr);

    fn add_invalid_request(&mut self, addr: &IpAddr);

    fn add_health_check(&mut self, addr: &IpAddr);

    fn add_response(&mut self, addr: &IpAddr, bytes_sent: usize);

    fn total_valid_requests(&self) -> u64;

    fn total_invalid_requests(&self) -> u64;

    fn total_health_checks(&self) -> u64;

    fn total_responses_sent(&self) -> u64;

    fn total_bytes_sent(&self) -> usize;

    fn total_unique_clients(&self) -> u64;

    fn get_stats(&self, addr: &IpAddr) -> Option<&StatEntry>;

    fn iter(&self) -> Iter<IpAddr, StatEntry>;

    fn clear(&mut self);
}

///
/// Specific metrics tracked per each client
///
#[derive(Debug, Clone, Copy)]
pub struct StatEntry {
    pub valid_requests: u64,
    pub invalid_requests: u64,
    pub health_checks: u64,
    pub responses_sent: u64,
    pub bytes_sent: usize,
}

impl StatEntry {
    fn new() -> Self {
        StatEntry {
            valid_requests: 0,
            invalid_requests: 0,
            health_checks: 0,
            responses_sent: 0,
            bytes_sent: 0,
        }
    }
}

///
/// Implementation of `ClientStats` backed by a hashmap.
///
/// Maintains a maximum of `MAX_CLIENTS` unique entries to bound memory use. Excess
/// entries beyond `MAX_CLIENTS` are ignored and `num_overflows` is incremented.
///
pub struct SimpleStats {
    clients: HashMap<IpAddr, StatEntry>,
    num_overflows: u64,
    max_clients: usize,
}

impl SimpleStats {

    /// Maximum number of stats entries to maintain to prevent
    /// unbounded memory growth.
    pub const MAX_CLIENTS: usize = 1_000_000;

    pub fn new() -> Self {
        SimpleStats {
            clients: HashMap::with_capacity(128),
            num_overflows: 0,
            max_clients: SimpleStats::MAX_CLIENTS,
        }
    }

    // visible for testing
    #[cfg(test)]
    fn with_limits(limit: usize) -> Self {
        SimpleStats {
            clients: HashMap::with_capacity(128),
            num_overflows: 0,
            max_clients: limit,
        }
    }

    #[inline]
    fn too_many_entries(&mut self) -> bool {
        let too_big = self.clients.len() >= self.max_clients;

        if too_big {
            self.num_overflows += 1;
        }

        return too_big;
    }

    #[allow(dead_code)]
    pub fn num_overflows(&self) -> u64 {
        self.num_overflows
    }
}

impl ClientStats for SimpleStats {
    fn add_valid_request(&mut self, addr: &IpAddr) {
        if self.too_many_entries() {
            return;
        }
        self.clients
            .entry(*addr)
            .or_insert_with(StatEntry::new)
            .valid_requests += 1;
    }

    fn add_invalid_request(&mut self, addr: &IpAddr) {
        if self.too_many_entries() {
            return;
        }
        self.clients
            .entry(*addr)
            .or_insert_with(StatEntry::new)
            .invalid_requests += 1;
    }

    fn add_health_check(&mut self, addr: &IpAddr) {
        if self.too_many_entries() {
            return;
        }
        self.clients
            .entry(*addr)
            .or_insert_with(StatEntry::new)
            .health_checks += 1;
    }

    fn add_response(&mut self, addr: &IpAddr, bytes_sent: usize) {
        if self.too_many_entries() {
            return;
        }
        let entry = self.clients
            .entry(*addr)
            .or_insert_with(StatEntry::new);

        entry.responses_sent += 1;
        entry.bytes_sent += bytes_sent;
    }

    fn total_valid_requests(&self) -> u64 {
        self.clients
            .values()
            .map(|&v| v.valid_requests)
            .sum()
    }

    fn total_invalid_requests(&self) -> u64 {
        self.clients
            .values()
            .map(|&v| v.invalid_requests)
            .sum()
    }

    fn total_health_checks(&self) -> u64 {
        self.clients
            .values()
            .map(|&v| v.health_checks)
            .sum()
    }

    fn total_responses_sent(&self) -> u64 {
        self.clients
            .values()
            .map(|&v| v.responses_sent)
            .sum()
    }

    fn total_bytes_sent(&self) -> usize {
        self.clients
            .values()
            .map(|&v| v.bytes_sent)
            .sum()
    }

    fn total_unique_clients(&self) -> u64 {
        self.clients.len() as u64
    }

    fn get_stats(&self, addr: &IpAddr) -> Option<&StatEntry> {
        self.clients.get(addr)
    }

    fn iter(&self) -> Iter<IpAddr, StatEntry> {
        self.clients.iter()
    }

    fn clear(&mut self) {
        self.clients.clear();
        self.num_overflows = 0;
    }
}

///
/// A no-op implementation that does not track anything and has no runtime cost
///
#[allow(dead_code)]
pub struct NoOpStats {
    empty_map: HashMap<IpAddr, StatEntry>
}

impl NoOpStats {

    #[allow(dead_code)]
    pub fn new() -> Self {
        NoOpStats {
            empty_map: HashMap::new()
        }
    }
}

impl ClientStats for NoOpStats {
    fn add_valid_request(&mut self, _addr: &IpAddr) {}

    fn add_invalid_request(&mut self, _addr: &IpAddr) {}

    fn add_health_check(&mut self, _addr: &IpAddr) {}

    fn add_response(&mut self, _addr: &IpAddr, _bytes_sent: usize) {}

    fn total_valid_requests(&self) -> u64 {
        0
    }

    fn total_invalid_requests(&self) -> u64 {
        0
    }

    fn total_health_checks(&self) -> u64 {
        0
    }

    fn total_responses_sent(&self) -> u64 {
        0
    }

    fn total_bytes_sent(&self) -> usize {
        0
    }

    fn total_unique_clients(&self) -> u64 {
        0
    }

    fn get_stats(&self, _addr: &IpAddr) -> Option<&StatEntry> {
        None
    }

    fn iter(&self) -> Iter<IpAddr, StatEntry> {
        self.empty_map.iter()
    }

    fn clear(&mut self) {}
}

#[cfg(test)]
mod test {
    use crate::stats::{ClientStats, SimpleStats};
    use std::net::{IpAddr, Ipv4Addr};

    #[test]
    fn simple_stats_starts_empty() {
        let stats = SimpleStats::new();

        assert_eq!(stats.total_valid_requests(), 0);
        assert_eq!(stats.total_invalid_requests(), 0);
        assert_eq!(stats.total_health_checks(), 0);
        assert_eq!(stats.total_responses_sent(), 0);
        assert_eq!(stats.total_bytes_sent(), 0);
        assert_eq!(stats.total_unique_clients(), 0);
        assert_eq!(stats.num_overflows(), 0);
    }

    #[test]
    fn client_requests_are_tracked() {
        let mut stats = SimpleStats::new();

        let ip1 = "127.0.0.1".parse().unwrap();
        let ip2 = "127.0.0.2".parse().unwrap();
        let ip3 = "127.0.0.3".parse().unwrap();

        stats.add_valid_request(&ip1);
        stats.add_valid_request(&ip2);
        stats.add_valid_request(&ip3);
        assert_eq!(stats.total_valid_requests(), 3);

        stats.add_invalid_request(&ip2);
        assert_eq!(stats.total_invalid_requests(), 1);

        stats.add_response(&ip2, 8192);
        assert_eq!(stats.total_bytes_sent(), 8192);

        assert_eq!(stats.total_unique_clients(), 3);
    }

    #[test]
    fn per_client_stats() {
        let mut stats = SimpleStats::new();
        let ip = "127.0.0.3".parse().unwrap();

        stats.add_valid_request(&ip);
        stats.add_response(&ip, 2048);
        stats.add_response(&ip, 1024);

        let entry = stats.get_stats(&ip).unwrap();
        assert_eq!(entry.valid_requests, 1);
        assert_eq!(entry.invalid_requests, 0);
        assert_eq!(entry.responses_sent, 2);
        assert_eq!(entry.bytes_sent, 3072);
    }

    #[test]
    fn overflow_max_entries() {
        let mut stats = SimpleStats::with_limits(100);

        for i in 0..201 {
            let ipv4 = Ipv4Addr::from(i as u32);
            let addr = IpAddr::from(ipv4);

            stats.add_valid_request(&addr);
        };

        assert_eq!(stats.total_unique_clients(), 100);
        assert_eq!(stats.num_overflows(), 101);
    }
}

<|endoftext|>


<commit_before>use super::Checked;

// Test a unary operator
macro_rules! test_unop {
    ($name:ident $t:ty: $op:tt $expr1:tt == $expr2:tt) => {
        #[test]
        fn $name() {
            let x = Checked::<$t>::from($expr1);
            let y = Checked::<$t>::from($expr2);
            assert_eq!($op x, y);
            assert_eq!($op &x, y);
        }
    };
}

// Test a binary operator
macro_rules! test_binop {
    ($name:ident $t:ty: $expr1:tt $op:tt $expr2:tt == $expr3:tt) => {
        #[test]
        fn $name() {
            let x1 = Checked::<$t>::from($expr1);
            let y1 = Checked::<$t>::from($expr2);
            let x2 = $expr1 as $t;
            let y2 = $expr2 as $t;
            let z = Checked::<$t>::from($expr3);
            assert_eq!(x1 $op y1, z);
            assert_eq!(x2 $op y1, z);
            assert_eq!(x1 $op y2, z);
            assert_eq!(&x1 $op y1, z);
            assert_eq!(&x2 $op y1, z);
            assert_eq!(&x1 $op y2, z);
            assert_eq!(x1 $op &y1, z);
            assert_eq!(x2 $op &y1, z);
            assert_eq!(x1 $op &y2, z);
            assert_eq!(&x1 $op &y1, z);
            assert_eq!(&x2 $op &y1, z);
            assert_eq!(&x1 $op &y2, z);
        }
    };

    ($name:ident $t:ty, $u:ty: $expr1:tt $op:tt $expr2:tt == $expr3:tt) => {
        #[test]
        fn $name() {
            let x1 = Checked::<$t>::from($expr1);
            let y1 = Checked::<$u>::from($expr2);
            let x2 = $expr1 as $t;
            let y2 = $expr2 as $u;
            let z = Checked::<$t>::from($expr3);
            assert_eq!(x1 $op y1, z);
            assert_eq!(x2 $op y1, z);
            assert_eq!(x1 $op y2, z);
            assert_eq!(&x1 $op y1, z);
            assert_eq!(&x2 $op y1, z);
            assert_eq!(&x1 $op y2, z);
            assert_eq!(x1 $op &y1, z);
            assert_eq!(x2 $op &y1, z);
            assert_eq!(x1 $op &y2, z);
            assert_eq!(&x1 $op &y1, z);
            assert_eq!(&x2 $op &y1, z);
            assert_eq!(&x1 $op &y2, z);
        }
    };

    ($name:ident $t:ty, $u:ty, $v:ty: $expr1:tt $op:tt $expr2:tt == $expr3:tt) => {
        #[test]
        fn $name() {
            let x1 = Checked::<$t>::from($expr1);
            let y1 = Checked::<$u>::from($expr2);
            let x2 = $expr1 as $t;
            let y2 = $expr2 as $u;
            let z = Checked::<$v>::from($expr3);
            assert_eq!(x1 $op y1, z);
            assert_eq!(x2 $op y1, z);
            assert_eq!(x1 $op y2, z);
            assert_eq!(&x1 $op y1, z);
            assert_eq!(&x2 $op y1, z);
            assert_eq!(&x1 $op y2, z);
            assert_eq!(x1 $op &y1, z);
            assert_eq!(x2 $op &y1, z);
            assert_eq!(x1 $op &y2, z);
            assert_eq!(&x1 $op &y1, z);
            assert_eq!(&x2 $op &y1, z);
            assert_eq!(&x1 $op &y2, z);
        }
    };
}

// These tests use None to indicate over/underflow.
test_binop! (add1 u8: 5 + 6 == 11);
test_binop! (add2 u32: 3_000_000_000 + 2_000_000_000 == None);
test_binop! (add3 i32: (-2_000_000_000) + (-2_000_000_000) == None);
test_binop! (sub u8: 5 - 6 == None);
test_binop! (mul1 u8: 5 * 6 == 30);
test_binop! (mul2 i32: 2_000_000_000 * 3 == None);
test_binop! (div1 u8: 10 / 3 == 3);
test_binop! (div2 u8: 10 / 0 == None);
test_binop! (and u8: 5 & 6 == 4);
test_binop! (xor u8: 5 ^ 6 == 3);
test_binop! (or u8: 5 | 6 == 7);
test_binop! (rem u8: 10 % 3 == 1);
test_binop! (shl u8, u32: 10 << 3 == 80);
test_binop! (shr u8, u32: 80 >> 3 == 10);
test_unop! (neg1 u8: - 5 == None);
test_unop! (neg2 i8: - 5 == (-5));
test_unop! (not i8: ! 5 == (-6));

#[test]
fn order() {
    assert!(Checked::from(1_000_u32) <= Checked::from(10_000_u32));
    assert!(!(Checked::from(1_000_u32) <= Checked::from(None)));
    assert!((Checked::from(None) <= Checked::from(1_000_u32)));
}

#[test]
fn new_checked() {
    assert_eq!(Checked::new(100) == Checked(Some(100)))
}

#[test]
fn from_checked() {
    assert_eq!(Checked::from(100_u32) = Checked(Some(100_u32)));
    assert_eq!(Checked::from(Some(100_u32)) = Checked(Some(100_u32)));
    assert_eq!(Checked::from(None) = Checked(None));
}
<commit_msg>fix old tests, add testing for using Option's methods on Checked<commit_after>use super::Checked;

// Test a unary operator
macro_rules! test_unop {
    ($name:ident $t:ty: $op:tt $expr1:tt == $expr2:tt) => {
        #[test]
        fn $name() {
            let x = Checked::<$t>::from($expr1);
            let y = Checked::<$t>::from($expr2);
            assert_eq!($op x, y);
            assert_eq!($op &x, y);
        }
    };
}

// Test a binary operator
macro_rules! test_binop {
    ($name:ident $t:ty: $expr1:tt $op:tt $expr2:tt == $expr3:tt) => {
        #[test]
        fn $name() {
            let x1 = Checked::<$t>::from($expr1);
            let y1 = Checked::<$t>::from($expr2);
            let x2 = $expr1 as $t;
            let y2 = $expr2 as $t;
            let z = Checked::<$t>::from($expr3);
            assert_eq!(x1 $op y1, z);
            assert_eq!(x2 $op y1, z);
            assert_eq!(x1 $op y2, z);
            assert_eq!(&x1 $op y1, z);
            assert_eq!(&x2 $op y1, z);
            assert_eq!(&x1 $op y2, z);
            assert_eq!(x1 $op &y1, z);
            assert_eq!(x2 $op &y1, z);
            assert_eq!(x1 $op &y2, z);
            assert_eq!(&x1 $op &y1, z);
            assert_eq!(&x2 $op &y1, z);
            assert_eq!(&x1 $op &y2, z);
        }
    };

    ($name:ident $t:ty, $u:ty: $expr1:tt $op:tt $expr2:tt == $expr3:tt) => {
        #[test]
        fn $name() {
            let x1 = Checked::<$t>::from($expr1);
            let y1 = Checked::<$u>::from($expr2);
            let x2 = $expr1 as $t;
            let y2 = $expr2 as $u;
            let z = Checked::<$t>::from($expr3);
            assert_eq!(x1 $op y1, z);
            assert_eq!(x2 $op y1, z);
            assert_eq!(x1 $op y2, z);
            assert_eq!(&x1 $op y1, z);
            assert_eq!(&x2 $op y1, z);
            assert_eq!(&x1 $op y2, z);
            assert_eq!(x1 $op &y1, z);
            assert_eq!(x2 $op &y1, z);
            assert_eq!(x1 $op &y2, z);
            assert_eq!(&x1 $op &y1, z);
            assert_eq!(&x2 $op &y1, z);
            assert_eq!(&x1 $op &y2, z);
        }
    };

    ($name:ident $t:ty, $u:ty, $v:ty: $expr1:tt $op:tt $expr2:tt == $expr3:tt) => {
        #[test]
        fn $name() {
            let x1 = Checked::<$t>::from($expr1);
            let y1 = Checked::<$u>::from($expr2);
            let x2 = $expr1 as $t;
            let y2 = $expr2 as $u;
            let z = Checked::<$v>::from($expr3);
            assert_eq!(x1 $op y1, z);
            assert_eq!(x2 $op y1, z);
            assert_eq!(x1 $op y2, z);
            assert_eq!(&x1 $op y1, z);
            assert_eq!(&x2 $op y1, z);
            assert_eq!(&x1 $op y2, z);
            assert_eq!(x1 $op &y1, z);
            assert_eq!(x2 $op &y1, z);
            assert_eq!(x1 $op &y2, z);
            assert_eq!(&x1 $op &y1, z);
            assert_eq!(&x2 $op &y1, z);
            assert_eq!(&x1 $op &y2, z);
        }
    };
}

// These tests use None to indicate over/underflow.
test_binop! (add1 u8: 5 + 6 == 11);
test_binop! (add2 u32: 3_000_000_000 + 2_000_000_000 == None);
test_binop! (add3 i32: (-2_000_000_000) + (-2_000_000_000) == None);
test_binop! (sub u8: 5 - 6 == None);
test_binop! (mul1 u8: 5 * 6 == 30);
test_binop! (mul2 i32: 2_000_000_000 * 3 == None);
test_binop! (div1 u8: 10 / 3 == 3);
test_binop! (div2 u8: 10 / 0 == None);
test_binop! (and u8: 5 & 6 == 4);
test_binop! (xor u8: 5 ^ 6 == 3);
test_binop! (or u8: 5 | 6 == 7);
test_binop! (rem u8: 10 % 3 == 1);
test_binop! (shl u8, u32: 10 << 3 == 80);
test_binop! (shr u8, u32: 80 >> 3 == 10);
test_unop! (neg1 u8: - 5 == None);
test_unop! (neg2 i8: - 5 == (-5));
test_unop! (not i8: ! 5 == (-6));

#[test]
fn order() {
    assert!(Checked::from(1_000_u32) <= Checked::from(10_000_u32));
    assert!(!(Checked::from(1_000_u32) <= Checked::from(None)));
    assert!((Checked::from(None) <= Checked::from(1_000_u32)));
}

#[test]
fn new_checked() {
    assert_eq!(Checked::new(100), Checked(Some(100)))
}

#[test]
fn from_checked() {
    assert_eq!(Checked::from(100_u32), Checked(Some(100_u32)));
    assert_eq!(Checked::from(Some(100_u32)), Checked(Some(100_u32)));
    assert_eq!(Checked::<u64>::from(None), Checked::<u64>(None));
}

#[test]
fn consuming_option_methods() {
    let x = Checked::from(1_000_i64);
    assert_eq!(x.expect("AHH!!!"), 1_000);

    let x = Checked::from(1_000_i64);
    assert_eq!(x.unwrap(), 1_000);

    let x = Checked::from(1_000_i64);
    assert_eq!(x.unwrap_or(10), 1_000);

    let x = Checked::<i64>::from(None);
    assert_eq!(x.unwrap_or(10), 10);

    let x = Checked::from(1_000_i64);
    assert_eq!(x.unwrap_or_else(|| 10), 1_000);

    let x = Checked::<i64>::from(None);
    assert_eq!(x.unwrap_or_else(|| 10), 10);

    let x = Checked::from(1_000_i64);
    assert_eq!(x.map(|x| x + 15), Some(1_015));

    let x = Checked::<i64>::from(None);
    assert_eq!(x.map(|x| x + 15), None);

    let x = Checked::from(1_000_i64);
    assert_eq!(x.map_or(10, |x| x + 15), 1_015);

    let x = Checked::<i64>::from(None);
    assert_eq!(x.map_or(10, |x| x + 15), 10);

    let x = Checked::from(1_000_i64);
    assert_eq!(x.map_or_else(|| 10, |x| x + 15), 1_015);

    let x = Checked::<i64>::from(None);
    assert_eq!(x.map_or_else(|| 10, |x| x + 15), 10);

    let x = Checked::from(1_000_i64);
    assert_eq!(x.ok_or(14), Ok(1_000));

    let x = Checked::<i64>::from(None);
    assert_eq!(x.ok_or(14), Err(14));

    let x = Checked::from(1_000_i64);
    assert_eq!(x.ok_or_else(|| 14), Ok(1_000));

    let x = Checked::<i64>::from(None);
    assert_eq!(x.ok_or_else(|| 14), Err(14));

    let x = Checked::from(1_000_i64);
    assert_eq!(x.and(Some(19)), Some(19));

    let x = Checked::<i64>::from(None);
    assert_eq!(x.and(Some(19)), None);

    let x = Checked::from(1_000_i64);
    assert_eq!(x.and_then(|x| Some(x + 15)), Some(1_015));

    let x = Checked::<i64>::from(None);
    assert_eq!(x.and_then(|x| Some(x + 15)), None);

    let x = Checked::from(1_000_i64);
    assert_eq!(x.or(Some(19)), Some(1_000));

    let x = Checked::<i64>::from(None);
    assert_eq!(x.or(Some(19)), Some(19));

    let x = Checked::from(1_000_i64);
    assert_eq!(x.or_else(|| Some(19)), Some(1_000));

    let x = Checked::<i64>::from(None);
    assert_eq!(x.or_else(|| Some(19)), Some(19));
}

#[test]
fn ref_option_methods() {
    let x = Checked::from(1_000_i64);
    let y = Checked::<i64>::from(None);

    assert!(x.is_some());
    assert!(!y.is_some());

    assert!(!x.is_none());
    assert!(y.is_none());

    // More thorough testing is done in the docs, this mostly just checks to see if it works at all.
    Option::as_ref(&x);
    Option::as_ref(&y);

    println!("Numbers in x: ");
    for t in x.iter() {
        println!("{:?}", t);
    }

    println!("Numbers in y: ");
    for t in y.iter() {
        println!("{:?}", t);
    }
}

#[test]
fn mut_ref_option_methods() {
    let mut x = Checked::from(1_000_i64);
    let mut y = Checked::<i64>::from(None);

    Option::as_mut(&mut x);
    Option::as_mut(&mut y);

    println!("Numbers in x: ");
    for t in x.iter_mut() {
        println!("{:?}", t);
    }

    println!("Numbers in y: ");
    for t in y.iter_mut() {
        println!("{:?}", t);
    }

    let z = x.take();

    assert_eq!(z, Some(1_000_i64));
    assert_eq!(x, Checked::<i64>::from(None));

    let w = y.take();
    assert_eq!(w, None);
    assert_eq!(y, Checked::<i64>::from(None));
}
<|endoftext|>


<commit_before>use std::io;
use std::io::Write;
use std::path::PathBuf;

use indoc::indoc;

use crate::platform::is_mac;

use super::version::Version;

const TABLE: &str = "F12";
const COPY_MODE: &str = "copy-mode -u";
const COPY_MODE_BOTTOM_EXIT: &str = "copy-mode -eu";
const CWD: &str = "#{pane_current_path}";
const NEXT_PANE: &str = r###"select-pane -t :.+ \; display-panes"###;
const NO_ENTER_COPY_MODE: &str = r###"#{?pane_in_mode,1,}#{?alternate_on,1,}"###;
const LAST_SESSION: &str = "switch-client -l";
#[allow(dead_code)]
const NEXT_SESSION: &str = "switch-client -n";
#[allow(dead_code)]
const PREV_SESSION: &str = "switch-client -p";

pub fn render(w: &mut dyn Write, c: &Context, v: Version) -> io::Result<()> {
    writeln!(w, "# Tmux {} configuration", v.as_str())?;
    section(w, c, "Tmux Options", render_options)?;
    section(w, c, "Prefix Keys", |w, _| {
        writeln!(w, "unbind-key C-b")?;
        writeln!(w, "bind-key -r C-b send-prefix")?;
        writeln!(w, "bind-key -r b {}", NEXT_PANE)
    })?;
    writeln!(w, "bind-key C-c command-prompt")?;
    writeln!(w, "bind-key C-l {}", LAST_SESSION)?;
    writeln!(w, "{}", "bind-key C-t display-message '#{pane_tty}'")?;
    section(w, c, "Function Key Binding", |w, _| {
        writeln!(w, "bind-key -n F1 {}", NEXT_PANE)?;
        writeln!(w, "bind-key -n F2 last-window")?;
        writeln!(w, "bind-key -n F3 previous-window")?;
        writeln!(w, "bind-key -n F4 next-window")?;
        writeln!(w, "bind-key -n F5 resize-pane -Z")?;
        writeln!(w, "bind-key -n F6 {}", choose_tree(v))?;
        writeln!(w, "bind-key -n F8 switch-client -n")?;
        for n in 1..=12 {
            writeln!(w, "bind-key -n S-F{n} send-keys F{n}", n = n)?;
        }
        Ok(())
    })?;
    section(w, c, "F12 Key Table", |w, _| {
        writeln!(w, "bind-key F12 send-keys F12")?;
        writeln!(w, "bind-key -n F12 switch-client -T {}", TABLE)?;
        for n in 1..=11 {
            writeln!(w, "bind-key -T {} F{n} send-keys F{n}", TABLE, n = n)?;
        }
        writeln!(w, "bind-key -T {} F12 detach-client", TABLE)?;
        writeln!(w, "bind-key -T {} -r Space next-layout", TABLE)?;
        for n in 1..=9 {
            writeln!(w, "bind-key -T {} {n} select-window -t {n}", TABLE, n = n)?;
        }
        Ok(())
    })?;
    section(w, c, "Pane Current Path", pane_current_path)?;
    section(w, c, "Copy Mode", |w, _| {
        writeln!(w, "bind-key C-u {}", COPY_MODE)?;
        copy_to_system_clipboard(w)?;
        // PageUp and PageDown special behaviors
        //  If the condition is match, PageUp should enter copy mode
        //  see https://www.reddit.com/r/tmux/comments/3paqoi/tmux_21_has_been_released/
        writeln!(
            w,
            r##"bind-key -T root PageUp if-shell -F "{}" "send-keys PageUp" "{}""##,
            NO_ENTER_COPY_MODE, COPY_MODE_BOTTOM_EXIT
        )?;
        half_pageup_pagedown(w)
    })?;
    // Colors
    section(w, c, "Colors", |w, c| {
        if c.support_256_color {
            writeln!(w, "{}", include_str!("256color.conf"))
        } else {
            writeln!(w, "{}", include_str!("8color.conf"))
        }
    })
}

fn render_options(w: &mut dyn Write, c: &Context) -> io::Result<()> {
    let options = indoc! {r##"
        set-option -g base-index 0
        set-option -g display-time 1200
        set-option -g history-limit 2500
        set-option -g status-keys emacs
        set-option -g status-left-length 20
        set-option -g status-right-length 60
        set-option -g status-right "#{?client_prefix,^B ,}'#[fg=colour51]#{=40:pane_title}#[default]' %H:%M %Z %a, %d"
        set-window-option -g mode-keys vi
    "##};
    write!(w, "{}", options)?;
    writeln!(
        w,
        r#"set-option -g default-shell "{}""#,
        c.default_shell.display()
    )?;
    writeln!(w, r#"set-option -g default-terminal "{}""#, c.default_term)?;
    writeln!(
        w,
        r#"set-option -g detach-on-destroy "{}""#,
        on_off!(c.detach_on_destroy)
    )?;
    writeln!(w, r#"set-option -g history-file "$NMK_HOME/.tmux_history""#)?;
    Ok(())
}

fn section<F>(w: &mut dyn Write, c: &Context, name: &str, mut f: F) -> io::Result<()>
where
    F: FnMut(&mut dyn Write, &Context) -> io::Result<()>,
{
    write_start_section(w, name)?;
    f(w, c)?;
    write_end_section(w, name)
}

fn write_start_section(c: &mut dyn Write, name: &str) -> io::Result<()> {
    let label = format!(" Start {} ", name);
    writeln!(c, "# {:-^120}", label)
}

fn write_end_section(c: &mut dyn Write, name: &str) -> io::Result<()> {
    let label = format!(" End {} ", name);
    writeln!(c, "# {:-^120}", label)
}

fn pane_current_path(w: &mut dyn Write, _: &Context) -> io::Result<()> {
    let key_binding = &[
        ("%", "split-window -h "),
        ("|", "split-window -h "),
        ("_", "split-window"),
        ("c", "new-window"),
        ("'\"'", "split-window"),
    ];
    for (key, _) in key_binding {
        writeln!(w, "unbind-key {}", key)?;
    }
    for (key, binding) in key_binding {
        writeln!(w, "bind-key {} {} -c '{}'", key, binding, CWD)?;
    }
    writeln!(
        w,
        r##"bind-key C command-prompt "new-session -c '{}' -s '%%'""##,
        CWD
    )
}

fn choose_tree(v: Version) -> String {
    let mut vec = vec!["choose-tree"];
    if v >= Version::V26 {
        vec.push("-s");
    }
    if v >= Version::V27 {
        vec.push("-Z");
    }
    vec.join(" ")
}

fn copy_to_system_clipboard(w: &mut dyn Write) -> io::Result<()> {
    let to_system_clipboard;
    if is_mac() {
        to_system_clipboard = "pbcopy";
    } else {
        to_system_clipboard = "xclip -selection clipboard";
    }
    fn write_config_line(w: &mut dyn Write, fmt_args: std::fmt::Arguments) -> io::Result<()> {
        if is_mac() {
            writeln!(w, "{}", fmt_args)
        } else {
            writeln!(w, "if-shell 'xclip -o > /dev/null 2>&1' '{}'", fmt_args)
        }
    }
    write_config_line(
        w,
        format_args!(
            r#"bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "{}""#,
            to_system_clipboard
        ),
    )
}

fn half_pageup_pagedown(w: &mut dyn Write) -> io::Result<()> {
    let key_binding = &[("PageUp", "halfpage-up"), ("PageDown", "halfpage-down")];
    for (key, binding) in key_binding {
        writeln!(w, "unbind-key -T copy-mode-vi {}", key)?;
        writeln!(
            w,
            "bind-key -T copy-mode-vi {} send-keys -X {}",
            key, binding
        )?;
    }
    Ok(())
}

pub struct Context {
    pub detach_on_destroy: bool,
    pub support_256_color: bool,
    pub default_shell: PathBuf,
    pub default_term: String,
}

impl Default for Context {
    fn default() -> Self {
        Context {
            detach_on_destroy: false,
            support_256_color: false,
            default_shell: PathBuf::from("/bin/zsh"),
            default_term: String::from("screen"),
        }
    }
}
<commit_msg>Modify Tmux key binding<commit_after>use std::io::Write;
use std::path::PathBuf;
use std::{fmt, io};

use indoc::indoc;

use crate::platform::is_mac;

use super::version::Version;

const COPY_MODE: &str = "copy-mode -u";
const COPY_MODE_BOTTOM_EXIT: &str = "copy-mode -eu";
const CWD: &str = "#{pane_current_path}";
const F12_TABLE: &str = "F12";
const LAST_SESSION: &str = "switch-client -l";
const NEXT_PANE: &str = r###"select-pane -t :.+ \; display-panes"###;
const NO_ENTER_COPY_MODE: &str = r###"#{?pane_in_mode,1,}#{?alternate_on,1,}"###;

pub fn render(w: &mut dyn Write, c: &Context, v: Version) -> io::Result<()> {
    writeln!(w, "# Tmux {} configuration", v.as_str())?;
    section(w, c, "Tmux Options", render_options)?;
    section(w, c, "Prefix Keys", |w, _| {
        writeln!(w, "bind-key -r C-b send-prefix")
    })?;
    writeln!(w, "bind-key -r o {}", NEXT_PANE)?;
    writeln!(w, "bind-key -r C-o {}", "rotate-window")?;
    writeln!(w, "bind-key C-c command-prompt")?;
    writeln!(w, "bind-key C-l {}", LAST_SESSION)?;
    writeln!(w, "{}", "bind-key C-t display-message '#{pane_tty}'")?;
    section(w, c, "Function Key Binding", |w, _| {
        writeln!(w, "bind-key -n F1 {}", NEXT_PANE)?;
        writeln!(w, "bind-key -n F2 last-window")?;
        writeln!(w, "bind-key -n F3 previous-window")?;
        writeln!(w, "bind-key -n F4 next-window")?;
        writeln!(w, "bind-key -n F5 resize-pane -Z")?;
        writeln!(w, "bind-key -n F6 {}", choose_tree(v))?;
        writeln!(w, "bind-key -n F8 switch-client -n")?;
        for n in 1..=12 {
            writeln!(w, "bind-key -n S-F{n} send-keys F{n}", n = n)?;
        }
        Ok(())
    })?;
    section(w, c, "F12 Key Table", |w, _| {
        writeln!(w, "bind-key F12 send-keys F12")?;
        writeln!(w, "bind-key -n F12 switch-client -T {}", F12_TABLE)?;
        for n in 1..=11 {
            writeln!(w, "bind-key -T {} F{n} send-keys F{n}", F12_TABLE, n = n)?;
        }
        writeln!(w, "bind-key -T {} F12 detach-client", F12_TABLE)?;
        writeln!(w, "bind-key -T {} -r Space next-layout", F12_TABLE)?;
        for n in 1..=9 {
            writeln!(
                w,
                "bind-key -T {} {n} select-window -t {n}",
                F12_TABLE,
                n = n
            )?;
        }
        Ok(())
    })?;
    section(w, c, "Pane Current Path", pane_current_path)?;
    section(w, c, "Copy Mode", |w, _| {
        writeln!(w, "bind-key C-u {}", COPY_MODE)?;
        copy_to_system_clipboard(w)?;
        // PageUp and PageDown special behaviors
        //  If the condition is match, PageUp should enter copy mode
        //  see https://www.reddit.com/r/tmux/comments/3paqoi/tmux_21_has_been_released/
        writeln!(
            w,
            r##"bind-key -T root PageUp if-shell -F "{}" "send-keys PageUp" "{}""##,
            NO_ENTER_COPY_MODE, COPY_MODE_BOTTOM_EXIT
        )?;
        half_pageup_pagedown(w)
    })?;
    // Colors
    section(w, c, "Colors", |w, c| {
        if c.support_256_color {
            writeln!(w, "{}", include_str!("256color.conf"))
        } else {
            writeln!(w, "{}", include_str!("8color.conf"))
        }
    })
}

fn render_options(w: &mut dyn Write, c: &Context) -> io::Result<()> {
    let options = indoc! {r##"
        set-option -g base-index 0
        set-option -g display-time 1200
        set-option -g history-limit 2500
        set-option -g status-keys emacs
        set-option -g status-left-length 20
        set-option -g status-right-length 60
        set-option -g status-right "#{?client_prefix,^B ,}'#[fg=colour51]#{=40:pane_title}#[default]' %H:%M %Z %a, %d"
        set-window-option -g mode-keys vi
    "##};
    write!(w, "{}", options)?;
    writeln!(
        w,
        r#"set-option -g default-shell "{}""#,
        c.default_shell.display()
    )?;
    writeln!(w, r#"set-option -g default-terminal "{}""#, c.default_term)?;
    writeln!(
        w,
        r#"set-option -g detach-on-destroy "{}""#,
        on_off!(c.detach_on_destroy)
    )?;
    writeln!(w, r#"set-option -g history-file "$NMK_HOME/.tmux_history""#)?;
    Ok(())
}

fn section<F>(w: &mut dyn Write, c: &Context, name: &str, mut f: F) -> io::Result<()>
where
    F: FnMut(&mut dyn Write, &Context) -> io::Result<()>,
{
    write_start_section(w, name)?;
    f(w, c)?;
    write_end_section(w, name)
}

fn write_start_section(c: &mut dyn Write, name: &str) -> io::Result<()> {
    let label = format!(" Start {} ", name);
    writeln!(c, "# {:-^120}", label)
}

fn write_end_section(c: &mut dyn Write, name: &str) -> io::Result<()> {
    let label = format!(" End {} ", name);
    writeln!(c, "# {:-^120}", label)
}

fn pane_current_path(w: &mut dyn Write, _: &Context) -> io::Result<()> {
    let key_binding = &[
        ("%", "split-window -h "),
        ("|", "split-window -h "),
        ("_", "split-window"),
        ("c", "new-window"),
        ("'\"'", "split-window"),
    ];
    for (key, binding) in key_binding {
        writeln!(w, "bind-key {} {} -c '{}'", key, binding, CWD)?;
    }
    writeln!(
        w,
        r##"bind-key C command-prompt "new-session -c '{}' -s '%%'""##,
        CWD
    )
}

fn choose_tree(v: Version) -> String {
    let mut vec = vec!["choose-tree"];
    if v >= Version::V26 {
        vec.push("-s");
    }
    if v >= Version::V27 {
        vec.push("-Z");
    }
    vec.join(" ")
}

fn copy_to_system_clipboard(w: &mut dyn Write) -> io::Result<()> {
    fn write_config(w: &mut dyn Write, arguments: fmt::Arguments<'_>) -> io::Result<()> {
        if is_mac() {
            writeln!(w, "{}", arguments)
        } else {
            writeln!(w, "if-shell 'xclip -o > /dev/null 2>&1' '{}'", arguments)
        }
    }
    let copy_cmd = if is_mac() {
        "pbcopy"
    } else {
        "xclip -selection clipboard"
    };
    // We use format_args! to avoid allocation.
    // See this comment why we can't use let binding, https://stackoverflow.com/a/56304571
    write_config(
        w,
        format_args!(
            r#"bind-key -T copy-mode-vi y send-keys -X copy-pipe-and-cancel "{}""#,
            copy_cmd
        ),
    )
}

fn half_pageup_pagedown(w: &mut dyn Write) -> io::Result<()> {
    let key_binding = &[("PageUp", "halfpage-up"), ("PageDown", "halfpage-down")];
    for (key, binding) in key_binding {
        writeln!(
            w,
            "bind-key -T copy-mode-vi {} send-keys -X {}",
            key, binding
        )?;
    }
    Ok(())
}

pub struct Context {
    pub detach_on_destroy: bool,
    pub support_256_color: bool,
    pub default_shell: PathBuf,
    pub default_term: String,
}

impl Default for Context {
    fn default() -> Self {
        Context {
            detach_on_destroy: false,
            support_256_color: false,
            default_shell: PathBuf::from("/bin/zsh"),
            default_term: String::from("screen"),
        }
    }
}
<|endoftext|>


<commit_before>/*!
The types module provides a way of associating glob patterns on file names to
file types.
*/

use std::collections::HashMap;
use std::error::Error as StdError;
use std::fmt;
use std::path::Path;

use regex;

use gitignore::{Match, Pattern};
use glob::{self, MatchOptions};

const TYPE_EXTENSIONS: &'static [(&'static str, &'static [&'static str])] = &[
    ("asm", &["*.asm", "*.s", "*.S"]),
    ("awk", &["*.awk"]),
    ("c", &["*.c", "*.h", "*.H"]),
    ("cbor", &["*.cbor"]),
    ("clojure", &["*.clj", "*.cljs"]),
    ("cmake", &["CMakeLists.txt"]),
    ("coffeescript", &["*.coffee"]),
    ("cpp", &[
        "*.C", "*.cc", "*.cpp", "*.cxx",
        "*.h", "*.H", "*.hh", "*.hpp",
    ]),
    ("csharp", &["*.cs"]),
    ("css", &["*.css"]),
    ("cython", &["*.pyx"]),
    ("dart", &["*.dart"]),
    ("d", &["*.d"]),
    ("elisp", &["*.el"]),
    ("erlang", &["*.erl", "*.hrl"]),
    ("fortran", &[
        "*.f", "*.F", "*.f77", "*.F77", "*.pfo",
        "*.f90", "*.F90", "*.f95", "*.F95",
    ]),
    ("go", &["*.go"]),
    ("groovy", &["*.groovy"]),
    ("haskell", &["*.hs", "*.lhs"]),
    ("html", &["*.htm", "*.html"]),
    ("java", &["*.java"]),
    ("js", &["*.js"]),
    ("json", &["*.json"]),
    ("jsonl", &["*.jsonl"]),
    ("lisp", &["*.el", "*.jl", "*.lisp", "*.lsp", "*.sc", "*.scm"]),
    ("lua", &["*.lua"]),
    ("m4", &["*.ac", "*.m4"]),
    ("make", &["gnumakefile", "Gnumakefile", "makefile", "Makefile", "*.mk"]),
    ("markdown", &["*.md"]),
    ("matlab", &["*.m"]),
    ("mk", &["mkfile"]),
    ("ml", &["*.ml"]),
    ("objc", &["*.h", "*.m"]),
    ("objcpp", &["*.h", "*.mm"]),
    ("ocaml", &["*.ml", "*.mli", "*.mll", "*.mly"]),
    ("perl", &["*.perl", "*.pl", "*.PL", "*.plh", "*.plx", "*.pm"]),
    ("php", &["*.php", "*.php3", "*.php4", "*.php5", "*.phtml"]),
    ("py", &["*.py"]),
    ("readme", &["README*", "*README"]),
    ("rr", &["*.R"]),
    ("rst", &["*.rst"]),
    ("ruby", &["*.rb"]),
    ("rust", &["*.rs"]),
    ("scala", &["*.scala"]),
    ("sh", &["*.bash", "*.csh", "*.ksh", "*.sh", "*.tcsh"]),
    ("sql", &["*.sql"]),
    ("tex", &["*.tex", "*.cls", "*.sty"]),
    ("txt", &["*.txt"]),
    ("toml", &["*.toml", "Cargo.lock"]),
    ("vala", &["*.vala"]),
    ("vimscript", &["*.vim"]),
    ("xml", &["*.xml"]),
    ("yacc", &["*.y"]),
    ("yaml", &["*.yaml", "*.yml"]),
];

/// Describes all the possible failure conditions for building a file type
/// matcher.
#[derive(Debug)]
pub enum Error {
    /// We tried to select (or negate) a file type that is not defined.
    UnrecognizedFileType(String),
    /// A user specified file type definition could not be parsed.
    InvalidDefinition,
    /// There was an error building the matcher (probably a bad glob).
    Glob(glob::Error),
    /// There was an error compiling a glob as a regex.
    Regex(regex::Error),
}

impl StdError for Error {
    fn description(&self) -> &str {
        match *self {
            Error::UnrecognizedFileType(_) => "unrecognized file type",
            Error::InvalidDefinition => "invalid definition",
            Error::Glob(ref err) => err.description(),
            Error::Regex(ref err) => err.description(),
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::UnrecognizedFileType(ref ty) => {
                write!(f, "unrecognized file type: {}", ty)
            }
            Error::InvalidDefinition => {
                write!(f, "invalid definition (format is type:glob, e.g., \
                           html:*.html)")
            }
            Error::Glob(ref err) => err.fmt(f),
            Error::Regex(ref err) => err.fmt(f),
        }
    }
}

impl From<glob::Error> for Error {
    fn from(err: glob::Error) -> Error {
        Error::Glob(err)
    }
}

impl From<regex::Error> for Error {
    fn from(err: regex::Error) -> Error {
        Error::Regex(err)
    }
}

/// A single file type definition.
#[derive(Clone, Debug)]
pub struct FileTypeDef {
    name: String,
    pats: Vec<String>,
}

impl FileTypeDef {
    /// Return the name of this file type.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Return the glob patterns used to recognize this file type.
    pub fn patterns(&self) -> &[String] {
        &self.pats
    }
}

/// Types is a file type matcher.
#[derive(Clone, Debug)]
pub struct Types {
    selected: Option<glob::SetYesNo>,
    negated: Option<glob::SetYesNo>,
    has_selected: bool,
    unmatched_pat: Pattern,
}

impl Types {
    /// Creates a new file type matcher from the given Gitignore matcher. If
    /// not Gitignore matcher is provided, then the file type matcher has no
    /// effect.
    ///
    /// If has_selected is true, then at least one file type was selected.
    /// Therefore, any non-matches should be ignored.
    fn new(
        selected: Option<glob::SetYesNo>,
        negated: Option<glob::SetYesNo>,
        has_selected: bool,
    ) -> Types {
        Types {
            selected: selected,
            negated: negated,
            has_selected: has_selected,
            unmatched_pat: Pattern {
                from: Path::new("<filetype>").to_path_buf(),
                original: "<N/A>".to_string(),
                pat: "<N/A>".to_string(),
                whitelist: false,
                only_dir: false,
            },
        }
    }

    /// Creates a new file type matcher that never matches.
    pub fn empty() -> Types {
        Types::new(None, None, false)
    }

    /// Returns a match for the given path against this file type matcher.
    ///
    /// The path is considered whitelisted if it matches a selected file type.
    /// The path is considered ignored if it matched a negated file type.
    /// If at least one file type is selected and path doesn't match, then
    /// the path is also considered ignored.
    pub fn matched<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> Match {
        // If we don't have any matcher, then we can't do anything.
        if self.negated.is_none() && self.selected.is_none() {
            return Match::None;
        }
        // File types don't apply to directories.
        if is_dir {
            return Match::None;
        }
        let path = path.as_ref();
        let name = match path.file_name() {
            Some(name) => name.to_string_lossy(),
            None if self.has_selected => {
                return Match::Ignored(&self.unmatched_pat);
            }
            None => {
                return Match::None;
            }
        };
        if self.negated.as_ref().map(|s| s.is_match(&*name)).unwrap_or(false) {
            return Match::Ignored(&self.unmatched_pat);
        }
        if self.selected.as_ref().map(|s|s.is_match(&*name)).unwrap_or(false) {
            return Match::Whitelist(&self.unmatched_pat);
        }
        if self.has_selected {
            Match::Ignored(&self.unmatched_pat)
        } else {
            Match::None
        }
    }
}

/// TypesBuilder builds a type matcher from a set of file type definitions and
/// a set of file type selections.
pub struct TypesBuilder {
    types: HashMap<String, Vec<String>>,
    selected: Vec<String>,
    negated: Vec<String>,
}

impl TypesBuilder {
    /// Create a new builder for a file type matcher.
    pub fn new() -> TypesBuilder {
        TypesBuilder {
            types: HashMap::new(),
            selected: vec![],
            negated: vec![],
        }
    }

    /// Build the current set of file type definitions *and* selections into
    /// a file type matcher.
    pub fn build(&self) -> Result<Types, Error> {
        let opts = MatchOptions {
            require_literal_separator: true, ..MatchOptions::default()
        };
        let selected_globs =
            if self.selected.is_empty() {
                None
            } else {
                let mut bset = glob::SetBuilder::new();
                for name in &self.selected {
                    let globs = match self.types.get(name) {
                        Some(globs) => globs,
                        None => {
                            let msg = name.to_string();
                            return Err(Error::UnrecognizedFileType(msg));
                        }
                    };
                    for glob in globs {
                        try!(bset.add_with(glob, &opts));
                    }
                }
                Some(try!(bset.build_yesno()))
            };
        let negated_globs =
            if self.negated.is_empty() {
                None
            } else {
                let mut bset = glob::SetBuilder::new();
                for name in &self.negated {
                    let globs = match self.types.get(name) {
                        Some(globs) => globs,
                        None => {
                            let msg = name.to_string();
                            return Err(Error::UnrecognizedFileType(msg));
                        }
                    };
                    for glob in globs {
                        try!(bset.add_with(glob, &opts));
                    }
                }
                Some(try!(bset.build_yesno()))
            };
        Ok(Types::new(
            selected_globs, negated_globs, !self.selected.is_empty()))
    }

    /// Return the set of current file type definitions.
    pub fn definitions(&self) -> Vec<FileTypeDef> {
        let mut defs = vec![];
        for (ref name, ref pats) in &self.types {
            let mut pats = pats.to_vec();
            pats.sort();
            defs.push(FileTypeDef {
                name: name.to_string(),
                pats: pats,
            });
        }
        defs.sort_by(|def1, def2| def1.name().cmp(def2.name()));
        defs
    }

    /// Select the file type given by `name`.
    ///
    /// If `name` is `all`, then all file types are selected.
    pub fn select(&mut self, name: &str) -> &mut TypesBuilder {
        if name == "all" {
            for name in self.types.keys() {
                self.selected.push(name.to_string());
            }
        } else {
            self.selected.push(name.to_string());
        }
        self
    }

    /// Ignore the file type given by `name`.
    ///
    /// If `name` is `all`, then all file types are negated.
    pub fn negate(&mut self, name: &str) -> &mut TypesBuilder {
        if name == "all" {
            for name in self.types.keys() {
                self.negated.push(name.to_string());
            }
        } else {
            self.negated.push(name.to_string());
        }
        self
    }

    /// Clear any file type definitions for the type given.
    pub fn clear(&mut self, name: &str) -> &mut TypesBuilder {
        self.types.remove(name);
        self
    }

    /// Add a new file type definition. `name` can be arbitrary and `pat`
    /// should be a glob recognizing file paths belonging to the `name` type.
    pub fn add(&mut self, name: &str, pat: &str) -> &mut TypesBuilder {
        self.types.entry(name.to_string())
            .or_insert(vec![]).push(pat.to_string());
        self
    }

    /// Add a new file type definition specified in string form. The format
    /// is `name:glob`. Names may not include a colon.
    pub fn add_def(&mut self, def: &str) -> Result<(), Error> {
        let name: String = def.chars().take_while(|&c| c != ':').collect();
        let pat: String = def.chars().skip(name.chars().count() + 1).collect();
        if name.is_empty() || pat.is_empty() {
            return Err(Error::InvalidDefinition);
        }
        self.add(&name, &pat);
        Ok(())
    }

    /// Add a set of default file type definitions.
    pub fn add_defaults(&mut self) -> &mut TypesBuilder {
        for &(name, exts) in TYPE_EXTENSIONS {
            for ext in exts {
                self.add(name, ext);
            }
        }
        self
    }
}

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

    macro_rules! matched {
        ($name:ident, $types:expr, $sel:expr, $selnot:expr,
         $path:expr) => {
            matched!($name, $types, $sel, $selnot, $path, true);
        };
        (not, $name:ident, $types:expr, $sel:expr, $selnot:expr,
         $path:expr) => {
            matched!($name, $types, $sel, $selnot, $path, false);
        };
        ($name:ident, $types:expr, $sel:expr, $selnot:expr,
         $path:expr, $matched:expr) => {
            #[test]
            fn $name() {
                let mut btypes = TypesBuilder::new();
                for tydef in $types {
                    btypes.add_def(tydef).unwrap();
                }
                for sel in $sel {
                    btypes.select(sel);
                }
                for selnot in $selnot {
                    btypes.negate(selnot);
                }
                let types = btypes.build().unwrap();
                let mat = types.matched($path, false);
                assert_eq!($matched, !mat.is_ignored());
            }
        };
    }

    fn types() -> Vec<&'static str> {
        vec![
            "html:*.html",
            "html:*.htm",
            "rust:*.rs",
            "js:*.js",
        ]
    }

    matched!(match1, types(), vec!["rust"], vec![], "lib.rs");
    matched!(match2, types(), vec!["html"], vec![], "index.html");
    matched!(match3, types(), vec!["html"], vec![], "index.htm");
    matched!(match4, types(), vec!["html", "rust"], vec![], "main.rs");
    matched!(match5, types(), vec![], vec![], "index.html");
    matched!(match6, types(), vec![], vec!["rust"], "index.html");

    matched!(not, matchnot1, types(), vec!["rust"], vec![], "index.html");
    matched!(not, matchnot2, types(), vec![], vec!["rust"], "main.rs");
}
<commit_msg>Add a few more R relevant extensions<commit_after>/*!
The types module provides a way of associating glob patterns on file names to
file types.
*/

use std::collections::HashMap;
use std::error::Error as StdError;
use std::fmt;
use std::path::Path;

use regex;

use gitignore::{Match, Pattern};
use glob::{self, MatchOptions};

const TYPE_EXTENSIONS: &'static [(&'static str, &'static [&'static str])] = &[
    ("asm", &["*.asm", "*.s", "*.S"]),
    ("awk", &["*.awk"]),
    ("c", &["*.c", "*.h", "*.H"]),
    ("cbor", &["*.cbor"]),
    ("clojure", &["*.clj", "*.cljs"]),
    ("cmake", &["CMakeLists.txt"]),
    ("coffeescript", &["*.coffee"]),
    ("cpp", &[
        "*.C", "*.cc", "*.cpp", "*.cxx",
        "*.h", "*.H", "*.hh", "*.hpp",
    ]),
    ("csharp", &["*.cs"]),
    ("css", &["*.css"]),
    ("cython", &["*.pyx"]),
    ("dart", &["*.dart"]),
    ("d", &["*.d"]),
    ("elisp", &["*.el"]),
    ("erlang", &["*.erl", "*.hrl"]),
    ("fortran", &[
        "*.f", "*.F", "*.f77", "*.F77", "*.pfo",
        "*.f90", "*.F90", "*.f95", "*.F95",
    ]),
    ("go", &["*.go"]),
    ("groovy", &["*.groovy"]),
    ("haskell", &["*.hs", "*.lhs"]),
    ("html", &["*.htm", "*.html"]),
    ("java", &["*.java"]),
    ("js", &["*.js"]),
    ("json", &["*.json"]),
    ("jsonl", &["*.jsonl"]),
    ("lisp", &["*.el", "*.jl", "*.lisp", "*.lsp", "*.sc", "*.scm"]),
    ("lua", &["*.lua"]),
    ("m4", &["*.ac", "*.m4"]),
    ("make", &["gnumakefile", "Gnumakefile", "makefile", "Makefile", "*.mk"]),
    ("markdown", &["*.md"]),
    ("matlab", &["*.m"]),
    ("mk", &["mkfile"]),
    ("ml", &["*.ml"]),
    ("objc", &["*.h", "*.m"]),
    ("objcpp", &["*.h", "*.mm"]),
    ("ocaml", &["*.ml", "*.mli", "*.mll", "*.mly"]),
    ("perl", &["*.perl", "*.pl", "*.PL", "*.plh", "*.plx", "*.pm"]),
    ("php", &["*.php", "*.php3", "*.php4", "*.php5", "*.phtml"]),
    ("py", &["*.py"]),
    ("readme", &["README*", "*README"]),
    ("r", &["*.R", "*.r", "*.Rmd", "*.Rnw"]),
    ("rst", &["*.rst"]),
    ("ruby", &["*.rb"]),
    ("rust", &["*.rs"]),
    ("scala", &["*.scala"]),
    ("sh", &["*.bash", "*.csh", "*.ksh", "*.sh", "*.tcsh"]),
    ("sql", &["*.sql"]),
    ("tex", &["*.tex", "*.cls", "*.sty"]),
    ("txt", &["*.txt"]),
    ("toml", &["*.toml", "Cargo.lock"]),
    ("vala", &["*.vala"]),
    ("vimscript", &["*.vim"]),
    ("xml", &["*.xml"]),
    ("yacc", &["*.y"]),
    ("yaml", &["*.yaml", "*.yml"]),
];

/// Describes all the possible failure conditions for building a file type
/// matcher.
#[derive(Debug)]
pub enum Error {
    /// We tried to select (or negate) a file type that is not defined.
    UnrecognizedFileType(String),
    /// A user specified file type definition could not be parsed.
    InvalidDefinition,
    /// There was an error building the matcher (probably a bad glob).
    Glob(glob::Error),
    /// There was an error compiling a glob as a regex.
    Regex(regex::Error),
}

impl StdError for Error {
    fn description(&self) -> &str {
        match *self {
            Error::UnrecognizedFileType(_) => "unrecognized file type",
            Error::InvalidDefinition => "invalid definition",
            Error::Glob(ref err) => err.description(),
            Error::Regex(ref err) => err.description(),
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::UnrecognizedFileType(ref ty) => {
                write!(f, "unrecognized file type: {}", ty)
            }
            Error::InvalidDefinition => {
                write!(f, "invalid definition (format is type:glob, e.g., \
                           html:*.html)")
            }
            Error::Glob(ref err) => err.fmt(f),
            Error::Regex(ref err) => err.fmt(f),
        }
    }
}

impl From<glob::Error> for Error {
    fn from(err: glob::Error) -> Error {
        Error::Glob(err)
    }
}

impl From<regex::Error> for Error {
    fn from(err: regex::Error) -> Error {
        Error::Regex(err)
    }
}

/// A single file type definition.
#[derive(Clone, Debug)]
pub struct FileTypeDef {
    name: String,
    pats: Vec<String>,
}

impl FileTypeDef {
    /// Return the name of this file type.
    pub fn name(&self) -> &str {
        &self.name
    }

    /// Return the glob patterns used to recognize this file type.
    pub fn patterns(&self) -> &[String] {
        &self.pats
    }
}

/// Types is a file type matcher.
#[derive(Clone, Debug)]
pub struct Types {
    selected: Option<glob::SetYesNo>,
    negated: Option<glob::SetYesNo>,
    has_selected: bool,
    unmatched_pat: Pattern,
}

impl Types {
    /// Creates a new file type matcher from the given Gitignore matcher. If
    /// not Gitignore matcher is provided, then the file type matcher has no
    /// effect.
    ///
    /// If has_selected is true, then at least one file type was selected.
    /// Therefore, any non-matches should be ignored.
    fn new(
        selected: Option<glob::SetYesNo>,
        negated: Option<glob::SetYesNo>,
        has_selected: bool,
    ) -> Types {
        Types {
            selected: selected,
            negated: negated,
            has_selected: has_selected,
            unmatched_pat: Pattern {
                from: Path::new("<filetype>").to_path_buf(),
                original: "<N/A>".to_string(),
                pat: "<N/A>".to_string(),
                whitelist: false,
                only_dir: false,
            },
        }
    }

    /// Creates a new file type matcher that never matches.
    pub fn empty() -> Types {
        Types::new(None, None, false)
    }

    /// Returns a match for the given path against this file type matcher.
    ///
    /// The path is considered whitelisted if it matches a selected file type.
    /// The path is considered ignored if it matched a negated file type.
    /// If at least one file type is selected and path doesn't match, then
    /// the path is also considered ignored.
    pub fn matched<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> Match {
        // If we don't have any matcher, then we can't do anything.
        if self.negated.is_none() && self.selected.is_none() {
            return Match::None;
        }
        // File types don't apply to directories.
        if is_dir {
            return Match::None;
        }
        let path = path.as_ref();
        let name = match path.file_name() {
            Some(name) => name.to_string_lossy(),
            None if self.has_selected => {
                return Match::Ignored(&self.unmatched_pat);
            }
            None => {
                return Match::None;
            }
        };
        if self.negated.as_ref().map(|s| s.is_match(&*name)).unwrap_or(false) {
            return Match::Ignored(&self.unmatched_pat);
        }
        if self.selected.as_ref().map(|s|s.is_match(&*name)).unwrap_or(false) {
            return Match::Whitelist(&self.unmatched_pat);
        }
        if self.has_selected {
            Match::Ignored(&self.unmatched_pat)
        } else {
            Match::None
        }
    }
}

/// TypesBuilder builds a type matcher from a set of file type definitions and
/// a set of file type selections.
pub struct TypesBuilder {
    types: HashMap<String, Vec<String>>,
    selected: Vec<String>,
    negated: Vec<String>,
}

impl TypesBuilder {
    /// Create a new builder for a file type matcher.
    pub fn new() -> TypesBuilder {
        TypesBuilder {
            types: HashMap::new(),
            selected: vec![],
            negated: vec![],
        }
    }

    /// Build the current set of file type definitions *and* selections into
    /// a file type matcher.
    pub fn build(&self) -> Result<Types, Error> {
        let opts = MatchOptions {
            require_literal_separator: true, ..MatchOptions::default()
        };
        let selected_globs =
            if self.selected.is_empty() {
                None
            } else {
                let mut bset = glob::SetBuilder::new();
                for name in &self.selected {
                    let globs = match self.types.get(name) {
                        Some(globs) => globs,
                        None => {
                            let msg = name.to_string();
                            return Err(Error::UnrecognizedFileType(msg));
                        }
                    };
                    for glob in globs {
                        try!(bset.add_with(glob, &opts));
                    }
                }
                Some(try!(bset.build_yesno()))
            };
        let negated_globs =
            if self.negated.is_empty() {
                None
            } else {
                let mut bset = glob::SetBuilder::new();
                for name in &self.negated {
                    let globs = match self.types.get(name) {
                        Some(globs) => globs,
                        None => {
                            let msg = name.to_string();
                            return Err(Error::UnrecognizedFileType(msg));
                        }
                    };
                    for glob in globs {
                        try!(bset.add_with(glob, &opts));
                    }
                }
                Some(try!(bset.build_yesno()))
            };
        Ok(Types::new(
            selected_globs, negated_globs, !self.selected.is_empty()))
    }

    /// Return the set of current file type definitions.
    pub fn definitions(&self) -> Vec<FileTypeDef> {
        let mut defs = vec![];
        for (ref name, ref pats) in &self.types {
            let mut pats = pats.to_vec();
            pats.sort();
            defs.push(FileTypeDef {
                name: name.to_string(),
                pats: pats,
            });
        }
        defs.sort_by(|def1, def2| def1.name().cmp(def2.name()));
        defs
    }

    /// Select the file type given by `name`.
    ///
    /// If `name` is `all`, then all file types are selected.
    pub fn select(&mut self, name: &str) -> &mut TypesBuilder {
        if name == "all" {
            for name in self.types.keys() {
                self.selected.push(name.to_string());
            }
        } else {
            self.selected.push(name.to_string());
        }
        self
    }

    /// Ignore the file type given by `name`.
    ///
    /// If `name` is `all`, then all file types are negated.
    pub fn negate(&mut self, name: &str) -> &mut TypesBuilder {
        if name == "all" {
            for name in self.types.keys() {
                self.negated.push(name.to_string());
            }
        } else {
            self.negated.push(name.to_string());
        }
        self
    }

    /// Clear any file type definitions for the type given.
    pub fn clear(&mut self, name: &str) -> &mut TypesBuilder {
        self.types.remove(name);
        self
    }

    /// Add a new file type definition. `name` can be arbitrary and `pat`
    /// should be a glob recognizing file paths belonging to the `name` type.
    pub fn add(&mut self, name: &str, pat: &str) -> &mut TypesBuilder {
        self.types.entry(name.to_string())
            .or_insert(vec![]).push(pat.to_string());
        self
    }

    /// Add a new file type definition specified in string form. The format
    /// is `name:glob`. Names may not include a colon.
    pub fn add_def(&mut self, def: &str) -> Result<(), Error> {
        let name: String = def.chars().take_while(|&c| c != ':').collect();
        let pat: String = def.chars().skip(name.chars().count() + 1).collect();
        if name.is_empty() || pat.is_empty() {
            return Err(Error::InvalidDefinition);
        }
        self.add(&name, &pat);
        Ok(())
    }

    /// Add a set of default file type definitions.
    pub fn add_defaults(&mut self) -> &mut TypesBuilder {
        for &(name, exts) in TYPE_EXTENSIONS {
            for ext in exts {
                self.add(name, ext);
            }
        }
        self
    }
}

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

    macro_rules! matched {
        ($name:ident, $types:expr, $sel:expr, $selnot:expr,
         $path:expr) => {
            matched!($name, $types, $sel, $selnot, $path, true);
        };
        (not, $name:ident, $types:expr, $sel:expr, $selnot:expr,
         $path:expr) => {
            matched!($name, $types, $sel, $selnot, $path, false);
        };
        ($name:ident, $types:expr, $sel:expr, $selnot:expr,
         $path:expr, $matched:expr) => {
            #[test]
            fn $name() {
                let mut btypes = TypesBuilder::new();
                for tydef in $types {
                    btypes.add_def(tydef).unwrap();
                }
                for sel in $sel {
                    btypes.select(sel);
                }
                for selnot in $selnot {
                    btypes.negate(selnot);
                }
                let types = btypes.build().unwrap();
                let mat = types.matched($path, false);
                assert_eq!($matched, !mat.is_ignored());
            }
        };
    }

    fn types() -> Vec<&'static str> {
        vec![
            "html:*.html",
            "html:*.htm",
            "rust:*.rs",
            "js:*.js",
        ]
    }

    matched!(match1, types(), vec!["rust"], vec![], "lib.rs");
    matched!(match2, types(), vec!["html"], vec![], "index.html");
    matched!(match3, types(), vec!["html"], vec![], "index.htm");
    matched!(match4, types(), vec!["html", "rust"], vec![], "main.rs");
    matched!(match5, types(), vec![], vec![], "index.html");
    matched!(match6, types(), vec![], vec!["rust"], "index.html");

    matched!(not, matchnot1, types(), vec!["rust"], vec![], "index.html");
    matched!(not, matchnot2, types(), vec![], vec!["rust"], "main.rs");
}
<|endoftext|>


<commit_before>use crate::errors;
use std::{
    iter::FromIterator,
    result,
    time::{Duration, SystemTime, UNIX_EPOCH},
};

/// A time tag in OSC message consists of two 32-bit integers where the first one denotes the number of seconds since 1900-01-01 and the second the fractions of a second.
/// For details on its semantics see http://opensoundcontrol.org/node/3/#timetags
///
/// # Examples
///
/// ```
/// use rosc::OscTime;
/// use std::time::SystemTime;
///
/// assert_eq!(OscTime::from(SystemTime::UNIX_EPOCH), OscTime::from((2_208_988_800, 0)));
/// ```
///
/// # Conversions between [`std::time::SystemTime`]
///
/// The [`From`]/[`Into`] traits are implemented to allow conversions between
/// [`SystemTime`](std::time::SystemTime) and `OscTime` in both directions. **These conversions are
/// lossy**, but are tested to have a deviation within 5 nanoseconds when converted back and forth
/// in either direction.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OscTime {
    pub seconds: u32,
    pub fractional: u32,
}

impl OscTime {
    const UNIX_OFFSET: u64 = 2_208_988_800; // From RFC 5905
    const TWO_POW_32: f64 = 4294967296.0; // Number of bits in a `u32`
    const NANOS_PER_SECOND: f64 = 1.0e9;

    fn epoch() -> SystemTime {
        UNIX_EPOCH - Duration::from_secs(OscTime::UNIX_OFFSET)
    }
}

impl From<SystemTime> for OscTime {
    fn from(time: SystemTime) -> OscTime {
        let duration_since_epoch = time.duration_since(OscTime::epoch()).unwrap();
        let seconds = duration_since_epoch.as_secs() as u32;
        let nanos = duration_since_epoch.subsec_nanos() as f64;
        let fractional = ((nanos * OscTime::TWO_POW_32) / OscTime::NANOS_PER_SECOND).round() as u32;
        OscTime {
            seconds,
            fractional,
        }
    }
}

impl From<OscTime> for SystemTime {
    fn from(time: OscTime) -> SystemTime {
        let nanos = (time.fractional as f64) * OscTime::NANOS_PER_SECOND / OscTime::TWO_POW_32;
        let duration_since_osc_epoch = Duration::new(time.seconds as u64, nanos as u32);
        OscTime::epoch() + duration_since_osc_epoch
    }
}

impl From<(u32, u32)> for OscTime {
    fn from(time: (u32, u32)) -> OscTime {
        let (seconds, fractional) = time;
        OscTime {
            seconds,
            fractional,
        }
    }
}

impl From<OscTime> for (u32, u32) {
    fn from(time: OscTime) -> (u32, u32) {
        (time.seconds, time.fractional)
    }
}

/// see OSC Type Tag String: [OSC Spec. 1.0](http://opensoundcontrol.org/spec-1_0)
/// padding: zero bytes (n*4)
#[derive(Clone, Debug, PartialEq)]
pub enum OscType {
    Int(i32),
    Float(f32),
    String(String),
    Blob(Vec<u8>),
    // use struct for time tag to avoid destructuring
    Time(OscTime),
    Long(i64),
    Double(f64),
    Char(char),
    Color(OscColor),
    Midi(OscMidiMessage),
    Bool(bool),
    Array(OscArray),
    Nil,
    Inf,
}
macro_rules! value_impl {
    ($(($name:ident, $variant:ident, $ty:ty)),*) => {
        $(
        impl OscType {
            #[allow(dead_code)]
            pub fn $name(self) -> Option<$ty> {
                match self {
                    OscType::$variant(v) => Some(v),
                    _ => None
                }
            }
        }
        impl From<$ty> for OscType {
            fn from(v: $ty) -> Self {
                OscType::$variant(v)
            }
        }
        )*
    }
}
value_impl! {
    (int, Int, i32),
    (float, Float, f32),
    (string, String, String),
    (blob, Blob, Vec<u8>),
    (array, Array, OscArray),
    (long, Long, i64),
    (double, Double, f64),
    (char, Char, char),
    (color, Color, OscColor),
    (midi, Midi, OscMidiMessage),
    (bool, Bool, bool)
}
impl From<(u32, u32)> for OscType {
    fn from(time: (u32, u32)) -> Self {
        OscType::Time(time.into())
    }
}
impl From<SystemTime> for OscType {
    fn from(time: SystemTime) -> Self {
        OscType::Time(time.into())
    }
}
impl OscType {
    pub fn time(self) -> Option<OscTime> {
        match self {
            OscType::Time(time) => Some(time),
            _ => None,
        }
    }
}
impl<'a> From<&'a str> for OscType {
    fn from(string: &'a str) -> Self {
        OscType::String(string.to_string())
    }
}
/// Represents the parts of a Midi message. Mainly used for
/// tunneling midi over a network using the OSC protocol.
#[derive(Clone, Debug, PartialEq)]
pub struct OscMidiMessage {
    pub port: u8,
    pub status: u8,
    pub data1: u8, // maybe use an enum for data?
    pub data2: u8,
}

/// An *osc packet* can contain an *osc message* or a bundle of nested messages
/// which is called *osc bundle*.
#[derive(Clone, Debug, PartialEq)]
pub enum OscPacket {
    Message(OscMessage),
    Bundle(OscBundle),
}

/// An OSC message consists of an address and
/// zero or more arguments. The address should
/// specify an element of your Instrument (or whatever
/// you want to control with OSC) and the arguments
/// are used to set properties of the element to the
/// respective values.
#[derive(Clone, Debug, PartialEq)]
pub struct OscMessage {
    pub addr: String,
    pub args: Vec<OscType>,
}

/// An OSC bundle contains zero or more OSC packets
/// and a time tag. The contained packets *should* be
/// applied at the given time tag.
#[derive(Clone, Debug, PartialEq)]
pub struct OscBundle {
    pub timetag: OscTime,
    pub content: Vec<OscPacket>,
}

/// An RGBA color.
#[derive(Clone, Debug, PartialEq)]
pub struct OscColor {
    pub red: u8,
    pub green: u8,
    pub blue: u8,
    pub alpha: u8,
}

/// An OscArray color.
#[derive(Clone, Debug, PartialEq)]
pub struct OscArray {
    pub content: Vec<OscType>,
}

impl<T: Into<OscType>> FromIterator<T> for OscArray {
    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> OscArray {
        OscArray {
            content: iter.into_iter().map(T::into).collect(),
        }
    }
}

pub type Result<T> = result::Result<T, errors::OscError>;

impl From<String> for OscMessage {
    fn from(s: String) -> OscMessage {
        OscMessage {
            addr: s,
            args: vec![],
        }
    }
}
impl<'a> From<&'a str> for OscMessage {
    fn from(s: &str) -> OscMessage {
        OscMessage {
            addr: s.to_string(),
            args: vec![],
        }
    }
}

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

    const TOLERANCE_NANOS: u64 = 5;

    #[test]
    fn conversions_are_reversible() {
        let times = vec![
            UNIX_EPOCH,
            UNIX_EPOCH - Duration::from_nanos(50),
            OscTime::epoch(),
        ];
        for time in times {
            for i in 0..1000 {
                let time = time + Duration::from_nanos(1) * i;
                assert_eq_system_times(time, SystemTime::from(OscTime::from(time)));
            }
        }

        for seconds in 0..10 {
            for fractional in 0..1000 {
                let osc_time = OscTime {
                    seconds,
                    fractional,
                };
                assert_eq_osc_times(osc_time, OscTime::from(SystemTime::from(osc_time)));
            }
        }
    }

    fn assert_eq_system_times(a: SystemTime, b: SystemTime) {
        let difference = if a < b {
            b.duration_since(a).unwrap()
        } else {
            a.duration_since(b).unwrap()
        };

        let tolerance = Duration::from_nanos(TOLERANCE_NANOS);

        if difference > tolerance {
            panic!(
                "the fractional seconds components of {:?} and {:?} vary more than the required tolerance of {:?}",
                a, b, tolerance,
            );
        }
    }

    fn assert_eq_osc_times(a: OscTime, b: OscTime) {
        // I did not want to implement subtraction with carrying in order to implement this in the
        // same way as the alternative for system times. Intsead we are compare each part of the
        // OSC times separately.
        let tolerance_fractional_seconds = ((TOLERANCE_NANOS as f64 * OscTime::TWO_POW_32)
            / OscTime::NANOS_PER_SECOND)
            .round() as i64;
        assert_eq!(
            a.seconds, b.seconds,
            "the seconds components of {:?} and {:?} are different",
            a, b
        );
        if (a.fractional as i64 - b.fractional as i64).abs() > tolerance_fractional_seconds {
            panic!(
                "the fractional seconds components of {:?} and {:?} vary more than the required tolerance of {} fractional seconds",
                a, b, tolerance_fractional_seconds,
            );
        }
    }
}
<commit_msg>Improve OscTime conversions<commit_after>use crate::errors;
use std::{
    convert::{TryFrom, TryInto},
    error,
    fmt::{self, Display},
    iter::FromIterator,
    result,
    time::{Duration, SystemTime, UNIX_EPOCH},
};

/// A time tag in OSC message consists of two 32-bit integers where the first one denotes the number of seconds since 1900-01-01 and the second the fractions of a second.
/// For details on its semantics see http://opensoundcontrol.org/node/3/#timetags
///
/// # Examples
///
/// ```
/// use rosc::OscTime;
/// use std::{convert::TryFrom, time::SystemTime};
///
/// assert_eq!(
///     OscTime::try_from(SystemTime::UNIX_EPOCH).unwrap(),
///     OscTime::from((2_208_988_800, 0))
/// );
/// ```
///
/// # Conversions between `(u32, u32)`
///
/// Prior to version `0.5.0` of this crate, `OscTime` was defined as a type alias to `(u32, u32)`.
/// If you are upgrading from one of these older versions, you can use [`.into()`](Into::into) to
/// convert between `(u32, u32)` and `OscTime` in either direction.
///
/// # Conversions between [`std::time::SystemTime`]
///
/// The traits in `std::convert` are implemented for converting between
/// [`SystemTime`](std::time::SystemTime) and `OscTime` in both directions. An `OscTime` can be
/// converted into a `SystemTime` using [`From`](std::convert::From)/[`Into`](std::convert::Into).
/// A `SystemTime` can be converted into an `OscTime` using
/// [`TryFrom`](std::convert::TryFrom)/[`TryInto`](std::convert::TryInto). The fallible variants of
/// the conversion traits are used this case because not every `SystemTime` can be represented as
/// an `OscTime`.
///
/// **These conversions are lossy**, but are tested to have a deviation within
/// 5 nanoseconds when converted back and forth in either direction.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct OscTime {
    pub seconds: u32,
    pub fractional: u32,
}

impl OscTime {
    const UNIX_OFFSET: u64 = 2_208_988_800; // From RFC 5905
    const TWO_POW_32: f64 = (u32::MAX as f64) + 1.0; // Number of bits in a `u32`
    const ONE_OVER_TWO_POW_32: f64 = 1.0 / OscTime::TWO_POW_32;
    const NANOS_PER_SECOND: f64 = 1.0e9;
    const SECONDS_PER_NANO: f64 = 1.0 / OscTime::NANOS_PER_SECOND;

    fn epoch() -> SystemTime {
        UNIX_EPOCH - Duration::from_secs(OscTime::UNIX_OFFSET)
    }
}

impl TryFrom<SystemTime> for OscTime {
    type Error = OscTimeError;

    fn try_from(time: SystemTime) -> std::result::Result<OscTime, OscTimeError> {
        let duration_since_epoch = time
            .duration_since(OscTime::epoch())
            .map_err(|_| OscTimeError(OscTimeErrorKind::BeforeEpoch))?;
        let seconds = u32::try_from(duration_since_epoch.as_secs())
            .map_err(|_| OscTimeError(OscTimeErrorKind::Overflow))?;
        let nanos = duration_since_epoch.subsec_nanos() as f64;
        let fractional = (nanos * OscTime::SECONDS_PER_NANO * OscTime::TWO_POW_32).round() as u32;
        Ok(OscTime {
            seconds,
            fractional,
        })
    }
}

impl From<OscTime> for SystemTime {
    fn from(time: OscTime) -> SystemTime {
        let nanos =
            (time.fractional as f64) * OscTime::ONE_OVER_TWO_POW_32 * OscTime::NANOS_PER_SECOND;
        let duration_since_osc_epoch = Duration::new(time.seconds as u64, nanos as u32);
        OscTime::epoch() + duration_since_osc_epoch
    }
}

impl From<(u32, u32)> for OscTime {
    fn from(time: (u32, u32)) -> OscTime {
        let (seconds, fractional) = time;
        OscTime {
            seconds,
            fractional,
        }
    }
}

impl From<OscTime> for (u32, u32) {
    fn from(time: OscTime) -> (u32, u32) {
        (time.seconds, time.fractional)
    }
}

/// An error returned by conversions involving [`OscTime`].
#[derive(Debug)]
pub struct OscTimeError(OscTimeErrorKind);

#[derive(Debug)]
enum OscTimeErrorKind {
    BeforeEpoch,
    Overflow,
}

impl Display for OscTimeError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.0 {
            OscTimeErrorKind::BeforeEpoch => {
                write!(f, "time is before the OSC epoch and cannot be stored")
            }
            OscTimeErrorKind::Overflow => {
                write!(f, "time overflows what OSC time can store")
            }
        }
    }
}

impl error::Error for OscTimeError {}

/// see OSC Type Tag String: [OSC Spec. 1.0](http://opensoundcontrol.org/spec-1_0)
/// padding: zero bytes (n*4)
#[derive(Clone, Debug, PartialEq)]
pub enum OscType {
    Int(i32),
    Float(f32),
    String(String),
    Blob(Vec<u8>),
    // use struct for time tag to avoid destructuring
    Time(OscTime),
    Long(i64),
    Double(f64),
    Char(char),
    Color(OscColor),
    Midi(OscMidiMessage),
    Bool(bool),
    Array(OscArray),
    Nil,
    Inf,
}
macro_rules! value_impl {
    ($(($name:ident, $variant:ident, $ty:ty)),*) => {
        $(
        impl OscType {
            #[allow(dead_code)]
            pub fn $name(self) -> Option<$ty> {
                match self {
                    OscType::$variant(v) => Some(v),
                    _ => None
                }
            }
        }
        impl From<$ty> for OscType {
            fn from(v: $ty) -> Self {
                OscType::$variant(v)
            }
        }
        )*
    }
}
value_impl! {
    (int, Int, i32),
    (float, Float, f32),
    (string, String, String),
    (blob, Blob, Vec<u8>),
    (array, Array, OscArray),
    (long, Long, i64),
    (double, Double, f64),
    (char, Char, char),
    (color, Color, OscColor),
    (midi, Midi, OscMidiMessage),
    (bool, Bool, bool)
}
impl From<(u32, u32)> for OscType {
    fn from(time: (u32, u32)) -> Self {
        OscType::Time(time.into())
    }
}
impl TryFrom<SystemTime> for OscType {
    type Error = OscTimeError;

    fn try_from(time: SystemTime) -> std::result::Result<OscType, OscTimeError> {
        time.try_into().map(OscType::Time)
    }
}
impl OscType {
    pub fn time(self) -> Option<OscTime> {
        match self {
            OscType::Time(time) => Some(time),
            _ => None,
        }
    }
}
impl<'a> From<&'a str> for OscType {
    fn from(string: &'a str) -> Self {
        OscType::String(string.to_string())
    }
}
/// Represents the parts of a Midi message. Mainly used for
/// tunneling midi over a network using the OSC protocol.
#[derive(Clone, Debug, PartialEq)]
pub struct OscMidiMessage {
    pub port: u8,
    pub status: u8,
    pub data1: u8, // maybe use an enum for data?
    pub data2: u8,
}

/// An *osc packet* can contain an *osc message* or a bundle of nested messages
/// which is called *osc bundle*.
#[derive(Clone, Debug, PartialEq)]
pub enum OscPacket {
    Message(OscMessage),
    Bundle(OscBundle),
}

/// An OSC message consists of an address and
/// zero or more arguments. The address should
/// specify an element of your Instrument (or whatever
/// you want to control with OSC) and the arguments
/// are used to set properties of the element to the
/// respective values.
#[derive(Clone, Debug, PartialEq)]
pub struct OscMessage {
    pub addr: String,
    pub args: Vec<OscType>,
}

/// An OSC bundle contains zero or more OSC packets
/// and a time tag. The contained packets *should* be
/// applied at the given time tag.
#[derive(Clone, Debug, PartialEq)]
pub struct OscBundle {
    pub timetag: OscTime,
    pub content: Vec<OscPacket>,
}

/// An RGBA color.
#[derive(Clone, Debug, PartialEq)]
pub struct OscColor {
    pub red: u8,
    pub green: u8,
    pub blue: u8,
    pub alpha: u8,
}

/// An OscArray color.
#[derive(Clone, Debug, PartialEq)]
pub struct OscArray {
    pub content: Vec<OscType>,
}

impl<T: Into<OscType>> FromIterator<T> for OscArray {
    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> OscArray {
        OscArray {
            content: iter.into_iter().map(T::into).collect(),
        }
    }
}

pub type Result<T> = result::Result<T, errors::OscError>;

impl From<String> for OscMessage {
    fn from(s: String) -> OscMessage {
        OscMessage {
            addr: s,
            args: vec![],
        }
    }
}
impl<'a> From<&'a str> for OscMessage {
    fn from(s: &str) -> OscMessage {
        OscMessage {
            addr: s.to_string(),
            args: vec![],
        }
    }
}

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

    const TOLERANCE_NANOS: u64 = 5;

    #[test]
    fn system_times_can_be_converted_to_and_from_osc() {
        let times = vec![UNIX_EPOCH, OscTime::epoch(), UNIX_EPOCH];
        for time in times {
            for i in 0..1000 {
                let time = time + Duration::from_nanos(1) * i;
                assert_eq_system_times(time, SystemTime::from(OscTime::try_from(time).unwrap()));
            }
        }
    }

    #[test]
    fn osc_times_can_be_converted_to_and_from_system_times() {
        let mut times = vec![];

        // Sweep across a few numbers to check for tolerance
        for seconds in vec![0, 1, 2, 3, u32::MAX - 1, u32::MAX] {
            let fractional_max = 100;
            for fractional in 0..fractional_max {
                times.push((seconds, fractional));
                times.push((seconds, fractional_max - fractional));
            }
        }

        for osc_time in times.into_iter().map(OscTime::from) {
            assert_eq_osc_times(
                osc_time,
                OscTime::try_from(SystemTime::from(osc_time)).unwrap(),
            );
        }
    }

    #[test]
    fn osc_time_cannot_represent_times_before_1900_01_01() {
        assert!(OscTime::try_from(OscTime::epoch() - Duration::from_secs(1)).is_err())
    }

    fn assert_eq_system_times(a: SystemTime, b: SystemTime) {
        let difference = if a < b {
            b.duration_since(a).unwrap()
        } else {
            a.duration_since(b).unwrap()
        };

        let tolerance = Duration::from_nanos(TOLERANCE_NANOS);

        if difference > tolerance {
            panic!(
                "the fractional seconds components of {:?} and {:?} vary more than the required tolerance of {:?}",
                a, b, tolerance,
            );
        }
    }

    fn assert_eq_osc_times(a: OscTime, b: OscTime) {
        // I did not want to implement subtraction with carrying in order to implement this in the
        // same way as the alternative for system times. Intsead we are compare each part of the
        // OSC times separately.
        let tolerance_fractional_seconds = ((TOLERANCE_NANOS as f64 * OscTime::TWO_POW_32)
            / OscTime::NANOS_PER_SECOND)
            .round() as i64;
        assert_eq!(
            a.seconds, b.seconds,
            "the seconds components of {:?} and {:?} are different",
            a, b
        );
        if (a.fractional as i64 - b.fractional as i64).abs() > tolerance_fractional_seconds {
            panic!(
                "the fractional seconds components of {:?} and {:?} vary more than the required tolerance of {} fractional seconds",
                a, b, tolerance_fractional_seconds,
            );
        }
    }
}
<|endoftext|>


<commit_before>extern crate c_ares_sys;
extern crate libc;

use std::fmt;
use std::net::{
    Ipv4Addr,
    Ipv6Addr,
};

use ctypes;

/// The platform-specific file descriptor / socket type.  That is, either a
/// `RawFd` or a `RawSocket`.
pub type Socket = c_ares_sys::ares_socket_t;

/// An invalid socket / file descriptor.  Use this to represent 'no action'
/// when calling `process_fd()` on a channel.
pub const SOCKET_BAD: Socket = c_ares_sys::ARES_SOCKET_BAD;

/// Address families.
#[derive(Clone, Copy, Debug)]
pub enum AddressFamily {
    /// IPv4.
    INET = ctypes::AF_INET as isize,

    /// IPv6.
    INET6 = ctypes::AF_INET6 as isize,
}

/// An IP address, either an IPv4 or an IPv6 address.
#[derive(Clone, Copy, Debug)]
pub enum IpAddr {
    /// An IPv4 address.
    V4(Ipv4Addr),

    /// An IPv6 address.
    V6(Ipv6Addr),
}

impl fmt::Display for IpAddr {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            IpAddr::V4(ref a) => a.fmt(fmt),
            IpAddr::V6(ref a) => a.fmt(fmt),
        }
    }
}

// See arpa/nameser.h
#[derive(Clone, Copy, Debug)]
pub enum QueryType {
    A = 1,
    NS = 2,
    CNAME = 5,
    SOA = 6,
    PTR = 12,
    MX = 15,
    TXT = 16,
    AAAA = 28,
    SRV = 33,
    NAPTR = 35,
}

// See arpa/nameser.h
#[derive(Clone, Copy, Debug)]
pub enum DnsClass {
   IN = 1,
}
<commit_msg>Remove unused import<commit_after>extern crate c_ares_sys;

use std::fmt;
use std::net::{
    Ipv4Addr,
    Ipv6Addr,
};

use ctypes;

/// The platform-specific file descriptor / socket type.  That is, either a
/// `RawFd` or a `RawSocket`.
pub type Socket = c_ares_sys::ares_socket_t;

/// An invalid socket / file descriptor.  Use this to represent 'no action'
/// when calling `process_fd()` on a channel.
pub const SOCKET_BAD: Socket = c_ares_sys::ARES_SOCKET_BAD;

/// Address families.
#[derive(Clone, Copy, Debug)]
pub enum AddressFamily {
    /// IPv4.
    INET = ctypes::AF_INET as isize,

    /// IPv6.
    INET6 = ctypes::AF_INET6 as isize,
}

/// An IP address, either an IPv4 or an IPv6 address.
#[derive(Clone, Copy, Debug)]
pub enum IpAddr {
    /// An IPv4 address.
    V4(Ipv4Addr),

    /// An IPv6 address.
    V6(Ipv6Addr),
}

impl fmt::Display for IpAddr {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            IpAddr::V4(ref a) => a.fmt(fmt),
            IpAddr::V6(ref a) => a.fmt(fmt),
        }
    }
}

// See arpa/nameser.h
#[derive(Clone, Copy, Debug)]
pub enum QueryType {
    A = 1,
    NS = 2,
    CNAME = 5,
    SOA = 6,
    PTR = 12,
    MX = 15,
    TXT = 16,
    AAAA = 28,
    SRV = 33,
    NAPTR = 35,
}

// See arpa/nameser.h
#[derive(Clone, Copy, Debug)]
pub enum DnsClass {
   IN = 1,
}
<|endoftext|>