<commit_before>use std::fmt::Write;
use std::string::String;

use types::*;
use formatter::Formatter;

fn write_char(f: &mut Formatter, c: char, n: usize) {
    for _ in 0..n {
        f.write_char(c).unwrap();
    }
}

#[test]
fn test_write_char() {
    let mut s = String::new();
    s.write_str("h ").unwrap();
    {
        let mut f = Formatter::from_str("{}", &mut s).unwrap();
        write_char(&mut f, 'f', 3);
    }
    assert!(s == "h fff");
}

fn write_from<'a, I>(fmt: &mut Formatter, f: I, n: usize) -> usize
    where I: Iterator<Item = char>
{
    // eexaust f or run out of n, return chars written
    if n == 0 {
        return 0;
    }
    let mut n_written: usize = 0;
    for c in f {
        fmt.write_char(c).unwrap();
        n_written += 1;
        if n_written == n {
            return n_written;
        }
    }
    n_written
}

#[test]
    fn test_write_from() {
    let mut s = String::new();
    s.write_str("h ").unwrap();
    {
        let mut f = Formatter::from_str("{}", &mut s).unwrap();
        write_from(&mut f, "fff".chars(), 5);
    }
    assert!(s == "h fff");
    {
        let mut f = Formatter::from_str("{}", &mut s).unwrap();
        write_from(&mut f, "xxxx".chars(), 2);
    }
    assert!(s == "h fffxx");
    {
        let mut f = Formatter::from_str("{}", &mut s).unwrap();
        write_from(&mut f, "333".chars(), 3);
    }
    assert!(s == "h fffxx333");
    s.clear();
    {
        let mut f = Formatter::from_str("{}", &mut s).unwrap();
        write!(f, "hello").unwrap();
    }
    assert!(s == "hello");
}

/// The formatter object given to closures
impl<'a, 'b> Formatter<'a, 'b> {
    /// format the given string onto the buffer
    pub fn str(self, s: &str) -> Result<()> {
        if !(self.ty() == None || self.ty() == Some('s')) {
            let mut msg = String::new();
            write!(msg, "Unknown format code {:?} for object of type 'str'", self.ty()).unwrap();
            return Err(FmtError::TypeError(msg));
        } else if self.alternate() {
            return Err(FmtError::TypeError("Alternate form (#) not allowed in string \
                                            format specifier".to_string()));
        } else if self.thousands() {
            return Err(FmtError::TypeError("Cannot specify ',' with 's'".to_string()));
        } else if self.sign().is_unspecified() {
            return Err(FmtError::TypeError("Sign not allowed in string format specifier"
                                           .to_string()));
        }
        self.str_unchecked(s)
    }

    /// UNSTABLE+UNTESTED: do not use in your own code (yet)
    /// Do the same as `str` but do not check the format string for errors.
    /// This gives a moderate performance boost.
    /// This isn't exactly unsafe, it just ends up ignoring extranious format
    /// specifiers
    //  For example, {x:<-#10} should technically be formatting an int, but ignoring the
    /// integer specific formatting is probably not the end of the world
    /// This can also be used by the `u64` etc methods to finish their formatting while
    /// still using the str formatter for width and alignment
    pub fn str_unchecked(mut self, s: &str) -> Result<()> {
        let len = s.len();
        let fill = self.fill();
        let width = self.width();
        let precision = self.precision();

        let mut chars = s.chars();
        let mut pad: usize = 0;
        match width {
            Some(mut width) => {
                if width > len {
                    let align = self.align();
                    match align {
                        Alignment::Left => pad = width - len,
                        Alignment::Center => {
                            width = width - len;
                            pad = width / 2;
                            write_char(&mut self, fill, pad);
                            pad += width % 2;
                        }
                        Alignment::Right => {
                            write_char(&mut self, fill, width - len);
                        }
                        Alignment::Equal => panic!("not yet supported"), // TODO
                    }
                }
            }
            None => {}
        }
        match precision {
            None => {
                for c in chars {
                    self.write_char(c).unwrap();
                }
            },
            Some(p) => {write_from(&mut self, &mut chars, p);},
        }
        write_char(&mut self, fill, pad);
        Ok(())
    }

}

/// UNSTABLE: the Formatter object is still considered unstable
/// Do not use this function if you aren't willing to have changes
/// forced on you!
///
/// format a string given the string and a closure that uses
/// a Formatter
pub fn strfmt_map<F>(fmtstr: &str, f: &F) -> Result<String>
    where F: Fn(Formatter) -> Result<()>
{
    let mut out = String::with_capacity(fmtstr.len() * 2);
    let mut bytes_read: usize = 0;
    let mut opening_brace: usize = 0;
    let mut closing_brace: bool = false;
    let mut reading_fmt = false;
    let mut remaining = fmtstr;
    for c in fmtstr.chars() {
        bytes_read += c.len_utf8();
        if c == '{' {
            if reading_fmt && opening_brace == bytes_read - 2 {
                // found {{
                out.push(c);
                reading_fmt = false;
            } else if !reading_fmt {
                // found a first {
                reading_fmt = true;
                opening_brace = bytes_read - 1;
            } else {
                // found a { after finding an opening brace, error!
                out.clear();
                out.write_str("extra { found").unwrap();
                return Err(FmtError::Invalid(out));
            }
        } else if c == '}' {
            if !reading_fmt && !closing_brace {
                // found a '}' that isn't after a '{'
                closing_brace = true;
            } else if closing_brace {
                // found "}}"
                out.push(c);
                closing_brace = false;
            } else {
                // found a format string
                // discard before opening brace
                let (_, r) = remaining.split_at(opening_brace);

                // get the fmt pattern and remaining
                let (fmt_pattern, r) = r.split_at(bytes_read - opening_brace);
                remaining = r;

                // discard the braces
                let (_, fmt_pattern) = fmt_pattern.split_at(1);
                let (fmt_pattern, _) = fmt_pattern.split_at(fmt_pattern.len() - 1);
                // use the closure to write the formatted string
                let fmt = try!(Formatter::from_str(fmt_pattern, &mut out));
                try!(f(fmt));
                reading_fmt = false;
                bytes_read = 0;
            }
        } else if closing_brace {
            return Err(FmtError::Invalid("Single '}' encountered in format string".to_string()));
        } else if !reading_fmt {
            out.push(c)
        } // else we are currently reading a format string, so don't push
    }
    if closing_brace {
        return Err(FmtError::Invalid("Single '}' encountered in format string".to_string()));
    } else if reading_fmt {
        return Err(FmtError::Invalid("Expected '}' before end of string".to_string()));
    }
    out.shrink_to_fit();
    Ok(out)
}
<commit_msg>fixed bug with precision + length<commit_after>use std::fmt::Write;
use std::string::String;

use types::*;
use formatter::Formatter;

fn write_char(f: &mut Formatter, c: char, n: usize) {
    for _ in 0..n {
        f.write_char(c).unwrap();
    }
}

#[test]
fn test_write_char() {
    let mut s = String::new();
    s.write_str("h ").unwrap();
    {
        let mut f = Formatter::from_str("{}", &mut s).unwrap();
        write_char(&mut f, 'f', 3);
    }
    assert!(s == "h fff");
}

fn write_from<'a, I>(fmt: &mut Formatter, f: I, n: usize) -> usize
    where I: Iterator<Item = char>
{
    // eexaust f or run out of n, return chars written
    if n == 0 {
        return 0;
    }
    let mut n_written: usize = 0;
    for c in f {
        fmt.write_char(c).unwrap();
        n_written += 1;
        if n_written == n {
            return n_written;
        }
    }
    n_written
}

#[test]
    fn test_write_from() {
    let mut s = String::new();
    s.write_str("h ").unwrap();
    {
        let mut f = Formatter::from_str("{}", &mut s).unwrap();
        write_from(&mut f, "fff".chars(), 5);
    }
    assert!(s == "h fff");
    {
        let mut f = Formatter::from_str("{}", &mut s).unwrap();
        write_from(&mut f, "xxxx".chars(), 2);
    }
    assert!(s == "h fffxx");
    {
        let mut f = Formatter::from_str("{}", &mut s).unwrap();
        write_from(&mut f, "333".chars(), 3);
    }
    assert!(s == "h fffxx333");
    s.clear();
    {
        let mut f = Formatter::from_str("{}", &mut s).unwrap();
        write!(f, "hello").unwrap();
    }
    assert!(s == "hello");
}

/// The formatter object given to closures
impl<'a, 'b> Formatter<'a, 'b> {
    /// format the given string onto the buffer
    pub fn str(self, s: &str) -> Result<()> {
        if !(self.ty() == None || self.ty() == Some('s')) {
            let mut msg = String::new();
            write!(msg, "Unknown format code {:?} for object of type 'str'", self.ty()).unwrap();
            return Err(FmtError::TypeError(msg));
        } else if self.alternate() {
            return Err(FmtError::TypeError("Alternate form (#) not allowed in string \
                                            format specifier".to_string()));
        } else if self.thousands() {
            return Err(FmtError::TypeError("Cannot specify ',' with 's'".to_string()));
        } else if self.sign().is_unspecified() {
            return Err(FmtError::TypeError("Sign not allowed in string format specifier"
                                           .to_string()));
        }
        self.str_unchecked(s)
    }

    /// UNSTABLE+UNTESTED: do not use in your own code (yet)
    /// Do the same as `str` but do not check the format string for errors.
    /// This gives a moderate performance boost.
    /// This isn't exactly unsafe, it just ends up ignoring extranious format
    /// specifiers
    //  For example, {x:<-#10} should technically be formatting an int, but ignoring the
    /// integer specific formatting is probably not the end of the world
    /// This can also be used by the `u64` etc methods to finish their formatting while
    /// still using the str formatter for width and alignment
    pub fn str_unchecked(mut self, s: &str) -> Result<()> {
        let fill = self.fill();
        let width = self.width();
        let precision = self.precision();
        // precision will limit length
        let len = match precision {
            Some(p) => if p < s.len() {
                p
            } else {
                s.len()
            },
            None => s.len(),
        };

        let mut chars = s.chars();
        let mut pad: usize = 0;
        match width {
            Some(mut width) => {
                if width > len {
                    let align = self.align();
                    match align {
                        Alignment::Left => pad = width - len,
                        Alignment::Center => {
                            width = width - len;
                            pad = width / 2;
                            write_char(&mut self, fill, pad);
                            pad += width % 2;
                        }
                        Alignment::Right => {
                            write_char(&mut self, fill, width - len);
                        }
                        Alignment::Equal => panic!("not yet supported"), // TODO
                    }
                }
            }
            None => {}
        }
        write_from(&mut self, &mut chars, len);
        write_char(&mut self, fill, pad);
        Ok(())
    }

}

