<commit_before>// This file is part of the uutils coreutils package.
//
// (c) Jian Zeng <anonymousknight96@gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//
//! Aims to provide platform-independent methods to obtain login records
//!
//! **ONLY** support linux, macos and freebsd for the time being
//!
//! # Examples:
//!
//! ```
//! use uucore::utmpx::Utmpx;
//! for ut in Utmpx::iter_all_records() {
//!     if ut.is_user_process() {
//!         println!("{}: {}", ut.host(), ut.user())
//!     }
//! }
//! ```
//!
//! Specifying the path to login record:
//!
//! ```
//! use uucore::utmpx::Utmpx;
//! for ut in Utmpx::iter_all_records().read_from("/some/where/else") {
//!     if ut.is_user_process() {
//!         println!("{}: {}", ut.host(), ut.user())
//!     }
//! }
//! ```

pub extern crate time;
use self::time::{Timespec, Tm};

use std::ffi::CString;
use std::io::Error as IOError;
use std::io::Result as IOResult;
use std::ptr;

pub use self::ut::*;
use libc::utmpx;
// pub use libc::getutxid;
// pub use libc::getutxline;
// pub use libc::pututxline;
pub use libc::endutxent;
pub use libc::getutxent;
pub use libc::setutxent;
#[cfg(any(target_os = "macos", target_os = "linux"))]
pub use libc::utmpxname;
#[cfg(target_os = "freebsd")]
pub unsafe extern "C" fn utmpxname(_file: *const libc::c_char) -> libc::c_int {
    0
}

// In case the c_char array doesn't end with NULL
macro_rules! chars2string {
    ($arr:expr) => {
        $arr.iter()
            .take_while(|i| **i > 0)
            .map(|&i| i as u8 as char)
            .collect::<String>()
    };
}

#[cfg(target_os = "linux")]
mod ut {
    pub static DEFAULT_FILE: &str = "/var/run/utmp";

    pub use libc::__UT_HOSTSIZE as UT_HOSTSIZE;
    pub use libc::__UT_LINESIZE as UT_LINESIZE;
    pub use libc::__UT_NAMESIZE as UT_NAMESIZE;
    pub const UT_IDSIZE: usize = 4;

    pub use libc::ACCOUNTING;
    pub use libc::BOOT_TIME;
    pub use libc::DEAD_PROCESS;
    pub use libc::EMPTY;
    pub use libc::INIT_PROCESS;
    pub use libc::LOGIN_PROCESS;
    pub use libc::NEW_TIME;
    pub use libc::OLD_TIME;
    pub use libc::RUN_LVL;
    pub use libc::USER_PROCESS;
}

#[cfg(target_os = "macos")]
mod ut {
    pub static DEFAULT_FILE: &str = "/var/run/utmpx";

    pub use libc::_UTX_HOSTSIZE as UT_HOSTSIZE;
    pub use libc::_UTX_IDSIZE as UT_IDSIZE;
    pub use libc::_UTX_LINESIZE as UT_LINESIZE;
    pub use libc::_UTX_USERSIZE as UT_NAMESIZE;

    pub use libc::ACCOUNTING;
    pub use libc::BOOT_TIME;
    pub use libc::DEAD_PROCESS;
    pub use libc::EMPTY;
    pub use libc::INIT_PROCESS;
    pub use libc::LOGIN_PROCESS;
    pub use libc::NEW_TIME;
    pub use libc::OLD_TIME;
    pub use libc::RUN_LVL;
    pub use libc::SHUTDOWN_TIME;
    pub use libc::SIGNATURE;
    pub use libc::USER_PROCESS;
}

#[cfg(target_os = "freebsd")]
mod ut {
    pub static DEFAULT_FILE: &str = "";

    pub const UT_LINESIZE: usize = 16;
    pub const UT_NAMESIZE: usize = 32;
    pub const UT_IDSIZE: usize = 8;
    pub const UT_HOSTSIZE: usize = 128;

    pub use libc::BOOT_TIME;
    pub use libc::DEAD_PROCESS;
    pub use libc::EMPTY;
    pub use libc::INIT_PROCESS;
    pub use libc::LOGIN_PROCESS;
    pub use libc::NEW_TIME;
    pub use libc::OLD_TIME;
    pub use libc::SHUTDOWN_TIME;
    pub use libc::USER_PROCESS;
}

pub struct Utmpx {
    inner: utmpx,
}

impl Utmpx {
    /// A.K.A. ut.ut_type
    pub fn record_type(&self) -> i16 {
        self.inner.ut_type as i16
    }
    /// A.K.A. ut.ut_pid
    pub fn pid(&self) -> i32 {
        self.inner.ut_pid as i32
    }
    /// A.K.A. ut.ut_id
    pub fn terminal_suffix(&self) -> String {
        chars2string!(self.inner.ut_id)
    }
    /// A.K.A. ut.ut_user
    pub fn user(&self) -> String {
        chars2string!(self.inner.ut_user)
    }
    /// A.K.A. ut.ut_host
    pub fn host(&self) -> String {
        chars2string!(self.inner.ut_host)
    }
    /// A.K.A. ut.ut_line
    pub fn tty_device(&self) -> String {
        chars2string!(self.inner.ut_line)
    }
    /// A.K.A. ut.ut_tv
    pub fn login_time(&self) -> Tm {
        time::at(Timespec::new(
            self.inner.ut_tv.tv_sec as i64,
            self.inner.ut_tv.tv_usec as i32,
        ))
    }
    /// A.K.A. ut.ut_exit
    ///
    /// Return (e_termination, e_exit)
    #[cfg(any(target_os = "linux", target_os = "android"))]
    pub fn exit_status(&self) -> (i16, i16) {
        (self.inner.ut_exit.e_termination, self.inner.ut_exit.e_exit)
    }
    /// A.K.A. ut.ut_exit
    ///
    /// Return (0, 0) on Non-Linux platform
    #[cfg(not(any(target_os = "linux", target_os = "android")))]
    pub fn exit_status(&self) -> (i16, i16) {
        (0, 0)
    }
    /// Consumes the `Utmpx`, returning the underlying C struct utmpx
    pub fn into_inner(self) -> utmpx {
        self.inner
    }
    pub fn is_user_process(&self) -> bool {
        !self.user().is_empty() && self.record_type() == USER_PROCESS
    }

    /// Canonicalize host name using DNS
    pub fn canon_host(&self) -> IOResult<String> {
        const AI_CANONNAME: libc::c_int = 0x2;
        let host = self.host();
        let host = host.split(':').nth(0).unwrap();
        let hints = libc::addrinfo {
            ai_flags: AI_CANONNAME,
            ai_family: 0,
            ai_socktype: 0,
            ai_protocol: 0,
            ai_addrlen: 0,
            ai_addr: ptr::null_mut(),
            ai_canonname: ptr::null_mut(),
            ai_next: ptr::null_mut(),
        };
        let c_host = CString::new(host).unwrap();
        let mut res = ptr::null_mut();
        let status = unsafe {
            libc::getaddrinfo(
                c_host.as_ptr(),
                ptr::null(),
                &hints as *const _,
                &mut res as *mut _,
            )
        };
        if status == 0 {
            let info: libc::addrinfo = unsafe { ptr::read(res as *const _) };
            // http://lists.gnu.org/archive/html/bug-coreutils/2006-09/msg00300.html
            // says Darwin 7.9.0 getaddrinfo returns 0 but sets
            // res->ai_canonname to NULL.
            let ret = if info.ai_canonname.is_null() {
                Ok(String::from(host))
            } else {
                Ok(unsafe { CString::from_raw(info.ai_canonname).into_string().unwrap() })
            };
            unsafe {
                libc::freeaddrinfo(res);
            }
            ret
        } else {
            Err(IOError::last_os_error())
        }
    }
    pub fn iter_all_records() -> UtmpxIter {
        UtmpxIter
    }
}

/// Iterator of login records
pub struct UtmpxIter;

impl UtmpxIter {
    /// Sets the name of the utmpx-format file for the other utmpx functions to access.
    ///
    /// If not set, default record file will be used(file path depends on the target OS)
    pub fn read_from(self, f: &str) -> Self {
        let res = unsafe { utmpxname(CString::new(f).unwrap().as_ptr()) };
        if res != 0 {
            println!("Warning: {}", IOError::last_os_error());
        }
        unsafe {
            setutxent();
        }
        self
    }
}

impl Iterator for UtmpxIter {
    type Item = Utmpx;
    fn next(&mut self) -> Option<Self::Item> {
        unsafe {
            let res = getutxent();
            if !res.is_null() {
                Some(Utmpx {
                    inner: ptr::read(res as *const _),
                })
            } else {
                endutxent();
                None
            }
        }
    }
}
<commit_msg>uucore/refactor ~ fix `cargo clippy` complaints (iter_nth_zero)<commit_after>// This file is part of the uutils coreutils package.
//
// (c) Jian Zeng <anonymousknight96@gmail.com>
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
//
//! Aims to provide platform-independent methods to obtain login records
//!
//! **ONLY** support linux, macos and freebsd for the time being
//!
//! # Examples:
//!
//! ```
//! use uucore::utmpx::Utmpx;
//! for ut in Utmpx::iter_all_records() {
//!     if ut.is_user_process() {
//!         println!("{}: {}", ut.host(), ut.user())
//!     }
//! }
//! ```
//!
//! Specifying the path to login record:
//!
//! ```
//! use uucore::utmpx::Utmpx;
//! for ut in Utmpx::iter_all_records().read_from("/some/where/else") {
//!     if ut.is_user_process() {
//!         println!("{}: {}", ut.host(), ut.user())
//!     }
//! }
//! ```

pub extern crate time;
use self::time::{Timespec, Tm};

use std::ffi::CString;
use std::io::Error as IOError;
use std::io::Result as IOResult;
use std::ptr;

pub use self::ut::*;
use libc::utmpx;
// pub use libc::getutxid;
// pub use libc::getutxline;
// pub use libc::pututxline;
pub use libc::endutxent;
pub use libc::getutxent;
pub use libc::setutxent;
#[cfg(any(target_os = "macos", target_os = "linux"))]
pub use libc::utmpxname;
#[cfg(target_os = "freebsd")]
pub unsafe extern "C" fn utmpxname(_file: *const libc::c_char) -> libc::c_int {
    0
}

// In case the c_char array doesn't end with NULL
macro_rules! chars2string {
    ($arr:expr) => {
        $arr.iter()
            .take_while(|i| **i > 0)
            .map(|&i| i as u8 as char)
            .collect::<String>()
    };
}