/// UNSTABLE: the Formatter object is still considered unstable
/// Do not use this function if you aren't willing to have changes
/// forced on you!
///
/// format a string given the string and a closure that uses
/// a Formatter
pub fn strfmt_map<F>(fmtstr: &str, f: &F) -> Result<String>
    where F: Fn(Formatter) -> Result<()>
{
    let mut out = String::with_capacity(fmtstr.len() * 2);
    let mut bytes_read: usize = 0;
    let mut opening_brace: usize = 0;
    let mut closing_brace: bool = false;
    let mut reading_fmt = false;
    let mut remaining = fmtstr;
    for c in fmtstr.chars() {
        bytes_read += c.len_utf8();
        if c == '{' {
            if reading_fmt && opening_brace == bytes_read - 2 {
                // found {{
                out.push(c);
                reading_fmt = false;
            } else if !reading_fmt {
                // found a first {
                reading_fmt = true;
                opening_brace = bytes_read - 1;
            } else {
                // found a { after finding an opening brace, error!
                out.clear();
                out.write_str("extra { found").unwrap();
                return Err(FmtError::Invalid(out));
            }
        } else if c == '}' {
            if !reading_fmt && !closing_brace {
                // found a '}' that isn't after a '{'
                closing_brace = true;
            } else if closing_brace {
                // found "}}"
                out.push(c);
                closing_brace = false;
            } else {
                // found a format string
                // discard before opening brace
                let (_, r) = remaining.split_at(opening_brace);

                // get the fmt pattern and remaining
                let (fmt_pattern, r) = r.split_at(bytes_read - opening_brace);
                remaining = r;

                // discard the braces
                let (_, fmt_pattern) = fmt_pattern.split_at(1);
                let (fmt_pattern, _) = fmt_pattern.split_at(fmt_pattern.len() - 1);
                // use the closure to write the formatted string
                let fmt = try!(Formatter::from_str(fmt_pattern, &mut out));
                try!(f(fmt));
                reading_fmt = false;
                bytes_read = 0;
            }
        } else if closing_brace {
            return Err(FmtError::Invalid("Single '}' encountered in format string".to_string()));
        } else if !reading_fmt {
            out.push(c)
        } // else we are currently reading a format string, so don't push
    }
    if closing_brace {
        return Err(FmtError::Invalid("Single '}' encountered in format string".to_string()));
    } else if reading_fmt {
        return Err(FmtError::Invalid("Expected '}' before end of string".to_string()));
    }
    out.shrink_to_fit();
    Ok(out)
}
<|endoftext|>


<commit_before>use std::fmt;
use std::io::{self, BufRead, BufReader};
use std::net::TcpStream;
use std::path::PathBuf;
use std::thread;

use log::{debug, error, info};
use serde::Deserialize;
use serde_json;
use ssh2;
use ssh2::Channel;

use futures::sync::mpsc::{channel, Receiver, Sender};
use futures::{Future, Sink, Stream};

/// Gerrit username
pub type Username = String;

#[derive(Deserialize, Debug, Clone)]
pub struct User {
    pub name: Option<String>,
    pub username: Username,
    pub email: Option<String>,
}

impl fmt::Display for User {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(&self.username)
    }
}

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Approval {
    #[serde(rename = "type")]
    pub approval_type: String,
    pub description: String,
    pub value: String,
    pub old_value: Option<String>,
}

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PatchSet {
    pub number: u32,
    pub revision: String,
    pub parents: Vec<String>,
    #[serde(rename = "ref")]
    pub reference: String,
    pub uploader: User,
    pub created_on: u32,
    pub author: User,
    pub is_draft: bool,
    pub kind: String,
    pub size_insertions: i32,
    pub size_deletions: i32,
    pub comments: Option<Vec<InlineComment>>,
}

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct InlineComment {
    pub file: String,
    pub line: u32,
    pub reviewer: User,
    pub message: String,
}

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Change {
    pub project: String,
    pub branch: String,
    pub id: String,
    pub number: u32,
    pub subject: String,
    pub topic: Option<String>,
    pub owner: User,
    pub url: String,
    pub commit_message: String,
    pub status: String,
    pub current_patch_set: Option<PatchSet>,
    pub comments: Option<Vec<Comment>>,
}

#[derive(Deserialize, Debug, Clone)]
pub struct Comment {
    pub timestamp: u64,
    pub reviewer: User,
    pub message: String,
}

#[derive(Deserialize, Debug, Eq, PartialEq, Hash, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ChangeKey {
    pub id: String,
}

#[derive(Deserialize, Debug, Clone, PartialEq)]
pub enum EventType {
    #[serde(rename = "reviewer-added")]
    ReviewerAdded,
    #[serde(rename = "comment-added")]
    CommentAdded,
}

// Only specific events are accepted by this type by design!
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Event {
    pub author: Option<User>,
    pub uploader: Option<User>,
    pub approvals: Option<Vec<Approval>>,
    pub reviewer: Option<User>,
    pub comment: Option<String>,
    #[serde(rename = "patchSet")]
    pub patchset: PatchSet,
    pub change: Change,
    pub project: String,
    #[serde(rename = "refName")]
    pub ref_name: String,
    #[serde(rename = "changeKey")]
    pub changekey: ChangeKey,
    #[serde(rename = "type")]
    pub event_type: EventType,
    #[serde(rename = "eventCreatedOn")]
    pub created_on: u32,
}

#[derive(Debug)]
pub enum StreamError {
    Io(io::Error),
    Parse(serde_json::Error),
    Terminated(String /* reason */),
}

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

impl From<serde_json::Error> for StreamError {
    fn from(err: serde_json::Error) -> StreamError {
        StreamError::Parse(err)
    }
}

fn get_pub_key_path(priv_key_path: &PathBuf) -> PathBuf {
    let mut pub_key_path = PathBuf::from(priv_key_path.to_str().unwrap());
    pub_key_path.set_extension("pub");
    pub_key_path
}

fn send_terminate_msg<T>(
    tx: &Sender<Result<String, StreamError>>,
    reason: String,
) -> Result<T, ()> {
    let _ = tx.clone().send(Err(StreamError::Terminated(reason))).wait();
    Err(())
}

pub struct GerritConnection {
    pub session: ssh2::Session,
    /// tcp has to be kept alive with session together, even if it is never used directly
    tcp: Box<TcpStream>,
    // Data needed for reconnection in case this connection was terminated.
    host: String,
    username: String,
    priv_key_path: PathBuf,
}

impl GerritConnection {
    pub fn connect(host: String, username: String, priv_key_path: PathBuf) -> Result<Self, String> {
        let pub_key_path = get_pub_key_path(&priv_key_path);
        debug!("Will use public key: {}", pub_key_path.to_str().unwrap());

        let mut session = ssh2::Session::new().unwrap();

        debug!("Connecting to tcp: {}", &host);
        let tcp = Box::new(TcpStream::connect(&host).or_else(|err| {
            Err(format!(
                "Could not connect to gerrit at {}: {:?}",
                host, err
            ))
        })?);

        session
            .handshake(&*tcp)
            .or_else(|err| Err(format!("Could not connect to gerrit: {:?}", err)))?;

        // Try to authenticate
        session
            .userauth_pubkey_file(&username, Some(&pub_key_path), &priv_key_path, None)
            .or_else(|err| Err(format!("Could not authenticate: {:?}", err)))?;

        Ok(Self {
            session,
            tcp,
            host,
            username,
            priv_key_path,
        })
    }

    pub fn reconnect(&mut self) -> Result<(), String> {
        let mut session = ssh2::Session::new().unwrap();
        let tcp = Box::new(TcpStream::connect(&self.host).or_else(|err| {
            Err(format!(
                "Could not connect to gerrit at {}: {:?}",
                self.host, err
            ))
        })?);

        session
            .handshake(&*tcp)
            .or_else(|err| Err(format!("Could not connect to gerrit: {:?}", err)))?;

        // Try to authenticate
        let pub_key_path = get_pub_key_path(&self.priv_key_path);
        debug!("Will use public key: {}", pub_key_path.to_str().unwrap());
        session
            .userauth_pubkey_file(
                &self.username,
                Some(&pub_key_path),
                &self.priv_key_path,
                None,
            )
            .or_else(|err| Err(format!("Could not authenticate: {:?}", err)))?;

        self.session = session;
        self.tcp = tcp;

        Ok(())
    }
}

fn receiver_into_event_stream(
    rx: Receiver<Result<String, StreamError>>,
) -> Box<dyn Stream<Item = Event, Error = String>> {
    let stream = rx
        // Receiver itself never fails, lower result value.
        .then(|event_data_result: Result<_, ()>| event_data_result.unwrap())
        .map_err(|err| format!("Stream error from Gerrit: {:?}", err))
        .filter_map(|event_data| {
            let event_result = serde_json::from_str(&event_data);
            debug!("Incoming Gerrit event: {:#?}", event_result);
            // Ignore JSON decoding errors.
            event_result.ok()
        });
    Box::new(stream)
}

pub fn event_stream(
    host: String,
    username: String,
    priv_key_path: PathBuf,
) -> Box<dyn Stream<Item = Event, Error = String>> {
    let (main_tx, rx) = channel(1);
    thread::spawn(move || -> Result<(), ()> {
        loop {
            info!("(Re)connecting to Gerrit over SSH: {}", &host);
            let conn =
                GerritConnection::connect(host.clone(), username.clone(), priv_key_path.clone())
                    .or_else(|err| {
                        send_terminate_msg(
                            &main_tx.clone(),
                            format!("Could not connect to Gerrit: {}", err),
                        )
                    })?;

            let mut ssh_channel = conn.session.channel_session().or_else(|err| {
                send_terminate_msg(
                    &main_tx.clone(),
                    format!("Could not open SSH channel: {:?}", err),
                )
            })?;
            ssh_channel
                .exec("gerrit stream-events -s comment-added -s reviewer-added")
                .or_else(|err| {
                    send_terminate_msg(
                        &main_tx.clone(),
                        format!(
                            "Could not execute gerrit stream-event command over ssh: {:?}",
                            err
                        ),
                    )
                })?;
            info!("Connected to Gerrit.");

            let buf_channel = BufReader::new(ssh_channel);
            let mut tx = main_tx.clone();
            for line in buf_channel.lines() {
                if let Ok(line) = line {
                    match tx.clone().send(Ok(line)).wait() {
                        Ok(s) => tx = s,
                        Err(err) => {
                            error!("Cannot send message through channel {:?}", err);
                            break;
                        }
                    }
                } else {
                    error!("Could not read line from buffer. Will drop connection.");
                    break;
                }
            }
        }
    });

    receiver_into_event_stream(rx)
}

#[derive(Debug)]
pub struct ChangeDetails {
    pub user: String,
    pub message: String,
    pub change: Change,
}

/// Create a channel accepting change ids and a stream of reponses with `Change` corresponding to
/// incoming change ids.
///
/// Note: If connection to Gerrit is lost, the stream will try to establish a new one for every
/// incoming change id.
pub fn change_sink(
    host: String,
    username: String,
    priv_key_path: PathBuf,
) -> Result<
    (
        Sender<(String, Change, String)>,
        Box<dyn Stream<Item = ChangeDetails, Error = String>>,
    ),
    String,
> {
    let mut conn = GerritConnection::connect(host, username, priv_key_path)?;
    let (tx, rx) = channel::<(String, Change, String)>(1);
    let response_stream = rx.then(move |data| {
        let (user, mut change, message) = data.expect("receiver should never fail");

        let change_id = change.id.clone();
        let res = conn.session.channel_session().map(|ssh_channel| {
            let comments = match fetch_patch_set(ssh_channel, change_id.clone()) {
                Ok(c) => c,
                Err(e) => {
                    error!("Could not fetch additional comments: {:?}", e);
                    None
                }
            };
            debug!("Got comments: {:#?}", comments);
            change.current_patch_set = comments;
        });

        if let Ok(_) = res {
            return Ok(ChangeDetails {
                user: user.clone(),
                message: message.clone(),
                change: change.clone(),
            });
        } else {
            info!(
                "Reconnecting to Gerrit over SSH for sending commands: {}",
                &conn.host
            );
            if let Err(e) = conn.reconnect() {
                return Err(format!("Failed to reconnect to Gerrit over SSH: {}", e));
            };

            conn.session
                .channel_session()
                .map_err(|e| {
                    format!(
                        "Failed to reconnect to Gerrit over SSH for sending commands: {}",
                        e
                    )
                })
                .and_then(|ssh_channel| {
                    let comments = match fetch_patch_set(ssh_channel, change_id) {
                        Ok(c) => c,
                        Err(e) => {
                            error!("Could not fetch additional comments: {:?}", e);
                            None
                        }
                    };
                    debug!("Got comments: {:#?}", comments);
                    change.current_patch_set = comments;
                    Ok(ChangeDetails {
                        user,
                        message,
                        change,
                    })
                })
        }
    });

    Ok((tx, Box::new(response_stream)))
}