#[cfg(target_os = "linux")]
mod ut {
    pub static DEFAULT_FILE: &str = "/var/run/utmp";

    pub use libc::__UT_HOSTSIZE as UT_HOSTSIZE;
    pub use libc::__UT_LINESIZE as UT_LINESIZE;
    pub use libc::__UT_NAMESIZE as UT_NAMESIZE;
    pub const UT_IDSIZE: usize = 4;

    pub use libc::ACCOUNTING;
    pub use libc::BOOT_TIME;
    pub use libc::DEAD_PROCESS;
    pub use libc::EMPTY;
    pub use libc::INIT_PROCESS;
    pub use libc::LOGIN_PROCESS;
    pub use libc::NEW_TIME;
    pub use libc::OLD_TIME;
    pub use libc::RUN_LVL;
    pub use libc::USER_PROCESS;
}

#[cfg(target_os = "macos")]
mod ut {
    pub static DEFAULT_FILE: &str = "/var/run/utmpx";

    pub use libc::_UTX_HOSTSIZE as UT_HOSTSIZE;
    pub use libc::_UTX_IDSIZE as UT_IDSIZE;
    pub use libc::_UTX_LINESIZE as UT_LINESIZE;
    pub use libc::_UTX_USERSIZE as UT_NAMESIZE;

    pub use libc::ACCOUNTING;
    pub use libc::BOOT_TIME;
    pub use libc::DEAD_PROCESS;
    pub use libc::EMPTY;
    pub use libc::INIT_PROCESS;
    pub use libc::LOGIN_PROCESS;
    pub use libc::NEW_TIME;
    pub use libc::OLD_TIME;
    pub use libc::RUN_LVL;
    pub use libc::SHUTDOWN_TIME;
    pub use libc::SIGNATURE;
    pub use libc::USER_PROCESS;
}

#[cfg(target_os = "freebsd")]
mod ut {
    pub static DEFAULT_FILE: &str = "";

    pub const UT_LINESIZE: usize = 16;
    pub const UT_NAMESIZE: usize = 32;
    pub const UT_IDSIZE: usize = 8;
    pub const UT_HOSTSIZE: usize = 128;

    pub use libc::BOOT_TIME;
    pub use libc::DEAD_PROCESS;
    pub use libc::EMPTY;
    pub use libc::INIT_PROCESS;
    pub use libc::LOGIN_PROCESS;
    pub use libc::NEW_TIME;
    pub use libc::OLD_TIME;
    pub use libc::SHUTDOWN_TIME;
    pub use libc::USER_PROCESS;
}

pub struct Utmpx {
    inner: utmpx,
}

impl Utmpx {
    /// A.K.A. ut.ut_type
    pub fn record_type(&self) -> i16 {
        self.inner.ut_type as i16
    }
    /// A.K.A. ut.ut_pid
    pub fn pid(&self) -> i32 {
        self.inner.ut_pid as i32
    }
    /// A.K.A. ut.ut_id
    pub fn terminal_suffix(&self) -> String {
        chars2string!(self.inner.ut_id)
    }
    /// A.K.A. ut.ut_user
    pub fn user(&self) -> String {
        chars2string!(self.inner.ut_user)
    }
    /// A.K.A. ut.ut_host
    pub fn host(&self) -> String {
        chars2string!(self.inner.ut_host)
    }
    /// A.K.A. ut.ut_line
    pub fn tty_device(&self) -> String {
        chars2string!(self.inner.ut_line)
    }
    /// A.K.A. ut.ut_tv
    pub fn login_time(&self) -> Tm {
        time::at(Timespec::new(
            self.inner.ut_tv.tv_sec as i64,
            self.inner.ut_tv.tv_usec as i32,
        ))
    }
    /// A.K.A. ut.ut_exit
    ///
    /// Return (e_termination, e_exit)
    #[cfg(any(target_os = "linux", target_os = "android"))]
    pub fn exit_status(&self) -> (i16, i16) {
        (self.inner.ut_exit.e_termination, self.inner.ut_exit.e_exit)
    }
    /// A.K.A. ut.ut_exit
    ///
    /// Return (0, 0) on Non-Linux platform
    #[cfg(not(any(target_os = "linux", target_os = "android")))]
    pub fn exit_status(&self) -> (i16, i16) {
        (0, 0)
    }
    /// Consumes the `Utmpx`, returning the underlying C struct utmpx
    pub fn into_inner(self) -> utmpx {
        self.inner
    }
    pub fn is_user_process(&self) -> bool {
        !self.user().is_empty() && self.record_type() == USER_PROCESS
    }

    /// Canonicalize host name using DNS
    pub fn canon_host(&self) -> IOResult<String> {
        const AI_CANONNAME: libc::c_int = 0x2;
        let host = self.host();
        let host = host.split(':').next().unwrap();
        let hints = libc::addrinfo {
            ai_flags: AI_CANONNAME,
            ai_family: 0,
            ai_socktype: 0,
            ai_protocol: 0,
            ai_addrlen: 0,
            ai_addr: ptr::null_mut(),
            ai_canonname: ptr::null_mut(),
            ai_next: ptr::null_mut(),
        };
        let c_host = CString::new(host).unwrap();
        let mut res = ptr::null_mut();
        let status = unsafe {
            libc::getaddrinfo(
                c_host.as_ptr(),
                ptr::null(),
                &hints as *const _,
                &mut res as *mut _,
            )
        };
        if status == 0 {
            let info: libc::addrinfo = unsafe { ptr::read(res as *const _) };
            // http://lists.gnu.org/archive/html/bug-coreutils/2006-09/msg00300.html
            // says Darwin 7.9.0 getaddrinfo returns 0 but sets
            // res->ai_canonname to NULL.
            let ret = if info.ai_canonname.is_null() {
                Ok(String::from(host))
            } else {
                Ok(unsafe { CString::from_raw(info.ai_canonname).into_string().unwrap() })
            };
            unsafe {
                libc::freeaddrinfo(res);
            }
            ret
        } else {
            Err(IOError::last_os_error())
        }
    }
    pub fn iter_all_records() -> UtmpxIter {
        UtmpxIter
    }
}

/// Iterator of login records
pub struct UtmpxIter;

impl UtmpxIter {
    /// Sets the name of the utmpx-format file for the other utmpx functions to access.
    ///
    /// If not set, default record file will be used(file path depends on the target OS)
    pub fn read_from(self, f: &str) -> Self {
        let res = unsafe { utmpxname(CString::new(f).unwrap().as_ptr()) };
        if res != 0 {
            println!("Warning: {}", IOError::last_os_error());
        }
        unsafe {
            setutxent();
        }
        self
    }
}

impl Iterator for UtmpxIter {
    type Item = Utmpx;
    fn next(&mut self) -> Option<Self::Item> {
        unsafe {
            let res = getutxent();
            if !res.is_null() {
                Some(Utmpx {
                    inner: ptr::read(res as *const _),
                })
            } else {
                endutxent();
                None
            }
        }
    }
}
<|endoftext|>


<commit_before>use crate::hypercall;
use crate::register_state::GeneralPurposeRegisterState;

const HYPERVISOR_VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn parse_version() -> [u32; 3] {
    let mut index = 0;
    let mut version: [u32; 3] = [0, 0, 0];
    for c in HYPERVISOR_VERSION.chars() {
        if index > version.len() {
            break;
        }
        if c == '.' {
            index += 1;
            continue;
        }
        if c.is_ascii_digit() {
            version[index] *= 10;
            version[index] += u32::from((c as u8) - ('0' as u8));
        }
    }

    version
}

pub fn handle_hypercall(gprs: &mut GeneralPurposeRegisterState) -> Result<(), x86::vmx::VmFail> {
    assert_eq!(hypercall::HYPERCALL_MAGIC, gprs.rax as u32);

    let reason = gprs.rbx as u32;
    match reason {
        hypercall::HYPERCALL_REASON_VERSION => {
            let version = parse_version();
            gprs.rax = u64::from(version[0]);
            gprs.rbx = u64::from(version[1]);
            gprs.rcx = u64::from(version[2]);
            gprs.rdx = 0; // Reserved 0
        }
        _ => {
            gprs.rax = 0;
            gprs.rbx = 0;
            gprs.rcx = 0;
            gprs.rdx = 0;
        }
    }
    Ok(())
}
/*
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_parse_version() {
        let version = parse_version();
        assert_eq!(version, [0, 1, 0]);
    }
}
*/
<commit_msg>Refactor version parsing code<commit_after>use crate::hypercall;
use crate::register_state::GeneralPurposeRegisterState;

const HYPERVISOR_VERSION: &str = env!("CARGO_PKG_VERSION");

fn parse_version() -> [u32; 3] {
    let mut index = 0;
    let mut version: [u32; 3] = [0, 0, 0];
    for c in HYPERVISOR_VERSION.chars() {
        if index > version.len() {
            break;
        }
        if c == '.' {
            index += 1;
            continue;
        }

        if let Some(digit) = c.to_digit(10) {
            version[index] *= 10;
            version[index] += digit;
        }
    }

    version
}

pub fn handle_hypercall(gprs: &mut GeneralPurposeRegisterState) -> Result<(), x86::vmx::VmFail> {
    assert_eq!(hypercall::HYPERCALL_MAGIC, gprs.rax as u32);

    let reason = gprs.rbx as u32;
    match reason {
        hypercall::HYPERCALL_REASON_VERSION => {
            let version = parse_version();
            gprs.rax = u64::from(version[0]);
            gprs.rbx = u64::from(version[1]);
            gprs.rcx = u64::from(version[2]);
            gprs.rdx = 0; // Reserved 0
        }
        _ => {
            gprs.rax = 0;
            gprs.rbx = 0;
            gprs.rcx = 0;
            gprs.rdx = 0;
        }
    }
    Ok(())
}
/*
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_parse_version() {
        let version = parse_version();
        assert_eq!(version, [0, 1, 0]);
    }
}
*/
<|endoftext|>


<commit_before>// Copyright (C) 2016 Élisabeth HENRY.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Crowbook is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

//! Misc utility functions used across crowbook

use std;
use std::path::Path;
use std::io::Result;

/// Try to canonicalize a path using std::fs::canonicalize, and returns the
/// unmodified path if it fails (e.g. if the path doesn't exist (yet))
pub fn canonicalize<P: AsRef<Path>>(path: P) -> String {
    try_canonicalize(path.as_ref())
        .unwrap_or(format!("{}", path.as_ref().display()))
}