pub fn fetch_patch_set(
    mut ssh_channel: Channel,
    change_id: String,
) -> Result<Option<PatchSet>, serde_json::Error> {
    let query = format!(
        "gerrit query --format JSON --current-patch-set --comments {}",
        change_id
    );
    ssh_channel.exec(&query).unwrap();

    let buf_channel = BufReader::new(ssh_channel);
    let line = buf_channel.lines().next();

    // event from our channel cannot fail
    let json: String = line.unwrap().ok().unwrap();
    debug!("{}", json);
    let complete_change: Change = serde_json::from_str::<Change>(&json)?;
    debug!("{:#?}", complete_change);
    //debug!("{:#?}", complete_change);
    //let mut new_change = change.clone();
    //change.comments = complete_change.comments;
    Ok(complete_change.current_patch_set)
}

#[cfg(test)]
mod test {
    use super::{get_pub_key_path, PathBuf};

    #[test]
    fn test_get_pub_key_path() {
        let result = get_pub_key_path(&PathBuf::from("some_priv_key"));
        assert!(result == PathBuf::from("some_priv_key.pub"));
    }
}
<commit_msg>don't box event stream<commit_after>use std::fmt;
use std::io::{self, BufRead, BufReader};
use std::net::TcpStream;
use std::path::PathBuf;
use std::thread;

use log::{debug, error, info};
use serde::Deserialize;
use serde_json;
use ssh2;
use ssh2::Channel;

use futures::sync::mpsc::{channel, Receiver, Sender};
use futures::{Future, Sink, Stream};

/// Gerrit username
pub type Username = String;

#[derive(Deserialize, Debug, Clone)]
pub struct User {
    pub name: Option<String>,
    pub username: Username,
    pub email: Option<String>,
}

impl fmt::Display for User {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str(&self.username)
    }
}

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Approval {
    #[serde(rename = "type")]
    pub approval_type: String,
    pub description: String,
    pub value: String,
    pub old_value: Option<String>,
}

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct PatchSet {
    pub number: u32,
    pub revision: String,
    pub parents: Vec<String>,
    #[serde(rename = "ref")]
    pub reference: String,
    pub uploader: User,
    pub created_on: u32,
    pub author: User,
    pub is_draft: bool,
    pub kind: String,
    pub size_insertions: i32,
    pub size_deletions: i32,
    pub comments: Option<Vec<InlineComment>>,
}

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct InlineComment {
    pub file: String,
    pub line: u32,
    pub reviewer: User,
    pub message: String,
}

#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Change {
    pub project: String,
    pub branch: String,
    pub id: String,
    pub number: u32,
    pub subject: String,
    pub topic: Option<String>,
    pub owner: User,
    pub url: String,
    pub commit_message: String,
    pub status: String,
    pub current_patch_set: Option<PatchSet>,
    pub comments: Option<Vec<Comment>>,
}

#[derive(Deserialize, Debug, Clone)]
pub struct Comment {
    pub timestamp: u64,
    pub reviewer: User,
    pub message: String,
}

#[derive(Deserialize, Debug, Eq, PartialEq, Hash, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ChangeKey {
    pub id: String,
}

#[derive(Deserialize, Debug, Clone, PartialEq)]
pub enum EventType {
    #[serde(rename = "reviewer-added")]
    ReviewerAdded,
    #[serde(rename = "comment-added")]
    CommentAdded,
}

// Only specific events are accepted by this type by design!
#[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Event {
    pub author: Option<User>,
    pub uploader: Option<User>,
    pub approvals: Option<Vec<Approval>>,
    pub reviewer: Option<User>,
    pub comment: Option<String>,
    #[serde(rename = "patchSet")]
    pub patchset: PatchSet,
    pub change: Change,
    pub project: String,
    #[serde(rename = "refName")]
    pub ref_name: String,
    #[serde(rename = "changeKey")]
    pub changekey: ChangeKey,
    #[serde(rename = "type")]
    pub event_type: EventType,
    #[serde(rename = "eventCreatedOn")]
    pub created_on: u32,
}

#[derive(Debug)]
pub enum StreamError {
    Io(io::Error),
    Parse(serde_json::Error),
    Terminated(String /* reason */),
}

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

impl From<serde_json::Error> for StreamError {
    fn from(err: serde_json::Error) -> StreamError {
        StreamError::Parse(err)
    }
}

fn get_pub_key_path(priv_key_path: &PathBuf) -> PathBuf {
    let mut pub_key_path = PathBuf::from(priv_key_path.to_str().unwrap());
    pub_key_path.set_extension("pub");
    pub_key_path
}

fn send_terminate_msg<T>(
    tx: &Sender<Result<String, StreamError>>,
    reason: String,
) -> Result<T, ()> {
    let _ = tx.clone().send(Err(StreamError::Terminated(reason))).wait();
    Err(())
}

pub struct GerritConnection {
    pub session: ssh2::Session,
    /// tcp has to be kept alive with session together, even if it is never used directly
    tcp: Box<TcpStream>,
    // Data needed for reconnection in case this connection was terminated.
    host: String,
    username: String,
    priv_key_path: PathBuf,
}

impl GerritConnection {
    pub fn connect(host: String, username: String, priv_key_path: PathBuf) -> Result<Self, String> {
        let pub_key_path = get_pub_key_path(&priv_key_path);
        debug!("Will use public key: {}", pub_key_path.to_str().unwrap());

        let mut session = ssh2::Session::new().unwrap();

        debug!("Connecting to tcp: {}", &host);
        let tcp = Box::new(TcpStream::connect(&host).or_else(|err| {
            Err(format!(
                "Could not connect to gerrit at {}: {:?}",
                host, err
            ))
        })?);

        session
            .handshake(&*tcp)
            .or_else(|err| Err(format!("Could not connect to gerrit: {:?}", err)))?;

        // Try to authenticate
        session
            .userauth_pubkey_file(&username, Some(&pub_key_path), &priv_key_path, None)
            .or_else(|err| Err(format!("Could not authenticate: {:?}", err)))?;

        Ok(Self {
            session,
            tcp,
            host,
            username,
            priv_key_path,
        })
    }

    pub fn reconnect(&mut self) -> Result<(), String> {
        let mut session = ssh2::Session::new().unwrap();
        let tcp = Box::new(TcpStream::connect(&self.host).or_else(|err| {
            Err(format!(
                "Could not connect to gerrit at {}: {:?}",
                self.host, err
            ))
        })?);

        session
            .handshake(&*tcp)
            .or_else(|err| Err(format!("Could not connect to gerrit: {:?}", err)))?;

        // Try to authenticate
        let pub_key_path = get_pub_key_path(&self.priv_key_path);
        debug!("Will use public key: {}", pub_key_path.to_str().unwrap());
        session
            .userauth_pubkey_file(
                &self.username,
                Some(&pub_key_path),
                &self.priv_key_path,
                None,
            )
            .or_else(|err| Err(format!("Could not authenticate: {:?}", err)))?;

        self.session = session;
        self.tcp = tcp;

        Ok(())
    }
}

fn receiver_into_event_stream(
    rx: Receiver<Result<String, StreamError>>,
) -> impl Stream<Item = Event, Error = String> {
    rx
        // Receiver itself never fails, lower result value.
        .then(|event_data_result: Result<_, ()>| event_data_result.unwrap())
        .map_err(|err| format!("Stream error from Gerrit: {:?}", err))
        .filter_map(|event_data| {
            let event_result = serde_json::from_str(&event_data);
            debug!("Incoming Gerrit event: {:#?}", event_result);
            // Ignore JSON decoding errors.
            event_result.ok()
        })
}

pub fn event_stream(
    host: String,
    username: String,
    priv_key_path: PathBuf,
) -> impl Stream<Item = Event, Error = String> {
    let (main_tx, rx) = channel(1);
    thread::spawn(move || -> Result<(), ()> {
        loop {
            info!("(Re)connecting to Gerrit over SSH: {}", &host);
            let conn =
                GerritConnection::connect(host.clone(), username.clone(), priv_key_path.clone())
                    .or_else(|err| {
                        send_terminate_msg(
                            &main_tx.clone(),
                            format!("Could not connect to Gerrit: {}", err),
                        )
                    })?;

            let mut ssh_channel = conn.session.channel_session().or_else(|err| {
                send_terminate_msg(
                    &main_tx.clone(),
                    format!("Could not open SSH channel: {:?}", err),
                )
            })?;
            ssh_channel
                .exec("gerrit stream-events -s comment-added -s reviewer-added")
                .or_else(|err| {
                    send_terminate_msg(
                        &main_tx.clone(),
                        format!(
                            "Could not execute gerrit stream-event command over ssh: {:?}",
                            err
                        ),
                    )
                })?;
            info!("Connected to Gerrit.");

            let buf_channel = BufReader::new(ssh_channel);
            let mut tx = main_tx.clone();
            for line in buf_channel.lines() {
                if let Ok(line) = line {
                    match tx.clone().send(Ok(line)).wait() {
                        Ok(s) => tx = s,
                        Err(err) => {
                            error!("Cannot send message through channel {:?}", err);
                            break;
                        }
                    }
                } else {
                    error!("Could not read line from buffer. Will drop connection.");
                    break;
                }
            }
        }
    });

    receiver_into_event_stream(rx)
}

#[derive(Debug)]
pub struct ChangeDetails {
    pub user: String,
    pub message: String,
    pub change: Change,
}

/// Create a channel accepting change ids and a stream of reponses with `Change` corresponding to
/// incoming change ids.
///
/// Note: If connection to Gerrit is lost, the stream will try to establish a new one for every
/// incoming change id.
pub fn change_sink(
    host: String,
    username: String,
    priv_key_path: PathBuf,
) -> Result<
    (
        Sender<(String, Change, String)>,
        Box<dyn Stream<Item = ChangeDetails, Error = String>>,
    ),
    String,
> {
    let mut conn = GerritConnection::connect(host, username, priv_key_path)?;
    let (tx, rx) = channel::<(String, Change, String)>(1);
    let response_stream = rx.then(move |data| {
        let (user, mut change, message) = data.expect("receiver should never fail");

        let change_id = change.id.clone();
        let res = conn.session.channel_session().map(|ssh_channel| {
            let comments = match fetch_patch_set(ssh_channel, change_id.clone()) {
                Ok(c) => c,
                Err(e) => {
                    error!("Could not fetch additional comments: {:?}", e);
                    None
                }
            };
            debug!("Got comments: {:#?}", comments);
            change.current_patch_set = comments;
        });

        if let Ok(_) = res {
            return Ok(ChangeDetails {
                user: user.clone(),
                message: message.clone(),
                change: change.clone(),
            });
        } else {
            info!(
                "Reconnecting to Gerrit over SSH for sending commands: {}",
                &conn.host
            );
            if let Err(e) = conn.reconnect() {
                return Err(format!("Failed to reconnect to Gerrit over SSH: {}", e));
            };

            conn.session
                .channel_session()
                .map_err(|e| {
                    format!(
                        "Failed to reconnect to Gerrit over SSH for sending commands: {}",
                        e
                    )
                })
                .and_then(|ssh_channel| {
                    let comments = match fetch_patch_set(ssh_channel, change_id) {
                        Ok(c) => c,
                        Err(e) => {
                            error!("Could not fetch additional comments: {:?}", e);
                            None
                        }
                    };
                    debug!("Got comments: {:#?}", comments);
                    change.current_patch_set = comments;
                    Ok(ChangeDetails {
                        user,
                        message,
                        change,
                    })
                })
        }
    });

    Ok((tx, Box::new(response_stream)))
}

pub fn fetch_patch_set(
    mut ssh_channel: Channel,
    change_id: String,
) -> Result<Option<PatchSet>, serde_json::Error> {
    let query = format!(
        "gerrit query --format JSON --current-patch-set --comments {}",
        change_id
    );
    ssh_channel.exec(&query).unwrap();

    let buf_channel = BufReader::new(ssh_channel);
    let line = buf_channel.lines().next();

    // event from our channel cannot fail
    let json: String = line.unwrap().ok().unwrap();
    debug!("{}", json);
    let complete_change: Change = serde_json::from_str::<Change>(&json)?;
    debug!("{:#?}", complete_change);
    //debug!("{:#?}", complete_change);
    //let mut new_change = change.clone();
    //change.comments = complete_change.comments;
    Ok(complete_change.current_patch_set)
}

#[cfg(test)]
mod test {
    use super::{get_pub_key_path, PathBuf};

    #[test]
    fn test_get_pub_key_path() {
        let result = get_pub_key_path(&PathBuf::from("some_priv_key"));
        assert!(result == PathBuf::from("some_priv_key.pub"));
    }
}
<|endoftext|>


<commit_before>
use std::io::Read;  // Trait providing read_to_string()

use hyper;
use rustc_serialize;

use projects;

pub const API_VERSION: u16 = 3;

#[derive(Debug, RustcDecodable, RustcEncodable)]
pub struct Version {
    pub version: String,
    pub revision: String,
}

#[derive(Debug)]
pub struct Pagination {
    pub page: u16,
    pub per_page: u16,
}

#[derive(Debug)]
pub struct GitLab {
    scheme: String,
    domain: String,
    port: u16,
    private_token: String,
    client: hyper::Client,
    pagination: Pagination,
}

impl GitLab {

    pub fn new(scheme: &str, domain: &str, port: u16, private_token: &str) -> GitLab {
        GitLab {
            scheme: scheme.to_string(),
            domain: domain.to_string(),
            port:   port,
            private_token: private_token.to_string(),
            client: hyper::Client::new(),
            pagination: Pagination {page: 1, per_page: 20},
        }
    }

    pub fn new_http(domain: &str, private_token: &str) -> GitLab {
        GitLab::new("http", &domain, 80, &private_token)
    }

    pub fn new_https(domain: &str, private_token: &str) -> GitLab {
        GitLab::new("https", &domain, 443, &private_token)
    }

    pub fn build_url(&self, command: &str) -> String {
        format!("{}://{}:{}/api/v{}/{}?private_token={}&page={}&per_page={}",
                                self.scheme,
                                self.domain,
                                self.port,
                                API_VERSION,
                                command,
                                self.private_token,
                                self.pagination.page,
                                self.pagination.per_page)
    }

    pub fn get(&self, command: &str) -> Result<hyper::client::Response, hyper::Error> {
        let url = self.build_url(&command);
        // Close connections after each GET.
        self.client.get(&url).header(hyper::header::Connection::close()).send()
    }

    pub fn set_pagination(&mut self, pagination: Pagination) {
        self.pagination = pagination;
    }

    pub fn version(&self) -> Result<Version, rustc_serialize::json::DecoderError> {
        let url = self.build_url("version");
        let mut res: hyper::client::Response =
                        self.client
                        .get(&url)
                        .header(hyper::header::Connection::close())
                        .send()
                        .unwrap();

        let mut body = String::new();
        res.read_to_string(&mut body).unwrap();

        rustc_serialize::json::decode(body.as_str())
    }

    pub fn attempt_connection(&self) -> Result<hyper::client::Response, hyper::Error> {
        self.get("version")
    }

    pub fn projects(&self) {
        let mut res: hyper::client::Response = self.get("projects").unwrap();
        println!("####################################################################");
        println!("res: {:?}", res);
        println!("####################################################################");
        println!("Response: {}", res.status);
        println!("####################################################################");
        println!("Headers:\n{}", res.headers);
        println!("####################################################################");

        // let fresh_request = Request::get(url);
        // let streaming_request = fresh_request.start();
        // let mut response = streaming_request.send();
        // Ok(response.read_to_string())

        // let mut body = Vec::new::<u8>();
        // body.resize(, 0);
        // let result = res.read_exact(&body);

        // println!("####################################################################");
        println!("----------------------------");
        let mut body = String::new();
        println!("----------------------------");
        res.read_to_string(&mut body).unwrap();
        // println!("####################################################################");
        // let result = res.read_to_string(&mut body);
        // println!("####################################################################");
        // println!("body:\n{}", body);

        let projects: projects::Projects = rustc_serialize::json::decode(body.as_str()).unwrap();
        println!("{}", projects.p[0].description);

    }

    // pub fn projects(&self) -> ProjectManager {
    //     ProjectManager()
    // }
}

#[cfg(test)]
mod tests {
    use std::env;
    use gitlab::GitLab;

    #[test]
    fn list_projects() {
        let token = match env::var("GITLAB_TOKEN") {
            Ok(val) => val,
            Err(_)  => panic!("Please set environment variable 'GITLAB_TOKEN'"),
        };

        let gl = GitLab::new_https("gitlab.com", &token);
        println!("gl: {:?}", gl);

        gl.projects();
        // assert_eq!(gl.attempt_connection().unwrap().status, hyper::Ok);

    }
}
<commit_msg>Add a generic get()<commit_after>
use std::io::Read;  // Trait providing read_to_string()

use hyper;
use rustc_serialize;

use projects;

pub const API_VERSION: u16 = 3;

#[derive(Debug, RustcDecodable, RustcEncodable)]
pub struct Version {
    pub version: String,
    pub revision: String,
}

#[derive(Debug)]
pub struct Pagination {
    pub page: u16,
    pub per_page: u16,
}

#[derive(Debug)]
pub struct GitLab {
    scheme: String,
    domain: String,
    port: u16,
    private_token: String,
    client: hyper::Client,
    pagination: Pagination,
}

impl GitLab {

    pub fn new(scheme: &str, domain: &str, port: u16, private_token: &str) -> GitLab {
        GitLab {
            scheme: scheme.to_string(),
            domain: domain.to_string(),
            port:   port,
            private_token: private_token.to_string(),
            client: hyper::Client::new(),
            pagination: Pagination {page: 1, per_page: 20},
        }
    }

    pub fn new_http(domain: &str, private_token: &str) -> GitLab {
        GitLab::new("http", &domain, 80, &private_token)
    }

    pub fn new_https(domain: &str, private_token: &str) -> GitLab {
        GitLab::new("https", &domain, 443, &private_token)
    }

    pub fn build_url(&self, command: &str) -> String {
        format!("{}://{}:{}/api/v{}/{}?private_token={}&page={}&per_page={}",
                                self.scheme,
                                self.domain,
                                self.port,
                                API_VERSION,
                                command,
                                self.private_token,
                                self.pagination.page,
                                self.pagination.per_page)
    }

    pub fn attempt_connection(&self) -> Result<hyper::client::Response, hyper::Error> {
        // self.get("version")
        let url = self.build_url("version");
        // Close connections after each GET.
        let res = self.client.get(&url).header(hyper::header::Connection::close()).send();

        res
    }

    pub fn set_pagination(&mut self, pagination: Pagination) {
        self.pagination = pagination;
    }

    pub fn get<T>(&self, command: &str) -> Result<T, rustc_serialize::json::DecoderError>
            where T: rustc_serialize::Decodable {

        let url = self.build_url(command);
        let mut res: hyper::client::Response =
                        self.client
                        .get(&url)
                        .header(hyper::header::Connection::close())
                        .send()
                        .unwrap();

        let mut body = String::new();
        res.read_to_string(&mut body).unwrap();

        rustc_serialize::json::decode(body.as_str())
    }

    pub fn version(&self) -> Result<Version, rustc_serialize::json::DecoderError> {
        self.get("version")
    }

    pub fn projects(&self) {
        // let mut res: hyper::client::Response = self.get("projects").unwrap();
        let url = self.build_url("projects");
        let mut res: hyper::client::Response =
                        self.client
                        .get(&url)
                        .header(hyper::header::Connection::close())
                        .send()
                        .unwrap();
        println!("####################################################################");
        println!("res: {:?}", res);
        println!("####################################################################");
        println!("Response: {}", res.status);
        println!("####################################################################");
        println!("Headers:\n{}", res.headers);
        println!("####################################################################");

        // let fresh_request = Request::get(url);
        // let streaming_request = fresh_request.start();
        // let mut response = streaming_request.send();
        // Ok(response.read_to_string())

        // let mut body = Vec::new::<u8>();
        // body.resize(, 0);
        // let result = res.read_exact(&body);

        // println!("####################################################################");
        println!("----------------------------");
        let mut body = String::new();
        println!("----------------------------");
        res.read_to_string(&mut body).unwrap();
        // println!("####################################################################");
        // let result = res.read_to_string(&mut body);
        // println!("####################################################################");
        // println!("body:\n{}", body);

        let projects: projects::Projects = rustc_serialize::json::decode(body.as_str()).unwrap();
        println!("{}", projects.p[0].description);

    }

    // pub fn projects(&self) -> ProjectManager {
    //     ProjectManager()
    // }
}

#[cfg(test)]
mod tests {
    use std::env;
    use gitlab::GitLab;

    #[test]
    fn list_projects() {
        let token = match env::var("GITLAB_TOKEN") {
            Ok(val) => val,
            Err(_)  => panic!("Please set environment variable 'GITLAB_TOKEN'"),
        };

        let gl = GitLab::new_https("gitlab.com", &token);
        println!("gl: {:?}", gl);

        gl.projects();
        // assert_eq!(gl.attempt_connection().unwrap().status, hyper::Ok);

    }
}
<|endoftext|>


<commit_before>/*!
The ignore module is responsible for managing the state required to determine
whether a *single* file path should be searched or not.

In general, there are two ways to ignore a particular file:

1. Specify an ignore rule in some "global" configuration, such as a
   $HOME/.ignore or on the command line.
2. A specific ignore file (like .gitignore) found during directory traversal.

The `IgnoreDir` type handles ignore patterns for any one particular directory
(including "global" ignore patterns), while the `Ignore` type handles a stack
of `IgnoreDir`s for use during directory traversal.
*/

use std::error::Error as StdError;
use std::ffi::OsString;
use std::fmt;
use std::io;
use std::path::{Path, PathBuf};

use gitignore::{self, Gitignore, GitignoreBuilder, Match, Pattern};
use pathutil::{file_name, is_hidden};
use types::Types;

const IGNORE_NAMES: &'static [&'static str] = &[
    ".gitignore",
    ".ignore",
    ".rgignore",
];

/// Represents an error that can occur when parsing a gitignore file.
#[derive(Debug)]
pub enum Error {
    Gitignore(gitignore::Error),
    Io {
        path: PathBuf,
        err: io::Error,
    },
}

impl Error {
    fn from_io<P: AsRef<Path>>(path: P, err: io::Error) -> Error {
        Error::Io { path: path.as_ref().to_path_buf(), err: err }
    }
}

impl StdError for Error {
    fn description(&self) -> &str {
        match *self {
            Error::Gitignore(ref err) => err.description(),
            Error::Io { ref err, .. } => err.description(),
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::Gitignore(ref err) => err.fmt(f),
            Error::Io { ref path, ref err } => {
                write!(f, "{}: {}", path.display(), err)
            }
        }
    }
}