fn try_canonicalize<P: AsRef<Path>>(path: P) -> Result<String> {
    let path = std::fs::canonicalize(path.as_ref())?;
    let cwd = std::env::current_dir()?;
    Ok(if let Ok(path) = path.strip_prefix(&cwd) {
        format!("{}", path.display())
    } else {
        format!("{}", path.display())
    })
}
<commit_msg>Fix behaviour of canonicalize at cost of unreadability<commit_after>// Copyright (C) 2016 Élisabeth HENRY.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Crowbook is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Crowbook.  If not, see <http://www.gnu.org/licenses/>.

//! Misc utility functions used across crowbook

use std;
use std::path::{Path, PathBuf};
use std::io::Result;

/// Try to canonicalize a path using std::fs::canonicalize, and returns the
/// unmodified path if it fails (e.g. if the path doesn't exist (yet))
pub fn canonicalize<P: AsRef<Path>>(path: P) -> String {
    try_canonicalize(path.as_ref())
        .unwrap_or(format!("{}", path.as_ref().display()))
}

fn try_canonicalize<P: AsRef<Path>>(path: P) -> Result<String> {
    let full_path = std::fs::canonicalize(path.as_ref())?;
    let mut cwd = std::env::current_dir()?;
    let mut ups = 0;

    loop {
        if let Ok(path) = full_path.strip_prefix(&cwd.clone()) {
            let mut new_path = PathBuf::new();
            for _ in 0..ups {
                new_path.push("../");
            }
            new_path.push(path);
            return Ok(format!("{}", new_path.display()));
        } else {
            if !cwd.pop() {
                return Ok(format!("{}", full_path.display()));
            } else {
                ups += 1;
            }
        }
    }
}
<|endoftext|>


<commit_before>//! Operations on manifest files.

use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::io::{self, BufRead};
use std::num::ParseIntError;
use std::path::Path;
use std::str::{self, FromStr, Utf8Error};
use std::usize;

use rawpath::RawPath;

/// Manifest file info.
#[derive(Debug)]
pub struct Manifest {
    hostname: String,
    local_dir: RawPath,
    volumes: Vec<Option<Volume>>,
}

/// Volume info.
#[derive(Debug)]
pub struct Volume {
    start_path: PathBlock,
    end_path: PathBlock,
    hash_type: String,
    hash: Vec<u8>,
}

/// wip
#[derive(Debug)]
pub enum ParseError {
    /// wip
    Io(io::Error),
    /// wip
    MissingKeyword(String),
    /// wip
    MissingHash,
    /// wip
    MissingHashType,
    /// wip
    MissingPath,
    /// wip
    ParseInt(ParseIntError),
    /// wip
    Utf8(Utf8Error),
}

#[derive(Debug)]
struct PathBlock {
    path: RawPath,
    block: Option<usize>,
}

struct ManifestParser<R> {
    input: R,
    buf: Vec<u8>,
}

struct WordIter<'a>(&'a [u8]);

impl Manifest {
    /// Parses a stream to get a manifest.
    pub fn parse<R: BufRead>(m: &mut R) -> Result<Self, ParseError> {
        let parser = ManifestParser::new(m);
        parser.parse()
    }

    /// wip
    pub fn hostname(&self) -> Option<&str> {
        Some(&self.hostname)
    }

    /// wip
    pub fn local_dir(&self) -> Option<&Path> {
        self.local_dir.as_path()
    }

    /// wip
    pub fn max_vol_num(&self) -> usize {
        self.volumes.len()
    }

    /// wip
    pub fn volume(&self, num: usize) -> Option<&Volume> {
        self.volumes.get(num).and_then(|v| v.as_ref())
    }
}

impl Volume {
    /// wip
    pub fn start_path(&self) -> Option<&Path> {
        self.start_path.path.as_path()
    }

    /// wip
    pub fn end_path(&self) -> Option<&Path> {
        self.end_path.path.as_path()
    }

    /// wip
    pub fn start_path_bytes(&self) -> &[u8] {
        self.start_path.path.as_bytes()
    }

    /// wip
    pub fn end_path_bytes(&self) -> &[u8] {
        self.end_path.path.as_bytes()
    }

    /// wip
    pub fn start_block(&self) -> Option<usize> {
        self.start_path.block
    }

    /// wip
    pub fn end_block(&self) -> Option<usize> {
        self.end_path.block
    }

    /// wip
    pub fn hash_type(&self) -> &str {
        &self.hash_type
    }

    /// wip
    pub fn hash(&self) -> &[u8] {
        &self.hash
    }
}

impl Error for ParseError {
    fn description(&self) -> &str {
        match *self {
            ParseError::Io(ref err) => err.description(),
            ParseError::MissingKeyword(_) => "missing keyword in manifest",
            ParseError::MissingHash => "missing required hash",
            ParseError::MissingHashType => "missing required hash type",
            ParseError::MissingPath => "missing required path",
            ParseError::ParseInt(ref err) => err.description(),
            ParseError::Utf8(ref err) => err.description(),
        }
    }
}

impl Display for ParseError {
    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
        match *self {
            ParseError::Io(ref e) => write!(fmt, "{}", e),
            ParseError::MissingKeyword(ref e) => write!(fmt, "missing keyword '{}' in manifest", e),
            ParseError::ParseInt(ref e) => write!(fmt, "{}", e),
            ParseError::Utf8(ref e) => write!(fmt, "{}", e),
            _ => write!(fmt, "{}", self.description()),
        }
    }
}

impl From<io::Error> for ParseError {
    fn from(err: io::Error) -> ParseError {
        ParseError::Io(err)
    }
}

impl From<ParseIntError> for ParseError {
    fn from(err: ParseIntError) -> ParseError {
        ParseError::ParseInt(err)
    }
}

impl From<Utf8Error> for ParseError {
    fn from(err: Utf8Error) -> ParseError {
        ParseError::Utf8(err)
    }
}

macro_rules! check_eof(
    ($e:expr) => (
        if !try!($e) {
            return Err(From::from(io::Error::new(io::ErrorKind::UnexpectedEof,
                                                 "file ends unexpectedly")));
        }
    )
);

impl<R: BufRead> ManifestParser<R> {
    pub fn new(input: R) -> Self {
        ManifestParser {
            input: input,
            buf: vec![],
        }
    }

    pub fn parse(mut self) -> Result<Manifest, ParseError> {
        check_eof!(self.read_line());
        let hostname = try!(self.read_param_str("Hostname"));
        check_eof!(self.read_line());
        let local_dir = RawPath::from_bytes(try!(self.read_param_bytes("Localdir")));

        let mut volumes = Vec::new();
        while let Some((vol, i)) = try!(self.read_volume()) {
            // resize volumes if necessary
            if i >= volumes.len() {
                volumes.reserve(i + 1);
                for _ in volumes.len()..i + 1 {
                    volumes.push(None);
                }
            }
            volumes[i] = Some(vol);
        }

        Ok(Manifest {
            hostname: hostname,
            local_dir: local_dir,
            volumes: volumes,
        })
    }

    fn read_volume(&mut self) -> Result<Option<(Volume, usize)>, ParseError> {
        if !try!(self.read_line()) {
            // EOF
            return Ok(None);
        }

        // volume number
        let mut param = try!(self.read_param_str("Volume"));
        if param.ends_with(":") {
            param.pop();
        }
        let num = try!(usize::from_str(&param));
        check_eof!(self.read_line());
        let start_path = try!(self.read_path_block("StartingPath"));
        check_eof!(self.read_line());
        let end_path = try!(self.read_path_block("EndingPath"));
        check_eof!(self.read_line());
        let (htype, h) = try!(self.read_hash_param());

        let vol = Volume {
            start_path: start_path,
            end_path: end_path,
            hash_type: htype,
            hash: h,
        };
        Ok(Some((vol, num)))
    }

    fn read_line(&mut self) -> io::Result<bool> {
        self.buf.clear();
        let mut len = try!(self.input.read_until(b'\n', &mut self.buf));
        if len > 0 && self.buf[len - 1] == b'\n' {
            len -= 1;
        }
        if len > 0 && self.buf[len - 1] == b'\r' {
            len -= 1;
        }
        self.buf.truncate(len);

        Ok(!self.buf.is_empty())
    }

    fn read_param_bytes(&mut self, key: &str) -> Result<Vec<u8>, ParseError> {
        let mut words = WordIter(&self.buf);
        let kw = match words.next() {
            Some(word) => try!(str::from_utf8(word)),
            None => "",
        };
        if kw != key {
            return Err(ParseError::MissingKeyword(key.to_owned()));
        }
        let param = match words.next() {
            Some(word) => word,
            None => {
                return Ok(vec![]);
            }
        };
        Ok(unescape(param))
    }

    fn read_param_str(&mut self, key: &str) -> Result<String, ParseError> {
        let bytes = try!(self.read_param_bytes(key));
        String::from_utf8(bytes).map_err(|e| From::from(e.utf8_error()))
    }

    fn read_path_block(&mut self, key: &str) -> Result<PathBlock, ParseError> {
        let mut words = WordIter(&self.buf);
        let kw = match words.next() {
            Some(word) => try!(str::from_utf8(word)),
            None => "",
        };
        if kw != key {
            return Err(ParseError::MissingKeyword(key.to_owned()));
        }
        let path = match words.next() {
            Some(word) => RawPath::from_bytes(unescape(word)),
            None => {
                return Err(ParseError::MissingPath);
            }
        };
        let block = match words.next() {
            Some(word) => {
                let s = try!(str::from_utf8(word));
                Some(try!(usize::from_str(s)))
            }
            None => None,
        };

        Ok(PathBlock {
            path: path,
            block: block,
        })
    }

    fn read_hash_param(&mut self) -> Result<(String, Vec<u8>), ParseError> {
        let mut words = WordIter(&self.buf);
        let kw = match words.next() {
            Some(word) => try!(str::from_utf8(word)),
            None => "",
        };
        if kw != "Hash" {
            return Err(ParseError::MissingKeyword("Hash".to_owned()));
        }
        let htype = match words.next() {
            Some(word) => try!(str::from_utf8(word)).to_owned(),
            None => {
                return Err(ParseError::MissingHashType);
            }
        };
        let hash = match words.next() {
            Some(word) => word.iter().cloned().map(|b| b - b'0').collect(),
            None => {
                return Err(ParseError::MissingHash);
            }
        };

        Ok((htype, hash))
    }
}

impl<'a> Iterator for WordIter<'a> {
    type Item = &'a [u8];

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            if self.0.is_empty() {
                return None;
            }
            let pos = self.0.iter().position(|b| *b == b' ').unwrap_or(self.0.len());
            let (w, rest) = {
                let (w, rest) = self.0.split_at(pos);
                // skip all the spaces from rest
                let pos = rest.iter().position(|b| *b != b' ').unwrap_or(rest.len());
                (w, &rest[pos..])
            };
            self.0 = rest;
            if !w.is_empty() {
                return Some(w);
            }
        }
    }
}

fn unescape(mut buf: &[u8]) -> Vec<u8> {
    let mut result = Vec::with_capacity(buf.len());
    buf = match (buf.first().cloned(), buf.last().cloned()) {
        // quoted
        (Some(b'"'), _) if buf.len() > 1 => &buf[1..buf.len() - 1],
        // unquoted or invalid single "
        (Some(_), _) => buf,
        // empty
        _ => {
            return result;
        }
    };

    // unescape
    let mut i = 0;
    let len = buf.len();
    while i < len {
        let b = buf[i];
        if b != b'\\' {
            result.push(b);
        } else {
            // expects a \xNN where NN is a number string representing the escaped char in hex
            // e.g. \x20 is the space ' '
            if buf.len() - i >= 4 && buf[i + 1] == b'x' {
                let num = ((buf[i + 2] - b'0') << 4) + buf[i + 3] - b'0';
                result.push(num);
                i += 3;
            }
        }
        i += 1;
    }

    result
}

#[cfg(test)]
mod test {
    use super::*;
    use std::fs::File;
    use std::io::BufReader;

    #[test]
    fn parse_no_err_full() {
        let file = File::open("tests/manifest/full1.manifest").unwrap();
        let mut bfile = BufReader::new(file);
        Manifest::parse(&mut bfile).unwrap();
    }

    #[test]
    fn parse_no_err_inc() {
        let file = File::open("tests/manifest/inc1.manifest").unwrap();
        let mut bfile = BufReader::new(file);
        Manifest::parse(&mut bfile).unwrap();
    }
}
<commit_msg>Implemented first_volume_of_path<commit_after>//! Operations on manifest files.

use std::cmp::Ordering;
use std::error::Error;
use std::fmt::{self, Display, Formatter};
use std::io::{self, BufRead};
use std::num::ParseIntError;
use std::path::Path;
use std::str::{self, FromStr, Utf8Error};
use std::usize;

use rawpath::RawPath;

/// Manifest file info.
#[derive(Debug)]
pub struct Manifest {
    hostname: String,
    local_dir: RawPath,
    volumes: Vec<Option<Volume>>,
}

/// Volume info.
#[derive(Debug)]
pub struct Volume {
    start_path: PathBlock,
    end_path: PathBlock,
    hash_type: String,
    hash: Vec<u8>,
}

/// wip
#[derive(Debug)]
pub enum ParseError {
    /// wip
    Io(io::Error),
    /// wip
    MissingKeyword(String),
    /// wip
    MissingHash,
    /// wip
    MissingHashType,
    /// wip
    MissingPath,
    /// wip
    ParseInt(ParseIntError),
    /// wip
    Utf8(Utf8Error),
}

#[derive(Debug)]
struct PathBlock {
    path: RawPath,
    block: Option<usize>,
}

struct ManifestParser<R> {
    input: R,
    buf: Vec<u8>,
}

struct WordIter<'a>(&'a [u8]);

impl Manifest {
    /// Parses a stream to get a manifest.
    pub fn parse<R: BufRead>(m: &mut R) -> Result<Self, ParseError> {
        let parser = ManifestParser::new(m);
        parser.parse()
    }

    /// wip
    pub fn hostname(&self) -> Option<&str> {
        Some(&self.hostname)
    }

    /// wip
    pub fn local_dir(&self) -> Option<&Path> {
        self.local_dir.as_path()
    }

    /// wip
    pub fn max_vol_num(&self) -> usize {
        self.volumes.len()
    }

    /// wip
    pub fn volume(&self, num: usize) -> Option<&Volume> {
        self.volumes.get(num).and_then(|v| v.as_ref())
    }

    /// wip
    pub fn first_volume_of_path(&self, path: &[u8]) -> Option<usize> {
        self.volumes
            .binary_search_by(|v| {
                let v: &Volume = match *v {
                    Some(ref v) => v,
                    None => {
                        // assume this was the item '0'
                        // so the given v is less than any other
                        return Ordering::Less;
                    }
                };

                match path.cmp(v.start_path_bytes()) {
                    Ordering::Less => Ordering::Greater,
                    Ordering::Greater => {
                        match path.cmp(v.end_path_bytes()) {
                            Ordering::Less | Ordering::Equal => Ordering::Equal,
                            Ordering::Greater => Ordering::Less,
                        }
                    }
                    Ordering::Equal => {
                        match v.start_path.block {
                            Some(n) if n > 0 => Ordering::Greater,
                            _ => Ordering::Equal,
                        }
                    }
                }
            })
            .ok()
    }
}

impl Volume {
    /// wip
    pub fn start_path(&self) -> Option<&Path> {
        self.start_path.path.as_path()
    }

    /// wip
    pub fn end_path(&self) -> Option<&Path> {
        self.end_path.path.as_path()
    }

    /// wip
    pub fn start_path_bytes(&self) -> &[u8] {
        self.start_path.path.as_bytes()
    }

    /// wip
    pub fn end_path_bytes(&self) -> &[u8] {
        self.end_path.path.as_bytes()
    }

    /// wip
    pub fn start_block(&self) -> Option<usize> {
        self.start_path.block
    }

    /// wip
    pub fn end_block(&self) -> Option<usize> {
        self.end_path.block
    }

    /// wip
    pub fn hash_type(&self) -> &str {
        &self.hash_type
    }

    /// wip
    pub fn hash(&self) -> &[u8] {
        &self.hash
    }
}

impl Error for ParseError {
    fn description(&self) -> &str {
        match *self {
            ParseError::Io(ref err) => err.description(),
            ParseError::MissingKeyword(_) => "missing keyword in manifest",
            ParseError::MissingHash => "missing required hash",
            ParseError::MissingHashType => "missing required hash type",
            ParseError::MissingPath => "missing required path",
            ParseError::ParseInt(ref err) => err.description(),
            ParseError::Utf8(ref err) => err.description(),
        }
    }
}

impl Display for ParseError {
    fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
        match *self {
            ParseError::Io(ref e) => write!(fmt, "{}", e),
            ParseError::MissingKeyword(ref e) => write!(fmt, "missing keyword '{}' in manifest", e),
            ParseError::ParseInt(ref e) => write!(fmt, "{}", e),
            ParseError::Utf8(ref e) => write!(fmt, "{}", e),
            _ => write!(fmt, "{}", self.description()),
        }
    }
}

impl From<io::Error> for ParseError {
    fn from(err: io::Error) -> ParseError {
        ParseError::Io(err)
    }
}

impl From<ParseIntError> for ParseError {
    fn from(err: ParseIntError) -> ParseError {
        ParseError::ParseInt(err)
    }
}

impl From<Utf8Error> for ParseError {
    fn from(err: Utf8Error) -> ParseError {
        ParseError::Utf8(err)
    }
}

macro_rules! check_eof(
    ($e:expr) => (
        if !try!($e) {
            return Err(From::from(io::Error::new(io::ErrorKind::UnexpectedEof,
                                                 "file ends unexpectedly")));
        }
    )
);

impl<R: BufRead> ManifestParser<R> {
    pub fn new(input: R) -> Self {
        ManifestParser {
            input: input,
            buf: vec![],
        }
    }

    pub fn parse(mut self) -> Result<Manifest, ParseError> {
        check_eof!(self.read_line());
        let hostname = try!(self.read_param_str("Hostname"));
        check_eof!(self.read_line());
        let local_dir = RawPath::from_bytes(try!(self.read_param_bytes("Localdir")));

        let mut volumes = Vec::new();
        while let Some((vol, i)) = try!(self.read_volume()) {
            // resize volumes if necessary
            if i >= volumes.len() {
                volumes.reserve(i + 1);
                for _ in volumes.len()..i + 1 {
                    volumes.push(None);
                }
            }
            volumes[i] = Some(vol);
        }

        Ok(Manifest {
            hostname: hostname,
            local_dir: local_dir,
            volumes: volumes,
        })
    }

    fn read_volume(&mut self) -> Result<Option<(Volume, usize)>, ParseError> {
        if !try!(self.read_line()) {
            // EOF
            return Ok(None);
        }

        // volume number
        let mut param = try!(self.read_param_str("Volume"));
        if param.ends_with(":") {
            param.pop();
        }
        let num = try!(usize::from_str(&param));
        check_eof!(self.read_line());
        let start_path = try!(self.read_path_block("StartingPath"));
        check_eof!(self.read_line());
        let end_path = try!(self.read_path_block("EndingPath"));
        check_eof!(self.read_line());
        let (htype, h) = try!(self.read_hash_param());

        let vol = Volume {
            start_path: start_path,
            end_path: end_path,
            hash_type: htype,
            hash: h,
        };
        Ok(Some((vol, num)))
    }

    fn read_line(&mut self) -> io::Result<bool> {
        self.buf.clear();
        let mut len = try!(self.input.read_until(b'\n', &mut self.buf));
        if len > 0 && self.buf[len - 1] == b'\n' {
            len -= 1;
        }
        if len > 0 && self.buf[len - 1] == b'\r' {
            len -= 1;
        }
        self.buf.truncate(len);

        Ok(!self.buf.is_empty())
    }

    fn read_param_bytes(&mut self, key: &str) -> Result<Vec<u8>, ParseError> {
        let mut words = WordIter(&self.buf);
        let kw = match words.next() {
            Some(word) => try!(str::from_utf8(word)),
            None => "",
        };
        if kw != key {
            return Err(ParseError::MissingKeyword(key.to_owned()));
        }
        let param = match words.next() {
            Some(word) => word,
            None => {
                return Ok(vec![]);
            }
        };
        Ok(unescape(param))
    }

    fn read_param_str(&mut self, key: &str) -> Result<String, ParseError> {
        let bytes = try!(self.read_param_bytes(key));
        String::from_utf8(bytes).map_err(|e| From::from(e.utf8_error()))
    }

    fn read_path_block(&mut self, key: &str) -> Result<PathBlock, ParseError> {
        let mut words = WordIter(&self.buf);
        let kw = match words.next() {
            Some(word) => try!(str::from_utf8(word)),
            None => "",
        };
        if kw != key {
            return Err(ParseError::MissingKeyword(key.to_owned()));
        }
        let path = match words.next() {
            Some(word) => RawPath::from_bytes(unescape(word)),
            None => {
                return Err(ParseError::MissingPath);
            }
        };
        let block = match words.next() {
            Some(word) => {
                let s = try!(str::from_utf8(word));
                Some(try!(usize::from_str(s)))
            }
            None => None,
        };

        Ok(PathBlock {
            path: path,
            block: block,
        })
    }