impl From<gitignore::Error> for Error {
    fn from(err: gitignore::Error) -> Error {
        Error::Gitignore(err)
    }
}

/// Ignore represents a collection of ignore patterns organized by directory.
/// In particular, a stack is maintained, where the top of the stack
/// corresponds to the current directory being searched and the bottom of the
/// stack represents the root of a search. Ignore patterns at the top of the
/// stack take precedence over ignore patterns at the bottom of the stack.
pub struct Ignore {
    /// A stack of ignore patterns at each directory level of traversal.
    /// A directory that contributes no ignore patterns is `None`.
    stack: Vec<IgnoreDir>,
    /// A stack of parent directories above the root of the current search.
    parent_stack: Vec<IgnoreDir>,
    /// A set of override globs that are always checked first. A match (whether
    /// it's whitelist or blacklist) trumps anything in stack.
    overrides: Overrides,
    /// A file type matcher.
    types: Types,
    /// Whether to ignore hidden files or not.
    ignore_hidden: bool,
    /// When true, don't look at .gitignore or .agignore files for ignore
    /// rules.
    no_ignore: bool,
}

impl Ignore {
    /// Create an empty set of ignore patterns.
    pub fn new() -> Ignore {
        Ignore {
            stack: vec![],
            parent_stack: vec![],
            overrides: Overrides::new(None),
            types: Types::empty(),
            ignore_hidden: true,
            no_ignore: false,
        }
    }

    /// Set whether hidden files/folders should be ignored (defaults to true).
    pub fn ignore_hidden(&mut self, yes: bool) -> &mut Ignore {
        self.ignore_hidden = yes;
        self
    }

    /// When set, ignore files are ignored.
    pub fn no_ignore(&mut self, yes: bool) -> &mut Ignore {
        self.no_ignore = yes;
        self
    }

    /// Add a set of globs that overrides all other match logic.
    pub fn add_override(&mut self, gi: Gitignore) -> &mut Ignore {
        self.overrides = Overrides::new(Some(gi));
        self
    }

    /// Add a file type matcher. The file type matcher has the lowest
    /// precedence.
    pub fn add_types(&mut self, types: Types) -> &mut Ignore {
        self.types = types;
        self
    }

    /// Push parent directories of `path` on to the stack.
    pub fn push_parents<P: AsRef<Path>>(
        &mut self,
        path: P,
    ) -> Result<(), Error> {
        let path = try!(path.as_ref().canonicalize().map_err(|err| {
            Error::from_io(path.as_ref(), err)
        }));
        let mut path = &*path;
        let mut saw_git = path.join(".git").is_dir();
        let mut ignore_names = IGNORE_NAMES.to_vec();
        let mut ignore_dir_results = vec![];
        while let Some(parent) = path.parent() {
            if self.no_ignore {
                ignore_dir_results.push(Ok(IgnoreDir::empty(parent)));
            } else {
                if saw_git {
                    ignore_names.retain(|&name| name != ".gitignore");
                } else {
                    saw_git = parent.join(".git").is_dir();
                }
                let ignore_dir_result =
                    IgnoreDir::with_ignore_names(parent, ignore_names.iter());
                ignore_dir_results.push(ignore_dir_result);
            }
            path = parent;
        }

        for ignore_dir_result in ignore_dir_results.into_iter().rev() {
            self.parent_stack.push(try!(ignore_dir_result));
        }
        Ok(())
    }

    /// Add a directory to the stack.
    ///
    /// Note that even if this returns an error, the directory is added to the
    /// stack (and therefore should be popped).
    pub fn push<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Error> {
        if self.no_ignore {
            self.stack.push(IgnoreDir::empty(path));
            return Ok(());
        }
        self.push_ignore_dir(IgnoreDir::new(path))
    }

    /// Pushes the result of building a directory matcher on to the stack.
    ///
    /// If the result given contains an error, then it is returned.
    pub fn push_ignore_dir(
        &mut self,
        result: Result<IgnoreDir, Error>,
    ) -> Result<(), Error> {
        match result {
            Ok(id) => {
                self.stack.push(id);
                Ok(())
            }
            Err(err) => {
                // Don't leave the stack in an inconsistent state.
                self.stack.push(IgnoreDir::empty("error"));
                Err(err)
            }
        }
    }

    /// Pop a directory from the stack.
    ///
    /// This panics if the stack is empty.
    pub fn pop(&mut self) {
        self.stack.pop().expect("non-empty stack");
    }

    /// Returns true if and only if the given file path should be ignored.
    pub fn ignored<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> bool {
        let path = path.as_ref();
        let mat = self.overrides.matched(path, is_dir);
        if let Some(is_ignored) = self.ignore_match(path, mat) {
            return is_ignored;
        }
        if self.ignore_hidden && is_hidden(&path) {
            debug!("{} ignored because it is hidden", path.display());
            return true;
        }
        if !self.no_ignore {
            for id in self.stack.iter().rev() {
                let mat = id.matched(path, is_dir);
                if let Some(is_ignored) = self.ignore_match(path, mat) {
                    if is_ignored {
                        return true;
                    }
                    // If this path is whitelisted by an ignore, then
                    // fallthrough and let the file type matcher have a say.
                    break;
                }
            }
            let mut path = path.to_path_buf();
            for id in self.parent_stack.iter().rev() {
                if let Some(ref dirname) = id.name {
                    path = Path::new(dirname).join(path);
                }
                let mat = id.matched(&*path, is_dir);
                if let Some(is_ignored) = self.ignore_match(&*path, mat) {
                    if is_ignored {
                        return true;
                    }
                    // If this path is whitelisted by an ignore, then
                    // fallthrough and let the file type matcher have a say.
                    break;
                }
            }
        }
        let mat = self.types.matched(path, is_dir);
        if let Some(is_ignored) = self.ignore_match(path, mat) {
            return is_ignored;
        }
        false
    }

    /// Returns true if the given match says the given pattern should be
    /// ignored or false if the given pattern should be explicitly whitelisted.
    /// Returns None otherwise.
    pub fn ignore_match<P: AsRef<Path>>(
        &self,
        path: P,
        mat: Match,
    ) -> Option<bool> {
        let path = path.as_ref();
        match mat {
            Match::Whitelist(ref pat) => {
                debug!("{} whitelisted by {:?}", path.display(), pat);
                Some(false)
            }
            Match::Ignored(ref pat) => {
                debug!("{} ignored by {:?}", path.display(), pat);
                Some(true)
            }
            Match::None => None,
        }
    }
}

/// IgnoreDir represents a set of ignore patterns retrieved from a single
/// directory.
#[derive(Debug)]
pub struct IgnoreDir {
    /// The path to this directory as given.
    path: PathBuf,
    /// The directory name, if one exists.
    name: Option<OsString>,
    /// A single accumulation of glob patterns for this directory, matched
    /// using gitignore semantics.
    ///
    /// This will include patterns from rgignore as well. The patterns are
    /// ordered so that precedence applies automatically (e.g., rgignore
    /// patterns procede gitignore patterns).
    gi: Option<Gitignore>,
    // TODO(burntsushi): Matching other types of glob patterns that don't
    // conform to gitignore will probably require refactoring this approach.
}

impl IgnoreDir {
    /// Create a new matcher for the given directory.
    ///
    /// If no ignore glob patterns could be found in the directory then `None`
    /// is returned.
    pub fn new<P: AsRef<Path>>(path: P) -> Result<IgnoreDir, Error> {
        IgnoreDir::with_ignore_names(path, IGNORE_NAMES.iter())
    }

    /// Create a new IgnoreDir that never matches anything with the given path.
    pub fn empty<P: AsRef<Path>>(path: P) -> IgnoreDir {
        IgnoreDir {
            path: path.as_ref().to_path_buf(),
            name: file_name(path.as_ref()).map(|s| s.to_os_string()),
            gi: None,
        }
    }

    /// Create a new matcher for the given directory using only the ignore
    /// patterns found in the file names given.
    ///
    /// If no ignore glob patterns could be found in the directory then `None`
    /// is returned.
    ///
    /// Note that the order of the names given is meaningful. Names appearing
    /// later in the list have precedence over names appearing earlier in the
    /// list.
    pub fn with_ignore_names<P: AsRef<Path>, S, I>(
        path: P,
        names: I,
    ) -> Result<IgnoreDir, Error>
    where P: AsRef<Path>, S: AsRef<str>, I: Iterator<Item=S> {
        let mut id = IgnoreDir::empty(path);
        let mut ok = false;
        let mut builder = GitignoreBuilder::new(&id.path);
        // The ordering here is important. Later globs have higher precedence.
        for name in names {
            ok = builder.add_path(id.path.join(name.as_ref())).is_ok() || ok;
        }
        if !ok {
            return Ok(id);
        }
        id.gi = Some(try!(builder.build()));
        Ok(id)
    }

    /// Returns true if and only if the given file path should be ignored
    /// according to the globs in this directory. `is_dir` should be true if
    /// the path refers to a directory and false otherwise.
    ///
    /// Before matching path, its prefix (as determined by a common suffix
    /// of this directory) is stripped. If there is
    /// no common suffix/prefix overlap, then path is assumed to reside
    /// directly in this directory.
    ///
    /// If the given path has a `./` prefix then it is stripped before
    /// matching.
    pub fn matched<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> Match {
        self.gi.as_ref()
            .map(|gi| gi.matched(path, is_dir))
            .unwrap_or(Match::None)
    }
}

/// Manages a set of overrides provided explicitly by the end user.
struct Overrides {
    gi: Option<Gitignore>,
    unmatched_pat: Pattern,
}

impl Overrides {
    /// Creates a new set of overrides from the gitignore matcher provided.
    /// If no matcher is provided, then the resulting overrides have no effect.
    fn new(gi: Option<Gitignore>) -> Overrides {
        Overrides {
            gi: gi,
            unmatched_pat: Pattern {
                from: Path::new("<argv>").to_path_buf(),
                original: "<none>".to_string(),
                pat: "<none>".to_string(),
                whitelist: false,
                only_dir: false,
            },
        }
    }

    /// Returns a match for the given path against this set of overrides.
    ///
    /// If there are no overrides, then this always returns Match::None.
    ///
    /// If there is at least one positive override, then this never returns
    /// Match::None (and interpreting non-matches as ignored) unless is_dir
    /// is true.
    pub fn matched<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> Match {
        let path = path.as_ref();
        self.gi.as_ref()
            .map(|gi| {
                let mat = gi.matched_stripped(path, is_dir).invert();
                if mat.is_none() && !is_dir {
                    if gi.num_ignores() > 0 {
                        return Match::Ignored(&self.unmatched_pat);
                    }
                }
                mat
            })
            .unwrap_or(Match::None)
    }
}

#[cfg(test)]
mod tests {
    use std::path::Path;
    use gitignore::GitignoreBuilder;
    use super::IgnoreDir;

    macro_rules! ignored_dir {
        ($name:ident, $root:expr, $gi:expr, $xi:expr, $path:expr) => {
            #[test]
            fn $name() {
                let mut builder = GitignoreBuilder::new(&$root);
                builder.add_str($gi).unwrap();
                builder.add_str($xi).unwrap();
                let gi = builder.build().unwrap();
                let id = IgnoreDir {
                    path: Path::new($root).to_path_buf(),
                    name: Path::new($root).file_name().map(|s| {
                        s.to_os_string()
                    }),
                    gi: Some(gi),
                };
                assert!(id.matched($path, false).is_ignored());
            }
        };
    }

    macro_rules! not_ignored_dir {
        ($name:ident, $root:expr, $gi:expr, $xi:expr, $path:expr) => {
            #[test]
            fn $name() {
                let mut builder = GitignoreBuilder::new(&$root);
                builder.add_str($gi).unwrap();
                builder.add_str($xi).unwrap();
                let gi = builder.build().unwrap();
                let id = IgnoreDir {
                    path: Path::new($root).to_path_buf(),
                    name: Path::new($root).file_name().map(|s| {
                        s.to_os_string()
                    }),
                    gi: Some(gi),
                };
                assert!(!id.matched($path, false).is_ignored());
            }
        };
    }

    const ROOT: &'static str = "/home/foobar/rust/rg";

    ignored_dir!(id1, ROOT, "src/main.rs", "", "src/main.rs");
    ignored_dir!(id2, ROOT, "", "src/main.rs", "src/main.rs");
    ignored_dir!(id3, ROOT, "!src/main.rs", "*.rs", "src/main.rs");

    not_ignored_dir!(idnot1, ROOT, "*.rs", "!src/main.rs", "src/main.rs");
}
<commit_msg>Fix whitelisting precedence.<commit_after>/*!
The ignore module is responsible for managing the state required to determine
whether a *single* file path should be searched or not.

In general, there are two ways to ignore a particular file:

1. Specify an ignore rule in some "global" configuration, such as a
   $HOME/.ignore or on the command line.
2. A specific ignore file (like .gitignore) found during directory traversal.

The `IgnoreDir` type handles ignore patterns for any one particular directory
(including "global" ignore patterns), while the `Ignore` type handles a stack
of `IgnoreDir`s for use during directory traversal.
*/

use std::error::Error as StdError;
use std::ffi::OsString;
use std::fmt;
use std::io;
use std::path::{Path, PathBuf};

use gitignore::{self, Gitignore, GitignoreBuilder, Match, Pattern};
use pathutil::{file_name, is_hidden};
use types::Types;

const IGNORE_NAMES: &'static [&'static str] = &[
    ".gitignore",
    ".ignore",
    ".rgignore",
];

/// Represents an error that can occur when parsing a gitignore file.
#[derive(Debug)]
pub enum Error {
    Gitignore(gitignore::Error),
    Io {
        path: PathBuf,
        err: io::Error,
    },
}

impl Error {
    fn from_io<P: AsRef<Path>>(path: P, err: io::Error) -> Error {
        Error::Io { path: path.as_ref().to_path_buf(), err: err }
    }
}

impl StdError for Error {
    fn description(&self) -> &str {
        match *self {
            Error::Gitignore(ref err) => err.description(),
            Error::Io { ref err, .. } => err.description(),
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Error::Gitignore(ref err) => err.fmt(f),
            Error::Io { ref path, ref err } => {
                write!(f, "{}: {}", path.display(), err)
            }
        }
    }
}

impl From<gitignore::Error> for Error {
    fn from(err: gitignore::Error) -> Error {
        Error::Gitignore(err)
    }
}

/// Ignore represents a collection of ignore patterns organized by directory.
/// In particular, a stack is maintained, where the top of the stack
/// corresponds to the current directory being searched and the bottom of the
/// stack represents the root of a search. Ignore patterns at the top of the
/// stack take precedence over ignore patterns at the bottom of the stack.
pub struct Ignore {
    /// A stack of ignore patterns at each directory level of traversal.
    /// A directory that contributes no ignore patterns is `None`.
    stack: Vec<IgnoreDir>,
    /// A stack of parent directories above the root of the current search.
    parent_stack: Vec<IgnoreDir>,
    /// A set of override globs that are always checked first. A match (whether
    /// it's whitelist or blacklist) trumps anything in stack.
    overrides: Overrides,
    /// A file type matcher.
    types: Types,
    /// Whether to ignore hidden files or not.
    ignore_hidden: bool,
    /// When true, don't look at .gitignore or .agignore files for ignore
    /// rules.
    no_ignore: bool,
}

impl Ignore {
    /// Create an empty set of ignore patterns.
    pub fn new() -> Ignore {
        Ignore {
            stack: vec![],
            parent_stack: vec![],
            overrides: Overrides::new(None),
            types: Types::empty(),
            ignore_hidden: true,
            no_ignore: false,
        }
    }

    /// Set whether hidden files/folders should be ignored (defaults to true).
    pub fn ignore_hidden(&mut self, yes: bool) -> &mut Ignore {
        self.ignore_hidden = yes;
        self
    }

    /// When set, ignore files are ignored.
    pub fn no_ignore(&mut self, yes: bool) -> &mut Ignore {
        self.no_ignore = yes;
        self
    }

    /// Add a set of globs that overrides all other match logic.
    pub fn add_override(&mut self, gi: Gitignore) -> &mut Ignore {
        self.overrides = Overrides::new(Some(gi));
        self
    }

    /// Add a file type matcher. The file type matcher has the lowest
    /// precedence.
    pub fn add_types(&mut self, types: Types) -> &mut Ignore {
        self.types = types;
        self
    }

    /// Push parent directories of `path` on to the stack.
    pub fn push_parents<P: AsRef<Path>>(
        &mut self,
        path: P,
    ) -> Result<(), Error> {
        let path = try!(path.as_ref().canonicalize().map_err(|err| {
            Error::from_io(path.as_ref(), err)
        }));
        let mut path = &*path;
        let mut saw_git = path.join(".git").is_dir();
        let mut ignore_names = IGNORE_NAMES.to_vec();
        let mut ignore_dir_results = vec![];
        while let Some(parent) = path.parent() {
            if self.no_ignore {
                ignore_dir_results.push(Ok(IgnoreDir::empty(parent)));
            } else {
                if saw_git {
                    ignore_names.retain(|&name| name != ".gitignore");
                } else {
                    saw_git = parent.join(".git").is_dir();
                }
                let ignore_dir_result =
                    IgnoreDir::with_ignore_names(parent, ignore_names.iter());
                ignore_dir_results.push(ignore_dir_result);
            }
            path = parent;
        }

        for ignore_dir_result in ignore_dir_results.into_iter().rev() {
            self.parent_stack.push(try!(ignore_dir_result));
        }
        Ok(())
    }

    /// Add a directory to the stack.
    ///
    /// Note that even if this returns an error, the directory is added to the
    /// stack (and therefore should be popped).
    pub fn push<P: AsRef<Path>>(&mut self, path: P) -> Result<(), Error> {
        if self.no_ignore {
            self.stack.push(IgnoreDir::empty(path));
            return Ok(());
        }
        self.push_ignore_dir(IgnoreDir::new(path))
    }

    /// Pushes the result of building a directory matcher on to the stack.
    ///
    /// If the result given contains an error, then it is returned.
    pub fn push_ignore_dir(
        &mut self,
        result: Result<IgnoreDir, Error>,
    ) -> Result<(), Error> {
        match result {
            Ok(id) => {
                self.stack.push(id);
                Ok(())
            }
            Err(err) => {
                // Don't leave the stack in an inconsistent state.
                self.stack.push(IgnoreDir::empty("error"));
                Err(err)
            }
        }
    }

    /// Pop a directory from the stack.
    ///
    /// This panics if the stack is empty.
    pub fn pop(&mut self) {
        self.stack.pop().expect("non-empty stack");
    }

    /// Returns true if and only if the given file path should be ignored.
    pub fn ignored<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> bool {
        let path = path.as_ref();
        let mat = self.overrides.matched(path, is_dir);
        if let Some(is_ignored) = self.ignore_match(path, mat) {
            return is_ignored;
        }
        if self.ignore_hidden && is_hidden(&path) {
            debug!("{} ignored because it is hidden", path.display());
            return true;
        }
        if !self.no_ignore {
            let mut whitelisted = false;
            for id in self.stack.iter().rev() {
                let mat = id.matched(path, is_dir);
                // println!("path: {}, mat: {:?}, id: {:?}",
                         // path.display(), mat, id);
                if let Some(is_ignored) = self.ignore_match(path, mat) {
                    if is_ignored {
                        return true;
                    }
                    // If this path is whitelisted by an ignore, then
                    // fallthrough and let the file type matcher have a say.
                    whitelisted = true;
                    break;
                }
            }
            // If the file has been whitelisted, then we have to stop checking
            // parent directories. The only thing that can override a whitelist
            // at this point is a type filter.
            if !whitelisted {
                let mut path = path.to_path_buf();
                for id in self.parent_stack.iter().rev() {
                    if let Some(ref dirname) = id.name {
                        path = Path::new(dirname).join(path);
                    }
                    let mat = id.matched(&*path, is_dir);
                    if let Some(is_ignored) = self.ignore_match(&*path, mat) {
                        if is_ignored {
                            return true;
                        }
                        // If this path is whitelisted by an ignore, then
                        // fallthrough and let the file type matcher have a
                        // say.
                        break;
                    }
                }
            }
        }
        let mat = self.types.matched(path, is_dir);
        if let Some(is_ignored) = self.ignore_match(path, mat) {
            return is_ignored;
        }
        false
    }

    /// Returns true if the given match says the given pattern should be
    /// ignored or false if the given pattern should be explicitly whitelisted.
    /// Returns None otherwise.
    pub fn ignore_match<P: AsRef<Path>>(
        &self,
        path: P,
        mat: Match,
    ) -> Option<bool> {
        let path = path.as_ref();
        match mat {
            Match::Whitelist(ref pat) => {
                debug!("{} whitelisted by {:?}", path.display(), pat);
                Some(false)
            }
            Match::Ignored(ref pat) => {
                debug!("{} ignored by {:?}", path.display(), pat);
                Some(true)
            }
            Match::None => None,
        }
    }
}

/// IgnoreDir represents a set of ignore patterns retrieved from a single
/// directory.
#[derive(Debug)]
pub struct IgnoreDir {
    /// The path to this directory as given.
    path: PathBuf,
    /// The directory name, if one exists.
    name: Option<OsString>,
    /// A single accumulation of glob patterns for this directory, matched
    /// using gitignore semantics.
    ///
    /// This will include patterns from rgignore as well. The patterns are
    /// ordered so that precedence applies automatically (e.g., rgignore
    /// patterns procede gitignore patterns).
    gi: Option<Gitignore>,
    // TODO(burntsushi): Matching other types of glob patterns that don't
    // conform to gitignore will probably require refactoring this approach.
}

impl IgnoreDir {
    /// Create a new matcher for the given directory.
    ///
    /// If no ignore glob patterns could be found in the directory then `None`
    /// is returned.
    pub fn new<P: AsRef<Path>>(path: P) -> Result<IgnoreDir, Error> {
        IgnoreDir::with_ignore_names(path, IGNORE_NAMES.iter())
    }

    /// Create a new IgnoreDir that never matches anything with the given path.
    pub fn empty<P: AsRef<Path>>(path: P) -> IgnoreDir {
        IgnoreDir {
            path: path.as_ref().to_path_buf(),
            name: file_name(path.as_ref()).map(|s| s.to_os_string()),
            gi: None,
        }
    }

    /// Create a new matcher for the given directory using only the ignore
    /// patterns found in the file names given.
    ///
    /// If no ignore glob patterns could be found in the directory then `None`
    /// is returned.
    ///
    /// Note that the order of the names given is meaningful. Names appearing
    /// later in the list have precedence over names appearing earlier in the
    /// list.
    pub fn with_ignore_names<P: AsRef<Path>, S, I>(
        path: P,
        names: I,
    ) -> Result<IgnoreDir, Error>
    where P: AsRef<Path>, S: AsRef<str>, I: Iterator<Item=S> {
        let mut id = IgnoreDir::empty(path);
        let mut ok = false;
        let mut builder = GitignoreBuilder::new(&id.path);
        // The ordering here is important. Later globs have higher precedence.
        for name in names {
            ok = builder.add_path(id.path.join(name.as_ref())).is_ok() || ok;
        }
        if !ok {
            return Ok(id);
        }
        id.gi = Some(try!(builder.build()));
        Ok(id)
    }