    fn read_hash_param(&mut self) -> Result<(String, Vec<u8>), ParseError> {
        let mut words = WordIter(&self.buf);
        let kw = match words.next() {
            Some(word) => try!(str::from_utf8(word)),
            None => "",
        };
        if kw != "Hash" {
            return Err(ParseError::MissingKeyword("Hash".to_owned()));
        }
        let htype = match words.next() {
            Some(word) => try!(str::from_utf8(word)).to_owned(),
            None => {
                return Err(ParseError::MissingHashType);
            }
        };
        let hash = match words.next() {
            Some(word) => word.iter().cloned().map(|b| b - b'0').collect(),
            None => {
                return Err(ParseError::MissingHash);
            }
        };

        Ok((htype, hash))
    }
}

impl<'a> Iterator for WordIter<'a> {
    type Item = &'a [u8];

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            if self.0.is_empty() {
                return None;
            }
            let pos = self.0.iter().position(|b| *b == b' ').unwrap_or(self.0.len());
            let (w, rest) = {
                let (w, rest) = self.0.split_at(pos);
                // skip all the spaces from rest
                let pos = rest.iter().position(|b| *b != b' ').unwrap_or(rest.len());
                (w, &rest[pos..])
            };
            self.0 = rest;
            if !w.is_empty() {
                return Some(w);
            }
        }
    }
}

fn unescape(mut buf: &[u8]) -> Vec<u8> {
    let mut result = Vec::with_capacity(buf.len());
    buf = match (buf.first().cloned(), buf.last().cloned()) {
        // quoted
        (Some(b'"'), _) if buf.len() > 1 => &buf[1..buf.len() - 1],
        // unquoted or invalid single "
        (Some(_), _) => buf,
        // empty
        _ => {
            return result;
        }
    };

    // unescape
    let mut i = 0;
    let len = buf.len();
    while i < len {
        let b = buf[i];
        if b != b'\\' {
            result.push(b);
        } else {
            // expects a \xNN where NN is a number string representing the escaped char in hex
            // e.g. \x20 is the space ' '
            if buf.len() - i >= 4 && buf[i + 1] == b'x' {
                let num = ((buf[i + 2] - b'0') << 4) + buf[i + 3] - b'0';
                result.push(num);
                i += 3;
            }
        }
        i += 1;
    }

    result
}

#[cfg(test)]
mod test {
    use super::*;
    use std::fs::File;
    use std::io::BufReader;

    fn inc1_manifest() -> Result<Manifest, ParseError> {
        let file = File::open("tests/manifest/inc1.manifest").unwrap();
        let mut bfile = BufReader::new(file);
        Manifest::parse(&mut bfile)
    }

    #[test]
    fn parse_no_err_full() {
        let file = File::open("tests/manifest/full1.manifest").unwrap();
        let mut bfile = BufReader::new(file);
        Manifest::parse(&mut bfile).unwrap();
    }

    #[test]
    fn parse_no_err_inc() {
        inc1_manifest().unwrap();
    }

    #[test]
    fn first_volume_of_path() {
        let manifest = inc1_manifest().unwrap();
        assert_eq!(manifest.first_volume_of_path(b"home/michele/Immagini/Foto/albumfiles.txt")
                           .unwrap(),
                   28);
    }
}
<|endoftext|>


<commit_before>//! Ignoring globs in `.gitignore` files.
//!
//! This uses a cache because the file with the globs in might not be the same
//! directory that we’re listing!

use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::sync::RwLock;

use fs::filter::IgnorePatterns;

/// An **ignore cache** holds sets of glob patterns paired with the
/// directories that they should be ignored underneath. Believe it or not,
/// that’s a valid English sentence.
#[derive(Default, Debug)]
pub struct IgnoreCache {
    entries: RwLock<Vec<(PathBuf, IgnorePatterns)>>
}

impl IgnoreCache {
    pub fn new() -> IgnoreCache {
        IgnoreCache::default()
    }

    pub fn discover_underneath(&self, path: &Path) {
        let mut path = Some(path);
        let mut entries = self.entries.write().unwrap();

        while let Some(p) = path {
            if p.components().next().is_none() { break }

            let ignore_file = p.join(".gitignore");
            if ignore_file.is_file() {
                debug!("Found a .gitignore file: {:?}", ignore_file);
                if let Ok(mut file) = File::open(ignore_file) {
                    let mut contents = String::new();

                    match file.read_to_string(&mut contents) {
                        Ok(_) => {
                            let patterns = file_lines_to_patterns(contents.lines());
                            entries.push((p.into(), patterns));
                        }
                        Err(e) => debug!("Failed to read a .gitignore: {:?}", e)
                    }
                }
            }
            else {
                debug!("Found no .gitignore file at {:?}", ignore_file);
            }

            path = p.parent();
        }
    }

    pub fn is_ignored(&self, suspect: &Path) -> bool {
        let entries = self.entries.read().unwrap();
        entries.iter().any(|&(ref base_path, ref patterns)| {
            if let Ok(suffix) = suspect.strip_prefix(&base_path) {
                patterns.is_ignored_path(suffix)
            }
            else {
                false
            }
        })
    }
}

fn file_lines_to_patterns<'a, I>(iter: I) -> IgnorePatterns
where I: Iterator<Item=&'a str>
{
    let iter = iter.filter(|el| !el.is_empty());
    let iter = iter.filter(|el| !el.starts_with("#"));

    // TODO: Figure out if this should trim whitespace or not

    // Errors are currently being ignored... not a good look
    IgnorePatterns::parse_from_iter(iter).0
}

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

    #[test]
    fn parse_nothing() {
        use std::iter::empty;
        let (patterns, _) = IgnorePatterns::parse_from_iter(empty());
        assert_eq!(patterns, file_lines_to_patterns(empty()));
    }

    #[test]
    fn parse_some_globs() {
        let stuff = vec![ "*.mp3", "README.md" ];
        let reals = vec![ "*.mp3", "README.md" ];
        let (patterns, _) = IgnorePatterns::parse_from_iter(reals.into_iter());
        assert_eq!(patterns, file_lines_to_patterns(stuff.into_iter()));
    }

    #[test]
    fn parse_some_comments() {
        let stuff = vec![ "*.mp3", "# I am a comment!", "#", "README.md" ];
        let reals = vec![ "*.mp3",                           "README.md" ];
        let (patterns, _) = IgnorePatterns::parse_from_iter(reals.into_iter());
        assert_eq!(patterns, file_lines_to_patterns(stuff.into_iter()));
    }

    #[test]
    fn parse_some_blank_lines() {
        let stuff = vec![ "*.mp3", "", "", "README.md" ];
        let reals = vec![ "*.mp3",         "README.md" ];
        let (patterns, _) = IgnorePatterns::parse_from_iter(reals.into_iter());
        assert_eq!(patterns, file_lines_to_patterns(stuff.into_iter()));
    }

    #[test]
    fn parse_some_whitespacey_lines() {
        let stuff = vec![ " *.mp3", "  ", "  a  ", "README.md   " ];
        let reals = vec![ " *.mp3", "  ", "  a  ", "README.md   " ];
        let (patterns, _) = IgnorePatterns::parse_from_iter(reals.into_iter());
        assert_eq!(patterns, file_lines_to_patterns(stuff.into_iter()));
    }

    fn test_cache(dir: &'static str, pats: Vec<&str>) -> IgnoreCache {
        IgnoreCache { entries: RwLock::new(vec![ (dir.into(), IgnorePatterns::parse_from_iter(pats.into_iter()).0) ]) }
    }

    #[test]
    fn an_empty_cache_ignores_nothing() {
        let ignores = IgnoreCache::default();
        assert_eq!(false, ignores.is_ignored(Path::new("/usr/bin/drinking")));
        assert_eq!(false, ignores.is_ignored(Path::new("target/debug/exa")));
    }

    #[test]
    fn a_nonempty_cache_ignores_some_things() {
        let ignores = test_cache("/vagrant", vec![ "target" ]);
        assert_eq!(false, ignores.is_ignored(Path::new("/vagrant/src")));
        assert_eq!(true,  ignores.is_ignored(Path::new("/vagrant/target")));
    }

    #[test]
    fn ignore_some_globs() {
        let ignores = test_cache("/vagrant", vec![ "*.ipr", "*.iws", ".docker" ]);
        assert_eq!(true,  ignores.is_ignored(Path::new("/vagrant/exa.ipr")));
        assert_eq!(true,  ignores.is_ignored(Path::new("/vagrant/exa.iws")));
        assert_eq!(false, ignores.is_ignored(Path::new("/vagrant/exa.iwiwal")));
        assert_eq!(true,  ignores.is_ignored(Path::new("/vagrant/.docker")));
        assert_eq!(false, ignores.is_ignored(Path::new("/vagrant/exa.docker")));

        assert_eq!(false, ignores.is_ignored(Path::new("/srcode/exa.ipr")));
        assert_eq!(false, ignores.is_ignored(Path::new("/srcode/exa.iws")));
    }
}
<commit_msg>Add some ignored tests for sub-globbing<commit_after>//! Ignoring globs in `.gitignore` files.
//!
//! This uses a cache because the file with the globs in might not be the same
//! directory that we’re listing!

use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::sync::RwLock;

use fs::filter::IgnorePatterns;

/// An **ignore cache** holds sets of glob patterns paired with the
/// directories that they should be ignored underneath. Believe it or not,
/// that’s a valid English sentence.
#[derive(Default, Debug)]
pub struct IgnoreCache {
    entries: RwLock<Vec<(PathBuf, IgnorePatterns)>>
}

impl IgnoreCache {
    pub fn new() -> IgnoreCache {
        IgnoreCache::default()
    }

    pub fn discover_underneath(&self, path: &Path) {
        let mut path = Some(path);
        let mut entries = self.entries.write().unwrap();

        while let Some(p) = path {
            if p.components().next().is_none() { break }

            let ignore_file = p.join(".gitignore");
            if ignore_file.is_file() {
                debug!("Found a .gitignore file: {:?}", ignore_file);
                if let Ok(mut file) = File::open(ignore_file) {
                    let mut contents = String::new();

                    match file.read_to_string(&mut contents) {
                        Ok(_) => {
                            let patterns = file_lines_to_patterns(contents.lines());
                            entries.push((p.into(), patterns));
                        }
                        Err(e) => debug!("Failed to read a .gitignore: {:?}", e)
                    }
                }
            }
            else {
                debug!("Found no .gitignore file at {:?}", ignore_file);
            }

            path = p.parent();
        }
    }

    pub fn is_ignored(&self, suspect: &Path) -> bool {
        let entries = self.entries.read().unwrap();
        entries.iter().any(|&(ref base_path, ref patterns)| {
            if let Ok(suffix) = suspect.strip_prefix(&base_path) {
                patterns.is_ignored_path(suffix)
            }
            else {
                false
            }
        })
    }
}

fn file_lines_to_patterns<'a, I>(iter: I) -> IgnorePatterns
where I: Iterator<Item=&'a str>
{
    let iter = iter.filter(|el| !el.is_empty());
    let iter = iter.filter(|el| !el.starts_with("#"));

    // TODO: Figure out if this should trim whitespace or not

    // Errors are currently being ignored... not a good look
    IgnorePatterns::parse_from_iter(iter).0
}

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

    #[test]
    fn parse_nothing() {
        use std::iter::empty;
        let (patterns, _) = IgnorePatterns::parse_from_iter(empty());
        assert_eq!(patterns, file_lines_to_patterns(empty()));
    }

    #[test]
    fn parse_some_globs() {
        let stuff = vec![ "*.mp3", "README.md" ];
        let reals = vec![ "*.mp3", "README.md" ];
        let (patterns, _) = IgnorePatterns::parse_from_iter(reals.into_iter());
        assert_eq!(patterns, file_lines_to_patterns(stuff.into_iter()));
    }

    #[test]
    fn parse_some_comments() {
        let stuff = vec![ "*.mp3", "# I am a comment!", "#", "README.md" ];
        let reals = vec![ "*.mp3",                           "README.md" ];
        let (patterns, _) = IgnorePatterns::parse_from_iter(reals.into_iter());
        assert_eq!(patterns, file_lines_to_patterns(stuff.into_iter()));
    }

    #[test]
    fn parse_some_blank_lines() {
        let stuff = vec![ "*.mp3", "", "", "README.md" ];
        let reals = vec![ "*.mp3",         "README.md" ];
        let (patterns, _) = IgnorePatterns::parse_from_iter(reals.into_iter());
        assert_eq!(patterns, file_lines_to_patterns(stuff.into_iter()));
    }

    #[test]
    fn parse_some_whitespacey_lines() {
        let stuff = vec![ " *.mp3", "  ", "  a  ", "README.md   " ];
        let reals = vec![ " *.mp3", "  ", "  a  ", "README.md   " ];
        let (patterns, _) = IgnorePatterns::parse_from_iter(reals.into_iter());
        assert_eq!(patterns, file_lines_to_patterns(stuff.into_iter()));
    }

    fn test_cache(dir: &'static str, pats: Vec<&str>) -> IgnoreCache {
        IgnoreCache { entries: RwLock::new(vec![ (dir.into(), IgnorePatterns::parse_from_iter(pats.into_iter()).0) ]) }
    }

    #[test]
    fn an_empty_cache_ignores_nothing() {
        let ignores = IgnoreCache::default();
        assert_eq!(false, ignores.is_ignored(Path::new("/usr/bin/drinking")));
        assert_eq!(false, ignores.is_ignored(Path::new("target/debug/exa")));
    }

    #[test]
    fn a_nonempty_cache_ignores_some_things() {
        let ignores = test_cache("/vagrant", vec![ "target" ]);
        assert_eq!(false, ignores.is_ignored(Path::new("/vagrant/src")));
        assert_eq!(true,  ignores.is_ignored(Path::new("/vagrant/target")));
    }

    #[test]
    fn ignore_some_globs() {
        let ignores = test_cache("/vagrant", vec![ "*.ipr", "*.iws", ".docker" ]);
        assert_eq!(true,  ignores.is_ignored(Path::new("/vagrant/exa.ipr")));
        assert_eq!(true,  ignores.is_ignored(Path::new("/vagrant/exa.iws")));
        assert_eq!(false, ignores.is_ignored(Path::new("/vagrant/exa.iwiwal")));
        assert_eq!(true,  ignores.is_ignored(Path::new("/vagrant/.docker")));
        assert_eq!(false, ignores.is_ignored(Path::new("/vagrant/exa.docker")));

        assert_eq!(false, ignores.is_ignored(Path::new("/srcode/exa.ipr")));
        assert_eq!(false, ignores.is_ignored(Path::new("/srcode/exa.iws")));
    }

    #[test] #[ignore]
    fn ignore_relatively() {
        let ignores = test_cache(".", vec![ "target" ]);
        assert_eq!(true,  ignores.is_ignored(Path::new("./target")));
        assert_eq!(true,  ignores.is_ignored(Path::new("./project/target")));
        assert_eq!(true,  ignores.is_ignored(Path::new("./project/project/target")));
        assert_eq!(true,  ignores.is_ignored(Path::new("./project/project/project/target")));

        assert_eq!(false, ignores.is_ignored(Path::new("./.target")));
    }

    #[test] #[ignore]
    fn ignore_relatively_sometimes() {
        let ignores = test_cache(".", vec![ "project/target" ]);
        assert_eq!(false, ignores.is_ignored(Path::new("./target")));
        assert_eq!(true,  ignores.is_ignored(Path::new("./project/target")));
        assert_eq!(true,  ignores.is_ignored(Path::new("./project/project/target")));
        assert_eq!(true,  ignores.is_ignored(Path::new("./project/project/project/target")));
    }

    #[test] #[ignore]
    fn ignore_relatively_absolutely() {
        let ignores = test_cache(".", vec![ "/project/target" ]);
        assert_eq!(false, ignores.is_ignored(Path::new("./target")));
        assert_eq!(true,  ignores.is_ignored(Path::new("./project/target")));
        assert_eq!(true,  ignores.is_ignored(Path::new("./project/project/target")));
        assert_eq!(true,  ignores.is_ignored(Path::new("./project/project/project/target")));
    }

    #[test] #[ignore]   // not 100% sure if dot works this way...
    fn ignore_relatively_absolutely_dot() {
        let ignores = test_cache(".", vec![ "./project/target" ]);
        assert_eq!(false, ignores.is_ignored(Path::new("./target")));
        assert_eq!(true,  ignores.is_ignored(Path::new("./project/target")));
        assert_eq!(true,  ignores.is_ignored(Path::new("./project/project/target")));
        assert_eq!(true,  ignores.is_ignored(Path::new("./project/project/project/target")));
    }
}
<|endoftext|>


<commit_before>extern crate byteorder;
extern crate num;

use pg_crate::types::*;
use pg_crate::error::Error;

use std::io::Cursor;
use std::fmt;
use std::result::*;
use std::error;

use self::byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};

use self::num::{BigUint, One, Zero, ToPrimitive};
use self::num::bigint::ToBigUint;

#[cfg(test)]
use pg_crate::{Connection, TlsMode};
#[cfg(test)]
use std::str::FromStr;

use super::Decimal;

#[derive(Debug, Clone, Copy)]
pub struct InvalidDecimal;

impl fmt::Display for InvalidDecimal {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.write_str(error::Error::description(self))
    }
}

impl error::Error for InvalidDecimal {
    fn description(&self) -> &str {
        "Invalid Decimal"
    }
}

impl FromSql for Decimal {

    // Decimals are represented as follows:
    // Header:
    //  u16 numGroups
    //  i16 weightFirstGroup (10000^weight)
    //  u16 sign (0x0000 = positive, 0x4000 = negative, 0xC000 = NaN)
    //  i16 dscale. Number of digits (in base 10) to print after decimal separator
    //
    //  Psuedo code :
    //  const Decimals [
    //          0.0000000000000000000000000001,
    //          0.000000000000000000000001,
    //          0.00000000000000000001,
    //          0.0000000000000001,
    //          0.000000000001,
    //          0.00000001,
    //          0.0001,
    //          1,
    //          10000,
    //          100000000,
    //          1000000000000,
    //          10000000000000000,
    //          100000000000000000000,
    //          1000000000000000000000000,
    //          10000000000000000000000000000
    //  ]
    //  overflow = false
    //  result = 0
    //  for i = 0, weight = weightFirstGroup + 7; i < numGroups; i++, weight--
    //    group = read.u16
    //    if weight < 0 or weight > MaxNum
    //       overflow = true
    //    else
    //       result += Decimals[weight] * group
    //  sign == 0x4000 ? -result : result

    // So if we were to take the number: 3950.123456
    //
    //  Stored on Disk:
    //    00 03 00 00 00 00 00 06 0F 6E 04 D2 15 E0
    //
    //  Number of groups: 00 03
    //  Weight of first group: 00 00
    //  Sign: 00 00
    //  DScale: 00 06
    //
    // 0F 6E = 3950
    //   result = result + 3950 * 1;
    // 04 D2 = 1234
    //   result = result + 1234 * 0.0001;
    // 15 E0 = 5600
    //   result = result + 5600 * 0.00000001;
    //

    fn from_sql(_: &Type, raw: &[u8])
        -> Result<Decimal, Box<error::Error + 'static + Sync + Send>> {
        let mut raw = Cursor::new(raw);
        let num_groups = try!(raw.read_u16::<BigEndian>());
        let weight = try!(raw.read_i16::<BigEndian>()); // 10000^weight
        // Sign: 0x0000 = positive, 0x4000 = negative, 0xC000 = NaN
        let sign = try!(raw.read_u16::<BigEndian>());
        // Number of digits (in base 10) to print after decimal separator
        let fixed_scale = try!(raw.read_u16::<BigEndian>()) as i32;

        // Build up a list of powers that will be used
        let mut powers = Vec::new();
        let mult = 10000.to_biguint().unwrap();
        let mut val: BigUint = One::one();
        powers.push(BigUint::one());
        for _ in 1..num_groups {
            val = &val * &mult;
            powers.push(val.clone());
        }
        powers.reverse();

        // Now process the number
        let mut result: BigUint = Zero::zero();
        for i in 0..num_groups {
            let group = try!(raw.read_u16::<BigEndian>());
            let calculated = &powers[i as usize] * group.to_biguint().unwrap();
            result = result + calculated;
        }

        // Finally, adjust for the scale
        let mut scale = (num_groups as i16 - weight - 1) as i32 * 4;
        // Scale could be negative
        if scale < 0 {
            result = result * 10i64.pow((scale * -1) as u32).to_biguint().unwrap();
            scale = 0;
        } else if scale > fixed_scale {
            result = result / 10i64.pow((scale - fixed_scale) as u32).to_biguint().unwrap();
            scale = fixed_scale;
        }