    /// Returns true if and only if the given file path should be ignored
    /// according to the globs in this directory. `is_dir` should be true if
    /// the path refers to a directory and false otherwise.
    ///
    /// Before matching path, its prefix (as determined by a common suffix
    /// of this directory) is stripped. If there is
    /// no common suffix/prefix overlap, then path is assumed to reside
    /// directly in this directory.
    ///
    /// If the given path has a `./` prefix then it is stripped before
    /// matching.
    pub fn matched<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> Match {
        self.gi.as_ref()
            .map(|gi| gi.matched(path, is_dir))
            .unwrap_or(Match::None)
    }
}

/// Manages a set of overrides provided explicitly by the end user.
struct Overrides {
    gi: Option<Gitignore>,
    unmatched_pat: Pattern,
}

impl Overrides {
    /// Creates a new set of overrides from the gitignore matcher provided.
    /// If no matcher is provided, then the resulting overrides have no effect.
    fn new(gi: Option<Gitignore>) -> Overrides {
        Overrides {
            gi: gi,
            unmatched_pat: Pattern {
                from: Path::new("<argv>").to_path_buf(),
                original: "<none>".to_string(),
                pat: "<none>".to_string(),
                whitelist: false,
                only_dir: false,
            },
        }
    }

    /// Returns a match for the given path against this set of overrides.
    ///
    /// If there are no overrides, then this always returns Match::None.
    ///
    /// If there is at least one positive override, then this never returns
    /// Match::None (and interpreting non-matches as ignored) unless is_dir
    /// is true.
    pub fn matched<P: AsRef<Path>>(&self, path: P, is_dir: bool) -> Match {
        let path = path.as_ref();
        self.gi.as_ref()
            .map(|gi| {
                let mat = gi.matched_stripped(path, is_dir).invert();
                if mat.is_none() && !is_dir {
                    if gi.num_ignores() > 0 {
                        return Match::Ignored(&self.unmatched_pat);
                    }
                }
                mat
            })
            .unwrap_or(Match::None)
    }
}

#[cfg(test)]
mod tests {
    use std::path::Path;
    use gitignore::GitignoreBuilder;
    use super::IgnoreDir;

    macro_rules! ignored_dir {
        ($name:ident, $root:expr, $gi:expr, $xi:expr, $path:expr) => {
            #[test]
            fn $name() {
                let mut builder = GitignoreBuilder::new(&$root);
                builder.add_str($gi).unwrap();
                builder.add_str($xi).unwrap();
                let gi = builder.build().unwrap();
                let id = IgnoreDir {
                    path: Path::new($root).to_path_buf(),
                    name: Path::new($root).file_name().map(|s| {
                        s.to_os_string()
                    }),
                    gi: Some(gi),
                };
                assert!(id.matched($path, false).is_ignored());
            }
        };
    }

    macro_rules! not_ignored_dir {
        ($name:ident, $root:expr, $gi:expr, $xi:expr, $path:expr) => {
            #[test]
            fn $name() {
                let mut builder = GitignoreBuilder::new(&$root);
                builder.add_str($gi).unwrap();
                builder.add_str($xi).unwrap();
                let gi = builder.build().unwrap();
                let id = IgnoreDir {
                    path: Path::new($root).to_path_buf(),
                    name: Path::new($root).file_name().map(|s| {
                        s.to_os_string()
                    }),
                    gi: Some(gi),
                };
                assert!(!id.matched($path, false).is_ignored());
            }
        };
    }

    const ROOT: &'static str = "/home/foobar/rust/rg";

    ignored_dir!(id1, ROOT, "src/main.rs", "", "src/main.rs");
    ignored_dir!(id2, ROOT, "", "src/main.rs", "src/main.rs");
    ignored_dir!(id3, ROOT, "!src/main.rs", "*.rs", "src/main.rs");

    not_ignored_dir!(idnot1, ROOT, "*.rs", "!src/main.rs", "src/main.rs");
}
<|endoftext|>


<commit_before>import T = inst::T;
import cmp::{eq, ord};

export min_value, max_value;
export min, max;
export add, sub, mul, div, rem;
export lt, le, eq, ne, ge, gt;
export is_positive, is_negative;
export is_nonpositive, is_nonnegative;
export range;
export compl;
export to_str, to_str_bytes;
export from_str, from_str_radix, str, parse_buf;
export num, ord, eq, times, timesi;

const min_value: T = 0 as T;
const max_value: T = 0 as T - 1 as T;

pure fn min(&&x: T, &&y: T) -> T { if x < y { x } else { y } }
pure fn max(&&x: T, &&y: T) -> T { if x > y { x } else { y } }

pure fn add(&&x: T, &&y: T) -> T { x + y }
pure fn sub(&&x: T, &&y: T) -> T { x - y }
pure fn mul(&&x: T, &&y: T) -> T { x * y }
pure fn div(&&x: T, &&y: T) -> T { x / y }
pure fn rem(&&x: T, &&y: T) -> T { x % y }

pure fn lt(&&x: T, &&y: T) -> bool { x < y }
pure fn le(&&x: T, &&y: T) -> bool { x <= y }
pure fn eq(&&x: T, &&y: T) -> bool { x == y }
pure fn ne(&&x: T, &&y: T) -> bool { x != y }
pure fn ge(&&x: T, &&y: T) -> bool { x >= y }
pure fn gt(&&x: T, &&y: T) -> bool { x > y }

pure fn is_positive(x: T) -> bool { x > 0 as T }
pure fn is_negative(x: T) -> bool { x < 0 as T }
pure fn is_nonpositive(x: T) -> bool { x <= 0 as T }
pure fn is_nonnegative(x: T) -> bool { x >= 0 as T }

#[inline(always)]
/// Iterate over the range [`lo`..`hi`)
fn range(lo: T, hi: T, it: fn(T) -> bool) {
    let mut i = lo;
    while i < hi {
        if !it(i) { break }
        i += 1 as T;
    }
}

/// Computes the bitwise complement
pure fn compl(i: T) -> T {
    max_value ^ i
}

impl ord of ord for T {
    fn lt(&&other: T) -> bool {
        ret self < other;
    }
}

impl eq of eq for T {
    fn eq(&&other: T) -> bool {
        ret self == other;
    }
}

impl num of num::num for T {
    fn add(&&other: T)    -> T { ret self + other; }
    fn sub(&&other: T)    -> T { ret self - other; }
    fn mul(&&other: T)    -> T { ret self * other; }
    fn div(&&other: T)    -> T { ret self / other; }
    fn modulo(&&other: T) -> T { ret self % other; }
    fn neg()              -> T { ret -self;        }

    fn to_int()         -> int { ret self as int; }
    fn from_int(n: int) -> T   { ret n as T;      }
}

/**
 * Parse a buffer of bytes
 *
 * # Arguments
 *
 * * buf - A byte buffer
 * * radix - The base of the number
 *
 * # Failure
 *
 * `buf` must not be empty
 */
fn parse_buf(buf: ~[u8], radix: uint) -> option<T> {
    if vec::len(buf) == 0u { ret none; }
    let mut i = vec::len(buf) - 1u;
    let mut power = 1u as T;
    let mut n = 0u as T;
    loop {
        alt char::to_digit(buf[i] as char, radix) {
          some(d) { n += d as T * power; }
          none { ret none; }
        }
        power *= radix as T;
        if i == 0u { ret some(n); }
        i -= 1u;
    };
}

impl times of iter::times for T {
    #[inline(always)]
    #[doc = "A convenience form for basic iteration. Given a variable `x` \
        of any numeric type, the expression `for x.times { /* anything */ }` \
        will execute the given function exactly x times. If we assume that \
        `x` is an int, this is functionally equivalent to \
        `for int::range(0, x) |_i| { /* anything */ }`."]
    fn times(it: fn() -> bool) {
        let mut i = self;
        while i > 0 {
            if !it() { break }
            i -= 1;
        }
    }
}

impl timesi of iter::timesi for T {
    #[inline(always)]
    /// Like `times`, but with an index, `eachi`-style.
    fn timesi(it: fn(uint) -> bool) {
        let slf = self as uint;
        let mut i = 0u;
        while i < slf {
            if !it(i) { break }
            i += 1u;
        }
    }
}

/// Parse a string to an int
fn from_str(s: ~str) -> option<T> { parse_buf(str::bytes(s), 10u) }

/// Parse a string as an unsigned integer.
fn from_str_radix(buf: ~str, radix: u64) -> option<u64> {
    if str::len(buf) == 0u { ret none; }
    let mut i = str::len(buf) - 1u;
    let mut power = 1u64, n = 0u64;
    loop {
        alt char::to_digit(buf[i] as char, radix as uint) {
          some(d) { n += d as u64 * power; }
          none { ret none; }
        }
        power *= radix;
        if i == 0u { ret some(n); }
        i -= 1u;
    };
}

/**
 * Convert to a string in a given base
 *
 * # Failure
 *
 * Fails if `radix` < 2 or `radix` > 16
 */
fn to_str(num: T, radix: uint) -> ~str {
    do to_str_bytes(false, num, radix) |slice| {
        do vec::as_buf(slice) |p, len| {
            unsafe { str::unsafe::from_buf_len(p, len) }
        }
    }
}

/// Low-level helper routine for string conversion.
fn to_str_bytes<U>(neg: bool, num: T, radix: uint,
                   f: fn(v: &[u8]) -> U) -> U {

    #[inline(always)]
    fn digit(n: T) -> u8 {
        if n <= 9u as T {
            n as u8 + '0' as u8
        } else if n <= 15u as T {
            (n - 10 as T) as u8 + 'a' as u8
        } else {
            fail;
        }
    }

    assert (1u < radix && radix <= 16u);

    // Enough room to hold any number in any radix.
    // Worst case: 64-bit number, binary-radix, with
    // a leading negative sign = 65 bytes.
    let buf : [mut u8]/65 =
        [mut
         0u8,0u8,0u8,0u8,0u8, 0u8,0u8,0u8,0u8,0u8,
         0u8,0u8,0u8,0u8,0u8, 0u8,0u8,0u8,0u8,0u8,

         0u8,0u8,0u8,0u8,0u8, 0u8,0u8,0u8,0u8,0u8,
         0u8,0u8,0u8,0u8,0u8, 0u8,0u8,0u8,0u8,0u8,

         0u8,0u8,0u8,0u8,0u8, 0u8,0u8,0u8,0u8,0u8,
         0u8,0u8,0u8,0u8,0u8, 0u8,0u8,0u8,0u8,0u8,

         0u8,0u8,0u8,0u8,0u8
         ]/65;

    // FIXME (#2649): post-snapshot, you can do this without the raw
    // pointers and unsafe bits, and the codegen will prove it's all
    // in-bounds, no extra cost.

    unsafe {
        do vec::as_buf(buf) |p, len| {
            let mp = p as *mut u8;
            let mut i = len;
            let mut n = num;
            let radix = radix as T;
            loop {
                i -= 1u;
                assert 0u < i && i < len;
                *ptr::mut_offset(mp, i) = digit(n % radix);
                n /= radix;
                if n == 0 as T { break; }
            }

            assert 0u < i && i < len;

            if neg {
                i -= 1u;
                *ptr::mut_offset(mp, i) = '-' as u8;
            }

            vec::unsafe::form_slice(ptr::offset(p, i),
                                    len - i, f)
        }
    }
}

/// Convert to a string
fn str(i: T) -> ~str { ret to_str(i, 10u); }