        // Create the decimal
        let neg = sign == 0x4000;
        let mut decimal = try!(match Decimal::from_biguint(result, scale as u32, neg) {
            Ok(x) => Ok(x),
            Err(_) => Err(Box::new(Error::Conversion(Box::new(InvalidDecimal)))),
        });

        // Normalize by truncating any trailing 0's from the decimal representation
        // This may not be the most efficient way of doing this
        if scale > 0 {
            let str_rep = decimal.to_string();
            let trailing_zeros = str_rep.chars().rev().take_while(|&x| x == '0').count();
            decimal = decimal.rescale(scale as u32 - trailing_zeros as u32);
        }

        Ok(decimal)
    }

    fn accepts(ty: &Type) -> bool {
        match *ty {
            Type::Numeric => true,
            _ => false,
        }
    }
}

impl ToSql for Decimal {
    fn to_sql(&self,
              _: &Type,
              out: &mut Vec<u8>)
              -> Result<IsNull, Box<error::Error + 'static + Sync + Send>> {
        let uint = self.to_biguint();
        let sign = if self.is_negative() { 0x4000 } else { 0x0000 };
        let scale = self.scale() as u16;
        let mut digits = uint.to_str_radix(10);
        let split_point = if scale as usize > digits.len() {
            let mut new_digits = vec!['0'; scale as usize - digits.len() as usize];
            new_digits.extend(digits.chars());
            digits = new_digits.into_iter().collect::<String>();
            0
        } else {
            digits.len() as isize - scale as isize
        };
        let (whole_digits, decimal_digits) = digits.split_at(split_point as usize);
        let whole_portion = whole_digits.chars().rev().collect::<Vec<char>>().chunks(4)
            .map(|x| {
                let mut x = x.to_owned();
                while x.len() < 4 { x.push('0'); }
                x.into_iter().rev().collect::<String>()
            })
            .rev().collect::<Vec<String>>();
        let decimal_portion = decimal_digits.chars().collect::<Vec<char>>().chunks(4)
            .map(|x| {
                let mut x = x.to_owned();
                while x.len() < 4 { x.push('0'); }
                x.into_iter().collect::<String>()
            })
            .collect::<Vec<String>>();
        let weight = if whole_portion.is_empty() {
            -(decimal_portion.len() as i16)
        } else {
            whole_portion.len() as i16 - 1
        };
        let all_groups = whole_portion.into_iter().chain(decimal_portion.into_iter())
            .skip_while(|ref x| *x == "0000").collect::<Vec<String>>();
        let num_groups = all_groups.len() as u16;

        // Number of groups
        try!(out.write_u16::<BigEndian>(num_groups));
        // Weight of first group
        try!(out.write_i16::<BigEndian>(weight));
        // Sign
        try!(out.write_u16::<BigEndian>(sign));
        // DScale
        try!(out.write_u16::<BigEndian>(scale));
        // Now process the number
        for chunk in all_groups {
            let calculated = chunk.parse::<u16>().unwrap();
            try!(out.write_u16::<BigEndian>(calculated.to_u16().unwrap()));
        }
        Ok(IsNull::No)
    }

    fn accepts(ty: &Type) -> bool {
        match *ty {
            Type::Numeric => true,
            _ => false,
        }
    }

    to_sql_checked!();
}

#[cfg(test)]
fn test_read_type(sql_type: &str, checks: &[&'static str]) {
    let conn = match Connection::connect("postgres://paulmason@localhost", TlsMode::None) {
        Ok(x) => x,
        Err(err) => panic!("{:#?}", err),
    };
    for &val in checks.iter() {
        let stmt = match conn.prepare(&*format!("SELECT {}::{}", val, sql_type)) {
            Ok(x) => x,
            Err(err) => panic!("{:#?}", err),
        };
        let result: Decimal = match stmt.query(&[]) {
            Ok(x) => x.iter().next().unwrap().get(0),
            Err(err) => panic!("{:#?}", err),
        };
        assert_eq!(val, result.to_string());
    }
}

#[cfg(test)]
fn test_write_type(sql_type: &str, checks: &[&'static str]) {
    let conn = match Connection::connect("postgres://paulmason@localhost", TlsMode::None) {
        Ok(x) => x,
        Err(err) => panic!("{:#?}", err),
    };
    for &val in checks.iter() {
        let stmt = match conn.prepare(&*format!("SELECT $1::{}", sql_type)) {
            Ok(x) => x,
            Err(err) => panic!("{:#?}", err)
        };
        let number = Decimal::from_str(val).unwrap();
        let result: Decimal = match stmt.query(&[&number]) {
            Ok(x) => x.iter().next().unwrap().get(0),
            Err(err) => panic!("{:#?}", err)
        };
        assert_eq!(val, result.to_string());
    }
}

#[test]
fn test_null() {
    let conn = match Connection::connect("postgres://paulmason@localhost", TlsMode::None) {
        Ok(x) => x,
        Err(err) => panic!("{:#?}", err),
    };

    // Test NULL
    let stmt = match conn.prepare(&"SELECT NULL::numeric") {
        Ok(x) => x,
        Err(err) => panic!("{:#?}", err),
    };
    let result: Option<Decimal> = match stmt.query(&[]) {
        Ok(x) => x.iter().next().unwrap().get(0),
        Err(err) => panic!("{:#?}", err),
    };
    assert_eq!(None, result);
}

#[test]
fn it_can_read_numeric_type() {
    test_read_type("NUMERIC(26,6)",
              &["3950.123456", "3950", "0.1", "0.01", "0.001", "0.0001", "0.00001", "0.000001",
                  "1", "-100", "-123.456", "119996.25", "1000000", "9999999.99999"]);
}

#[test]
fn it_can_write_numeric_type() {
    test_write_type("NUMERIC(26,6)",
              &["3950.123456", "3950", "0.1", "0.01", "0.001", "0.0001", "0.00001", "0.000001",
                  "1", "-100", "-123.456", "119996.25", "1000000", "9999999.99999"]);
}
<commit_msg>Reorganize tests from postgres.rs<commit_after>extern crate byteorder;
extern crate num;

use pg_crate::types::*;
use pg_crate::error::Error;

use std::io::Cursor;
use std::fmt;
use std::result::*;
use std::error;

use self::byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};

use self::num::{BigUint, One, Zero, ToPrimitive};
use self::num::bigint::ToBigUint;

use super::Decimal;

#[derive(Debug, Clone, Copy)]
pub struct InvalidDecimal;

impl fmt::Display for InvalidDecimal {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        fmt.write_str(error::Error::description(self))
    }
}

impl error::Error for InvalidDecimal {
    fn description(&self) -> &str {
        "Invalid Decimal"
    }
}

impl FromSql for Decimal {

    // Decimals are represented as follows:
    // Header:
    //  u16 numGroups
    //  i16 weightFirstGroup (10000^weight)
    //  u16 sign (0x0000 = positive, 0x4000 = negative, 0xC000 = NaN)
    //  i16 dscale. Number of digits (in base 10) to print after decimal separator
    //
    //  Psuedo code :
    //  const Decimals [
    //          0.0000000000000000000000000001,
    //          0.000000000000000000000001,
    //          0.00000000000000000001,
    //          0.0000000000000001,
    //          0.000000000001,
    //          0.00000001,
    //          0.0001,
    //          1,
    //          10000,
    //          100000000,
    //          1000000000000,
    //          10000000000000000,
    //          100000000000000000000,
    //          1000000000000000000000000,
    //          10000000000000000000000000000
    //  ]
    //  overflow = false
    //  result = 0
    //  for i = 0, weight = weightFirstGroup + 7; i < numGroups; i++, weight--
    //    group = read.u16
    //    if weight < 0 or weight > MaxNum
    //       overflow = true
    //    else
    //       result += Decimals[weight] * group
    //  sign == 0x4000 ? -result : result

    // So if we were to take the number: 3950.123456
    //
    //  Stored on Disk:
    //    00 03 00 00 00 00 00 06 0F 6E 04 D2 15 E0
    //
    //  Number of groups: 00 03
    //  Weight of first group: 00 00
    //  Sign: 00 00
    //  DScale: 00 06
    //
    // 0F 6E = 3950
    //   result = result + 3950 * 1;
    // 04 D2 = 1234
    //   result = result + 1234 * 0.0001;
    // 15 E0 = 5600
    //   result = result + 5600 * 0.00000001;
    //

    fn from_sql(_: &Type, raw: &[u8])
        -> Result<Decimal, Box<error::Error + 'static + Sync + Send>> {
        let mut raw = Cursor::new(raw);
        let num_groups = try!(raw.read_u16::<BigEndian>());
        let weight = try!(raw.read_i16::<BigEndian>()); // 10000^weight
        // Sign: 0x0000 = positive, 0x4000 = negative, 0xC000 = NaN
        let sign = try!(raw.read_u16::<BigEndian>());
        // Number of digits (in base 10) to print after decimal separator
        let fixed_scale = try!(raw.read_u16::<BigEndian>()) as i32;

        // Build up a list of powers that will be used
        let mut powers = Vec::new();
        let mult = 10000.to_biguint().unwrap();
        let mut val: BigUint = One::one();
        powers.push(BigUint::one());
        for _ in 1..num_groups {
            val = &val * &mult;
            powers.push(val.clone());
        }
        powers.reverse();

        // Now process the number
        let mut result: BigUint = Zero::zero();
        for i in 0..num_groups {
            let group = try!(raw.read_u16::<BigEndian>());
            let calculated = &powers[i as usize] * group.to_biguint().unwrap();
            result = result + calculated;
        }

        // Finally, adjust for the scale
        let mut scale = (num_groups as i16 - weight - 1) as i32 * 4;
        // Scale could be negative
        if scale < 0 {
            result = result * 10i64.pow((scale * -1) as u32).to_biguint().unwrap();
            scale = 0;
        } else if scale > fixed_scale {
            result = result / 10i64.pow((scale - fixed_scale) as u32).to_biguint().unwrap();
            scale = fixed_scale;
        }

        // Create the decimal
        let neg = sign == 0x4000;
        let mut decimal = try!(match Decimal::from_biguint(result, scale as u32, neg) {
            Ok(x) => Ok(x),
            Err(_) => Err(Box::new(Error::Conversion(Box::new(InvalidDecimal)))),
        });

        // Normalize by truncating any trailing 0's from the decimal representation
        // This may not be the most efficient way of doing this
        if scale > 0 {
            let str_rep = decimal.to_string();
            let trailing_zeros = str_rep.chars().rev().take_while(|&x| x == '0').count();
            decimal = decimal.rescale(scale as u32 - trailing_zeros as u32);
        }

        Ok(decimal)
    }

    fn accepts(ty: &Type) -> bool {
        match *ty {
            Type::Numeric => true,
            _ => false,
        }
    }
}

impl ToSql for Decimal {
    fn to_sql(&self,
              _: &Type,
              out: &mut Vec<u8>)
              -> Result<IsNull, Box<error::Error + 'static + Sync + Send>> {
        let uint = self.to_biguint();
        let sign = if self.is_negative() { 0x4000 } else { 0x0000 };
        let scale = self.scale() as u16;
        let mut digits = uint.to_str_radix(10);
        let split_point = if scale as usize > digits.len() {
            let mut new_digits = vec!['0'; scale as usize - digits.len() as usize];
            new_digits.extend(digits.chars());
            digits = new_digits.into_iter().collect::<String>();
            0
        } else {
            digits.len() as isize - scale as isize
        };
        let (whole_digits, decimal_digits) = digits.split_at(split_point as usize);
        let whole_portion = whole_digits.chars().rev().collect::<Vec<char>>().chunks(4)
            .map(|x| {
                let mut x = x.to_owned();
                while x.len() < 4 { x.push('0'); }
                x.into_iter().rev().collect::<String>()
            })
            .rev().collect::<Vec<String>>();
        let decimal_portion = decimal_digits.chars().collect::<Vec<char>>().chunks(4)
            .map(|x| {
                let mut x = x.to_owned();
                while x.len() < 4 { x.push('0'); }
                x.into_iter().collect::<String>()
            })
            .collect::<Vec<String>>();
        let weight = if whole_portion.is_empty() {
            -(decimal_portion.len() as i16)
        } else {
            whole_portion.len() as i16 - 1
        };
        let all_groups = whole_portion.into_iter().chain(decimal_portion.into_iter())
            .skip_while(|ref x| *x == "0000").collect::<Vec<String>>();
        let num_groups = all_groups.len() as u16;

        // Number of groups
        try!(out.write_u16::<BigEndian>(num_groups));
        // Weight of first group
        try!(out.write_i16::<BigEndian>(weight));
        // Sign
        try!(out.write_u16::<BigEndian>(sign));
        // DScale
        try!(out.write_u16::<BigEndian>(scale));
        // Now process the number
        for chunk in all_groups {
            let calculated = chunk.parse::<u16>().unwrap();
            try!(out.write_u16::<BigEndian>(calculated.to_u16().unwrap()));
        }
        Ok(IsNull::No)
    }

    fn accepts(ty: &Type) -> bool {
        match *ty {
            Type::Numeric => true,
            _ => false,
        }
    }

    to_sql_checked!();
}

#[cfg(test)]
mod test {
    use pg_crate::{Connection, TlsMode};
    use std::str::FromStr;
    use super::*;

    fn read_type(sql_type: &str, checks: &[&'static str]) {
        let conn = match Connection::connect("postgres://paulmason@localhost", TlsMode::None) {
            Ok(x) => x,
            Err(err) => panic!("{:#?}", err),
        };
        for &val in checks.iter() {
            let stmt = match conn.prepare(&*format!("SELECT {}::{}", val, sql_type)) {
                Ok(x) => x,
                Err(err) => panic!("{:#?}", err),
            };
            let result: Decimal = match stmt.query(&[]) {
                Ok(x) => x.iter().next().unwrap().get(0),
                Err(err) => panic!("{:#?}", err),
            };
            assert_eq!(val, result.to_string());
        }
    }

    fn write_type(sql_type: &str, checks: &[&'static str]) {
        let conn = match Connection::connect("postgres://paulmason@localhost", TlsMode::None) {
            Ok(x) => x,
            Err(err) => panic!("{:#?}", err),
        };
        for &val in checks.iter() {
            let stmt = match conn.prepare(&*format!("SELECT $1::{}", sql_type)) {
                Ok(x) => x,
                Err(err) => panic!("{:#?}", err),
            };
            let number = Decimal::from_str(val).unwrap();
            let result: Decimal = match stmt.query(&[&number]) {
                Ok(x) => x.iter().next().unwrap().get(0),
                Err(err) => panic!("{:#?}", err),
            };
            assert_eq!(val, result.to_string());
        }
    }

    #[test]
    fn test_null() {
        let conn = match Connection::connect("postgres://paulmason@localhost", TlsMode::None) {
            Ok(x) => x,
            Err(err) => panic!("{:#?}", err),
        };

        // Test NULL
        let stmt = match conn.prepare(&"SELECT NULL::numeric") {
            Ok(x) => x,
            Err(err) => panic!("{:#?}", err),
        };
        let result: Option<Decimal> = match stmt.query(&[]) {
            Ok(x) => x.iter().next().unwrap().get(0),
            Err(err) => panic!("{:#?}", err),
        };
        assert_eq!(None, result);
    }

    #[test]
    fn read_numeric_type() {
        read_type("NUMERIC(26,6)",
                       &["3950.123456",
                         "3950",
                         "0.1",
                         "0.01",
                         "0.001",
                         "0.0001",
                         "0.00001",
                         "0.000001",
                         "1",
                         "-100",
                         "-123.456",
                         "119996.25",
                         "1000000",
                         "9999999.99999"]);
    }

    #[test]
    fn write_numeric_type() {
        write_type("NUMERIC(26,6)",
                        &["3950.123456",
                          "3950",
                          "0.1",
                          "0.01",
                          "0.001",
                          "0.0001",
                          "0.00001",
                          "0.000001",
                          "1",
                          "-100",
                          "-123.456",
                          "119996.25",
                          "1000000",
                          "9999999.99999"]);
    }
}
<|endoftext|>


<commit_before>use grammar::repr::{Grammar, Production};
use normalize::normalize_without_validating;
use parser;
use test_util::expect_debug;

fn flat_productions(grammar: &Grammar) -> Vec<Production> {
    let mut productions: Vec<_> =
        grammar.productions.values()
                           .flat_map(|prods| prods.iter().cloned())
                           .collect();

    // sort by the action fn index just to get a consistent ordering
    productions.sort_by(|k1, k2| {
        Ord::cmp(&k1.action, &k2.action)
    });

    productions
}

#[test]
fn test_comma() {
    let grammar = parser::parse_grammar(r#"
grammar;
    extern { enum Tok { "," => .., "Id" => .. } }

    Comma<E>: Vec<E> =
       <v:(<E> ",")*> <e:E?> =>
           v.into_iter().chain(e.into_iter()).collect();

    Ids = Comma<"Id">;
"#).unwrap();
    let actual = normalize_without_validating(grammar).unwrap();

    expect_debug(flat_productions(&actual),
                 r#"[
    Ids = Comma<"Id"> => Call(ActionFn(0));,
    Comma<"Id"> = (<"Id"> ",")*, "Id"? => Call(ActionFn(1));,
    "Id"? = "Id" => Call(ActionFn(2));,
    "Id"? =  => Call(ActionFn(3));,
    (<"Id"> ",")* =  => Call(ActionFn(4));,
    (<"Id"> ",")* = (<"Id"> ",")*, (<"Id"> ",") => Call(ActionFn(5));,
    (<"Id"> ",") = "Id", "," => Call(ActionFn(6));
]"#);

    expect_debug(&actual.action_fn_defns,
                 r#"[
    fn _(__0: Vec<Tok>) -> Vec<Tok> { (__0) },
    fn _(v: ::std::vec::Vec<Tok>, e: ::std::option::Option<Tok>) -> Vec<Tok> { v.into_iter().chain(e.into_iter()).collect() },
    fn _(__0: Tok) -> ::std::option::Option<Tok> { Some(__0) },
    fn _() -> ::std::option::Option<Tok> { None },
    fn _() -> ::std::vec::Vec<Tok> { vec![] },
    fn _(v: ::std::vec::Vec<Tok>, e: Tok) -> ::std::vec::Vec<Tok> { { let mut v = v; v.push(e); v } },
    fn _(__0: Tok, _: Tok) -> Tok { (__0) }
]"#);
}
<commit_msg>add a simple test of lowering w/ no enum token<commit_after>use grammar::repr::{Grammar, Production};
use normalize::normalize_without_validating;
use parser;
use test_util::expect_debug;

fn flat_productions(grammar: &Grammar) -> Vec<Production> {
    let mut productions: Vec<_> =
        grammar.productions.values()
                           .flat_map(|prods| prods.iter().cloned())
                           .collect();

    // sort by the action fn index just to get a consistent ordering
    productions.sort_by(|k1, k2| {
        Ord::cmp(&k1.action, &k2.action)
    });

    productions
}

#[test]
fn test_comma() {
    let grammar = parser::parse_grammar(r#"
grammar;
    extern { enum Tok { "," => .., "Id" => .. } }

    Comma<E>: Vec<E> =
       <v:(<E> ",")*> <e:E?> =>
           v.into_iter().chain(e.into_iter()).collect();

    Ids = Comma<"Id">;
"#).unwrap();
    let actual = normalize_without_validating(grammar).unwrap();

    expect_debug(flat_productions(&actual),
                 r#"[
    Ids = Comma<"Id"> => Call(ActionFn(0));,
    Comma<"Id"> = (<"Id"> ",")*, "Id"? => Call(ActionFn(1));,
    "Id"? = "Id" => Call(ActionFn(2));,
    "Id"? =  => Call(ActionFn(3));,
    (<"Id"> ",")* =  => Call(ActionFn(4));,
    (<"Id"> ",")* = (<"Id"> ",")*, (<"Id"> ",") => Call(ActionFn(5));,
    (<"Id"> ",") = "Id", "," => Call(ActionFn(6));
]"#);

    expect_debug(&actual.action_fn_defns,
                 r#"[
    fn _(__0: Vec<Tok>) -> Vec<Tok> { (__0) },
    fn _(v: ::std::vec::Vec<Tok>, e: ::std::option::Option<Tok>) -> Vec<Tok> { v.into_iter().chain(e.into_iter()).collect() },
    fn _(__0: Tok) -> ::std::option::Option<Tok> { Some(__0) },
    fn _() -> ::std::option::Option<Tok> { None },
    fn _() -> ::std::vec::Vec<Tok> { vec![] },
    fn _(v: ::std::vec::Vec<Tok>, e: Tok) -> ::std::vec::Vec<Tok> { { let mut v = v; v.push(e); v } },
    fn _(__0: Tok, _: Tok) -> Tok { (__0) }
]"#);
}

#[test]
fn test_intern_token() {
    let grammar = parser::parse_grammar(r#"
grammar;
    extern { }

    A = ",";
"#).unwrap();
    let actual = normalize_without_validating(grammar).unwrap();
}
<|endoftext|>