#[test]
fn test_to_str() {
    assert to_str(0 as T, 10u) == ~"0";
    assert to_str(1 as T, 10u) == ~"1";
    assert to_str(2 as T, 10u) == ~"2";
    assert to_str(11 as T, 10u) == ~"11";
    assert to_str(11 as T, 16u) == ~"b";
    assert to_str(255 as T, 16u) == ~"ff";
    assert to_str(0xff as T, 10u) == ~"255";
}

#[test]
#[ignore]
fn test_from_str() {
    assert from_str(~"0") == some(0u as T);
    assert from_str(~"3") == some(3u as T);
    assert from_str(~"10") == some(10u as T);
    assert from_str(~"123456789") == some(123456789u as T);
    assert from_str(~"00100") == some(100u as T);

    assert from_str(~"") == none;
    assert from_str(~" ") == none;
    assert from_str(~"x") == none;
}

#[test]
#[ignore]
fn test_parse_buf() {
    import str::bytes;
    assert parse_buf(bytes(~"123"), 10u) == some(123u as T);
    assert parse_buf(bytes(~"1001"), 2u) == some(9u as T);
    assert parse_buf(bytes(~"123"), 8u) == some(83u as T);
    assert parse_buf(bytes(~"123"), 16u) == some(291u as T);
    assert parse_buf(bytes(~"ffff"), 16u) == some(65535u as T);
    assert parse_buf(bytes(~"z"), 36u) == some(35u as T);

    assert parse_buf(str::bytes(~"Z"), 10u) == none;
    assert parse_buf(str::bytes(~"_"), 2u) == none;
}

#[test]
#[should_fail]
#[ignore(cfg(windows))]
fn to_str_radix1() {
    uint::to_str(100u, 1u);
}

#[test]
#[should_fail]
#[ignore(cfg(windows))]
fn to_str_radix17() {
    uint::to_str(100u, 17u);
}

#[test]
fn test_times() {
    import iter::times;
    let ten = 10 as T;
    let mut accum = 0;
    for ten.times { accum += 1; }
    assert (accum == 10);
}
<commit_msg>range() is pure<commit_after>import T = inst::T;
import cmp::{eq, ord};

export min_value, max_value;
export min, max;
export add, sub, mul, div, rem;
export lt, le, eq, ne, ge, gt;
export is_positive, is_negative;
export is_nonpositive, is_nonnegative;
export range;
export compl;
export to_str, to_str_bytes;
export from_str, from_str_radix, str, parse_buf;
export num, ord, eq, times, timesi;

const min_value: T = 0 as T;
const max_value: T = 0 as T - 1 as T;

pure fn min(&&x: T, &&y: T) -> T { if x < y { x } else { y } }
pure fn max(&&x: T, &&y: T) -> T { if x > y { x } else { y } }

pure fn add(&&x: T, &&y: T) -> T { x + y }
pure fn sub(&&x: T, &&y: T) -> T { x - y }
pure fn mul(&&x: T, &&y: T) -> T { x * y }
pure fn div(&&x: T, &&y: T) -> T { x / y }
pure fn rem(&&x: T, &&y: T) -> T { x % y }

pure fn lt(&&x: T, &&y: T) -> bool { x < y }
pure fn le(&&x: T, &&y: T) -> bool { x <= y }
pure fn eq(&&x: T, &&y: T) -> bool { x == y }
pure fn ne(&&x: T, &&y: T) -> bool { x != y }
pure fn ge(&&x: T, &&y: T) -> bool { x >= y }
pure fn gt(&&x: T, &&y: T) -> bool { x > y }

pure fn is_positive(x: T) -> bool { x > 0 as T }
pure fn is_negative(x: T) -> bool { x < 0 as T }
pure fn is_nonpositive(x: T) -> bool { x <= 0 as T }
pure fn is_nonnegative(x: T) -> bool { x >= 0 as T }

#[inline(always)]
/// Iterate over the range [`lo`..`hi`)
pure fn range(lo: T, hi: T, it: fn(T) -> bool) {
    let mut i = lo;
    while i < hi {
        if !it(i) { break }
        i += 1 as T;
    }
}

/// Computes the bitwise complement
pure fn compl(i: T) -> T {
    max_value ^ i
}

impl ord of ord for T {
    fn lt(&&other: T) -> bool {
        ret self < other;
    }
}

impl eq of eq for T {
    fn eq(&&other: T) -> bool {
        ret self == other;
    }
}

impl num of num::num for T {
    fn add(&&other: T)    -> T { ret self + other; }
    fn sub(&&other: T)    -> T { ret self - other; }
    fn mul(&&other: T)    -> T { ret self * other; }
    fn div(&&other: T)    -> T { ret self / other; }
    fn modulo(&&other: T) -> T { ret self % other; }
    fn neg()              -> T { ret -self;        }

    fn to_int()         -> int { ret self as int; }
    fn from_int(n: int) -> T   { ret n as T;      }
}

/**
 * Parse a buffer of bytes
 *
 * # Arguments
 *
 * * buf - A byte buffer
 * * radix - The base of the number
 *
 * # Failure
 *
 * `buf` must not be empty
 */
fn parse_buf(buf: ~[u8], radix: uint) -> option<T> {
    if vec::len(buf) == 0u { ret none; }
    let mut i = vec::len(buf) - 1u;
    let mut power = 1u as T;
    let mut n = 0u as T;
    loop {
        alt char::to_digit(buf[i] as char, radix) {
          some(d) { n += d as T * power; }
          none { ret none; }
        }
        power *= radix as T;
        if i == 0u { ret some(n); }
        i -= 1u;
    };
}

impl times of iter::times for T {
    #[inline(always)]
    #[doc = "A convenience form for basic iteration. Given a variable `x` \
        of any numeric type, the expression `for x.times { /* anything */ }` \
        will execute the given function exactly x times. If we assume that \
        `x` is an int, this is functionally equivalent to \
        `for int::range(0, x) |_i| { /* anything */ }`."]
    fn times(it: fn() -> bool) {
        let mut i = self;
        while i > 0 {
            if !it() { break }
            i -= 1;
        }
    }
}

impl timesi of iter::timesi for T {
    #[inline(always)]
    /// Like `times`, but with an index, `eachi`-style.
    fn timesi(it: fn(uint) -> bool) {
        let slf = self as uint;
        let mut i = 0u;
        while i < slf {
            if !it(i) { break }
            i += 1u;
        }
    }
}

/// Parse a string to an int
fn from_str(s: ~str) -> option<T> { parse_buf(str::bytes(s), 10u) }

/// Parse a string as an unsigned integer.
fn from_str_radix(buf: ~str, radix: u64) -> option<u64> {
    if str::len(buf) == 0u { ret none; }
    let mut i = str::len(buf) - 1u;
    let mut power = 1u64, n = 0u64;
    loop {
        alt char::to_digit(buf[i] as char, radix as uint) {
          some(d) { n += d as u64 * power; }
          none { ret none; }
        }
        power *= radix;
        if i == 0u { ret some(n); }
        i -= 1u;
    };
}

/**
 * Convert to a string in a given base
 *
 * # Failure
 *
 * Fails if `radix` < 2 or `radix` > 16
 */
fn to_str(num: T, radix: uint) -> ~str {
    do to_str_bytes(false, num, radix) |slice| {
        do vec::as_buf(slice) |p, len| {
            unsafe { str::unsafe::from_buf_len(p, len) }
        }
    }
}

/// Low-level helper routine for string conversion.
fn to_str_bytes<U>(neg: bool, num: T, radix: uint,
                   f: fn(v: &[u8]) -> U) -> U {

    #[inline(always)]
    fn digit(n: T) -> u8 {
        if n <= 9u as T {
            n as u8 + '0' as u8
        } else if n <= 15u as T {
            (n - 10 as T) as u8 + 'a' as u8
        } else {
            fail;
        }
    }

    assert (1u < radix && radix <= 16u);

    // Enough room to hold any number in any radix.
    // Worst case: 64-bit number, binary-radix, with
    // a leading negative sign = 65 bytes.
    let buf : [mut u8]/65 =
        [mut
         0u8,0u8,0u8,0u8,0u8, 0u8,0u8,0u8,0u8,0u8,
         0u8,0u8,0u8,0u8,0u8, 0u8,0u8,0u8,0u8,0u8,

         0u8,0u8,0u8,0u8,0u8, 0u8,0u8,0u8,0u8,0u8,
         0u8,0u8,0u8,0u8,0u8, 0u8,0u8,0u8,0u8,0u8,

         0u8,0u8,0u8,0u8,0u8, 0u8,0u8,0u8,0u8,0u8,
         0u8,0u8,0u8,0u8,0u8, 0u8,0u8,0u8,0u8,0u8,

         0u8,0u8,0u8,0u8,0u8
         ]/65;

    // FIXME (#2649): post-snapshot, you can do this without the raw
    // pointers and unsafe bits, and the codegen will prove it's all
    // in-bounds, no extra cost.

    unsafe {
        do vec::as_buf(buf) |p, len| {
            let mp = p as *mut u8;
            let mut i = len;
            let mut n = num;
            let radix = radix as T;
            loop {
                i -= 1u;
                assert 0u < i && i < len;
                *ptr::mut_offset(mp, i) = digit(n % radix);
                n /= radix;
                if n == 0 as T { break; }
            }

            assert 0u < i && i < len;

            if neg {
                i -= 1u;
                *ptr::mut_offset(mp, i) = '-' as u8;
            }

            vec::unsafe::form_slice(ptr::offset(p, i),
                                    len - i, f)
        }
    }
}

/// Convert to a string
fn str(i: T) -> ~str { ret to_str(i, 10u); }

#[test]
fn test_to_str() {
    assert to_str(0 as T, 10u) == ~"0";
    assert to_str(1 as T, 10u) == ~"1";
    assert to_str(2 as T, 10u) == ~"2";
    assert to_str(11 as T, 10u) == ~"11";
    assert to_str(11 as T, 16u) == ~"b";
    assert to_str(255 as T, 16u) == ~"ff";
    assert to_str(0xff as T, 10u) == ~"255";
}

#[test]
#[ignore]
fn test_from_str() {
    assert from_str(~"0") == some(0u as T);
    assert from_str(~"3") == some(3u as T);
    assert from_str(~"10") == some(10u as T);
    assert from_str(~"123456789") == some(123456789u as T);
    assert from_str(~"00100") == some(100u as T);

    assert from_str(~"") == none;
    assert from_str(~" ") == none;
    assert from_str(~"x") == none;
}

#[test]
#[ignore]
fn test_parse_buf() {
    import str::bytes;
    assert parse_buf(bytes(~"123"), 10u) == some(123u as T);
    assert parse_buf(bytes(~"1001"), 2u) == some(9u as T);
    assert parse_buf(bytes(~"123"), 8u) == some(83u as T);
    assert parse_buf(bytes(~"123"), 16u) == some(291u as T);
    assert parse_buf(bytes(~"ffff"), 16u) == some(65535u as T);
    assert parse_buf(bytes(~"z"), 36u) == some(35u as T);

    assert parse_buf(str::bytes(~"Z"), 10u) == none;
    assert parse_buf(str::bytes(~"_"), 2u) == none;
}

#[test]
#[should_fail]
#[ignore(cfg(windows))]
fn to_str_radix1() {
    uint::to_str(100u, 1u);
}

#[test]
#[should_fail]
#[ignore(cfg(windows))]
fn to_str_radix17() {
    uint::to_str(100u, 17u);
}

#[test]
fn test_times() {
    import iter::times;
    let ten = 10 as T;
    let mut accum = 0;
    for ten.times { accum += 1; }
    assert (accum == 10);
}
<|endoftext|>