<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::fmt;
use std::io;
use std::path::{Path, PathBuf};

use gitignore::{self, Gitignore, GitignoreBuilder, Match, Pattern};
use pathutil::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<Option<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![],
            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(None));
            } 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() {
            try!(self.push_ignore_dir(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(None);
            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<Option<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(None);
                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().filter_map(|id| id.as_ref()) {
                let mat = id.matched(path, is_dir);
                // println!("{}, {:?}, {:?}, {:?}\n", path.display(), is_dir, 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.
                    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,
    /// 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<Option<IgnoreDir>, Error> {
        IgnoreDir::with_ignore_names(path, IGNORE_NAMES.iter())
    }

    /// 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<Option<IgnoreDir>, Error>
    where P: AsRef<Path>, S: AsRef<str>, I: Iterator<Item=S> {
        let mut id = IgnoreDir {
            path: path.as_ref().to_path_buf(),
            gi: None,
        };
        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 {
            Ok(None)
        } else {
            id.gi = Some(try!(builder.build()));
            Ok(Some(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 {
        // File types don't apply to directories.
        if is_dir {
            return Match::None;
        }
        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(),
                    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(),
                    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>Fixes #43.<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::fmt;
use std::io;
use std::path::{Path, PathBuf};

use gitignore::{self, Gitignore, GitignoreBuilder, Match, Pattern};
use pathutil::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<Option<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![],
            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(None));
            } 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() {
            try!(self.push_ignore_dir(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(None);
            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<Option<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(None);
                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().filter_map(|id| id.as_ref()) {
                let mat = id.matched(path, is_dir);
                // println!("{}, {:?}, {:?}, {:?}\n", path.display(), is_dir, 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.
                    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,
    /// 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<Option<IgnoreDir>, Error> {
        IgnoreDir::with_ignore_names(path, IGNORE_NAMES.iter())
    }

    /// 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<Option<IgnoreDir>, Error>
    where P: AsRef<Path>, S: AsRef<str>, I: Iterator<Item=S> {
        let mut id = IgnoreDir {
            path: path.as_ref().to_path_buf(),
            gi: None,
        };
        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 {
            Ok(None)
        } else {
            id.gi = Some(try!(builder.build()));
            Ok(Some(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(),
                    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(),
                    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>use std::cast;
use std::libc;
use std::ptr;
use std::str;

// CU16String is a cursed version of `std::c_str::CString` for
// null-terminated utf-16 strings.

/// The representation of a C UTF-16 string.
pub struct CU16String {
    priv buf: *u16,
    priv owns_buffer_: bool,
}

// TODO: iter
impl CU16String {
    /// Create a CU16String from a pointer.
    pub unsafe fn new(buf: *u16, owns_buffer: bool) -> CU16String {
        CU16String { buf: buf, owns_buffer_: owns_buffer }
    }

    /// Unwraps the wrapped `*u16` from the `CU16String` wrapper.
    /// Any ownership of the buffer by the `CU16String` wrapper is forgotten.
    pub unsafe fn unwrap(self) -> *u16 {
        let mut c_u16_str = self;
        c_u16_str.owns_buffer_ = false;
        c_u16_str.buf
    }

    /// Calls a closure with a reference to the underlying `*u16`.
    ///
    /// # Failure
    ///
    /// Fails if the CU16String is null.
    pub fn with_ref<T>(&self, f: |*u16| -> T) -> T {
        if self.buf.is_null() { fail!("CU16String is null!"); }
        f(self.buf)
    }

    /// Calls a closure with a mutable reference to the underlying `*u16`.
    ///
    /// # Failure
    ///
    /// Fails if the CU16String is null.
    pub fn with_mut_ref<T>(&mut self, f: |*mut u16| -> T) -> T {
        if self.buf.is_null() { fail!("CU16String is null!"); }
        f(unsafe { cast::transmute_mut_unsafe(self.buf) })
    }

    /// Returns true if the CU16String is a null.
    pub fn is_null(&self) -> bool {
        self.buf.is_null()
    }

    /// Returns true if the CU16String is not null.
    pub fn is_not_null(&self) -> bool {
        self.buf.is_not_null()
    }

    /// Returns whether or not the `CU16String` owns the buffer.
    pub fn owns_buffer(&self) -> bool {
        self.owns_buffer_
    }
}

impl CU16String {
    /// Converts the CU16String into a `&[u8]` without copying.
    ///
    /// # Failure
    ///
    /// Fails if the CU16String is null.
    #[inline]
    pub fn as_u16_vec<'a>(&'a self) -> &'a [u16] {
        if self.buf.is_null() { fail!("CU16String is null!"); }
        unsafe {
            cast::transmute((self.buf, self.len() + 1))
        }
    }
}

impl ToStr for CU16String {
    #[inline]
    fn to_str(&self) -> ~str {
        if self.buf.is_null() {
        }
        str::from_utf16(self.as_u16_vec())
    }
}

impl Drop for CU16String {
    fn drop(&mut self) {
        if self.owns_buffer_ {
            unsafe {
                libc::free(self.buf as *libc::c_void)
            }
        }
    }
}

impl Container for CU16String {
    #[inline]
    fn len(&self) -> uint {
        unsafe {
            ptr::position(self.buf, |c| *c == 0)
        }
    }
}

/// Parses a C utf-16 "multistring".
/// See `std::c_str::from_c_multistring` for detailed explanation.
pub unsafe fn from_c_u16_multistring(buf: *u16, count: Option<uint>, f: |&CU16String|) -> uint {

    let mut curr_ptr: uint = buf as uint;
    let mut ctr = 0;
    let (limited_count, limit) = match count {
        Some(limit) => (true, limit),
        None => (false, 0)
    };
    while ((limited_count && ctr < limit) || !limited_count)
          && *(curr_ptr as *u16) != 0 as u16 {
        let cstr = CU16String::new(curr_ptr as *u16, false);
        f(&cstr);
        curr_ptr += (cstr.len() * 1 + 1) * 2;
        ctr += 1;
    }
    return ctr;
}

/// A generic trait for converting a value to a `CU16String`, like `ToCStr`.
pub trait ToCU16Str {
    fn with_c_u16_str<T>(&self, f: |*u16| -> T) -> T;
}

impl<'self> ToCU16Str for &'self str {
    fn with_c_u16_str<T>(&self, f: |*u16| -> T) -> T {
        let mut t = self.to_utf16();
        // Null terminate before passing on.
        t.push(0u16);
        t.as_imm_buf(|buf, _len| f(buf))
    }
}

impl<S: Str> ToCU16Str for Option<S> {
    fn with_c_u16_str<T>(&self, f: |*u16| -> T) -> T {
        match self {
            &None => f(ptr::null()),
            &Some(ref s) => {
                s.as_slice().with_c_u16_str(f)
            },
        }
    }
}

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

    #[test]
    fn test_as_u16_vec() {
        let u16s: &[u16] = [
            0xac00, 0x20, 0xac00, 0x00,
        ];

        u16s.as_imm_buf(|buf, _len| {
            let cu = unsafe { CU16String::new(buf, false) };
            let v = cu.as_u16_vec();
            debug!("v: {:?}", v);
            assert_eq!(v, u16s);
        })
    }

    #[test]
    fn test_from_c_u16_multistring() {
        let test: &[u16] = [
            0xac00, 0x00,
            0xac00, 0xac00, 0x00,
            0xac00, 0xac00, 0xac00, 0x00,
            0xac00, 0xac00, 0xac00, 0xac02, 0x00,
            0x00,
        ];
        let compare = [
            "가",
            "가가",
            "가가가",
            "가가가갂",
        ];
        let mut i = 0;
        test.as_imm_buf(|buf, _len| {
            unsafe {
                from_c_u16_multistring(buf, None, |cu| {
                    let b = cu.to_str();
                    assert_eq!(b.char_len(), i + 1);
                    assert_eq!(b, compare[i].to_owned());
                    i += 1;
                })
            }
        });
        assert_eq!(i, 4);
    }
}
<commit_msg>wchar: Add with_c_u16_str_mut<commit_after>use std::cast;
use std::libc;
use std::ptr;
use std::str;

// CU16String is a cursed version of `std::c_str::CString` for
// null-terminated utf-16 strings.

/// The representation of a C UTF-16 string.
pub struct CU16String {
    priv buf: *u16,
    priv owns_buffer_: bool,
}

// TODO: iter
impl CU16String {
    /// Create a CU16String from a pointer.
    pub unsafe fn new(buf: *u16, owns_buffer: bool) -> CU16String {
        CU16String { buf: buf, owns_buffer_: owns_buffer }
    }

    /// Unwraps the wrapped `*u16` from the `CU16String` wrapper.
    /// Any ownership of the buffer by the `CU16String` wrapper is forgotten.
    pub unsafe fn unwrap(self) -> *u16 {
        let mut c_u16_str = self;
        c_u16_str.owns_buffer_ = false;
        c_u16_str.buf
    }

    /// Calls a closure with a reference to the underlying `*u16`.
    ///
    /// # Failure
    ///
    /// Fails if the CU16String is null.
    pub fn with_ref<T>(&self, f: |*u16| -> T) -> T {
        if self.buf.is_null() { fail!("CU16String is null!"); }
        f(self.buf)
    }

    /// Calls a closure with a mutable reference to the underlying `*u16`.
    ///
    /// # Failure
    ///
    /// Fails if the CU16String is null.
    pub fn with_mut_ref<T>(&mut self, f: |*mut u16| -> T) -> T {
        if self.buf.is_null() { fail!("CU16String is null!"); }
        f(unsafe { cast::transmute_mut_unsafe(self.buf) })
    }

    /// Returns true if the CU16String is a null.
    pub fn is_null(&self) -> bool {
        self.buf.is_null()
    }

    /// Returns true if the CU16String is not null.
    pub fn is_not_null(&self) -> bool {
        self.buf.is_not_null()
    }

    /// Returns whether or not the `CU16String` owns the buffer.
    pub fn owns_buffer(&self) -> bool {
        self.owns_buffer_
    }
}

impl CU16String {
    /// Converts the CU16String into a `&[u8]` without copying.
    ///
    /// # Failure
    ///
    /// Fails if the CU16String is null.
    #[inline]
    pub fn as_u16_vec<'a>(&'a self) -> &'a [u16] {
        if self.buf.is_null() { fail!("CU16String is null!"); }
        unsafe {
            cast::transmute((self.buf, self.len() + 1))
        }
    }
}

impl ToStr for CU16String {
    #[inline]
    fn to_str(&self) -> ~str {
        if self.buf.is_null() {
        }
        str::from_utf16(self.as_u16_vec())
    }
}

impl Drop for CU16String {
    fn drop(&mut self) {
        if self.owns_buffer_ {
            unsafe {
                libc::free(self.buf as *libc::c_void)
            }
        }
    }
}

impl Container for CU16String {
    #[inline]
    fn len(&self) -> uint {
        unsafe {
            ptr::position(self.buf, |c| *c == 0)
        }
    }
}

/// Parses a C utf-16 "multistring".
/// See `std::c_str::from_c_multistring` for detailed explanation.
pub unsafe fn from_c_u16_multistring(buf: *u16, count: Option<uint>, f: |&CU16String|) -> uint {

    let mut curr_ptr: uint = buf as uint;
    let mut ctr = 0;
    let (limited_count, limit) = match count {
        Some(limit) => (true, limit),
        None => (false, 0)
    };
    while ((limited_count && ctr < limit) || !limited_count)
          && *(curr_ptr as *u16) != 0 as u16 {
        let cstr = CU16String::new(curr_ptr as *u16, false);
        f(&cstr);
        curr_ptr += (cstr.len() * 1 + 1) * 2;
        ctr += 1;
    }
    return ctr;
}

/// A generic trait for converting a value to a `CU16String`, like `ToCStr`.
pub trait ToCU16Str {
    fn with_c_u16_str<T>(&self, f: |*u16| -> T) -> T;
    fn with_c_u16_str_mut<T>(&mut self, f: |*mut u16| -> T) -> T;
}

impl<'self> ToCU16Str for &'self str {
    fn with_c_u16_str<T>(&self, f: |*u16| -> T) -> T {
        let mut t = self.to_utf16();
        // Null terminate before passing on.
        t.push(0u16);
        t.as_imm_buf(|buf, _len| f(buf))
    }

    fn with_c_u16_str_mut<T>(&mut self, f: |*mut u16| -> T) -> T {
        let mut t = self.to_utf16();
        t.push(0u16);
        t.as_mut_buf(|buf, _len| f(buf))
    }
}

impl<S: Str> ToCU16Str for Option<S> {
    fn with_c_u16_str<T>(&self, f: |*u16| -> T) -> T {
        match self {
            &None => f(ptr::null()),
            &Some(ref s) => {
                s.as_slice().with_c_u16_str(f)
            },
        }
    }

    fn with_c_u16_str_mut<T>(&mut self, f: |*mut u16| -> T) -> T {
        match self {
            &None => f(ptr::mut_null()),
            &Some(ref s) => {
                s.as_slice().with_c_u16_str_mut(f)
            },
        }
    }
}

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

    #[test]
    fn test_as_u16_vec() {
        let u16s: &[u16] = [
            0xac00, 0x20, 0xac00, 0x00,
        ];

        u16s.as_imm_buf(|buf, _len| {
            let cu = unsafe { CU16String::new(buf, false) };
            let v = cu.as_u16_vec();
            debug!("v: {:?}", v);
            assert_eq!(v, u16s);
        })
    }

    #[test]
    fn test_from_c_u16_multistring() {
        let test: &[u16] = [
            0xac00, 0x00,
            0xac00, 0xac00, 0x00,
            0xac00, 0xac00, 0xac00, 0x00,
            0xac00, 0xac00, 0xac00, 0xac02, 0x00,
            0x00,
        ];
        let compare = [
            "가",
            "가가",
            "가가가",
            "가가가갂",
        ];
        let mut i = 0;
        test.as_imm_buf(|buf, _len| {
            unsafe {
                from_c_u16_multistring(buf, None, |cu| {
                    let b = cu.to_str();
                    assert_eq!(b.char_len(), i + 1);
                    assert_eq!(b, compare[i].to_owned());
                    i += 1;
                })
            }
        });
        assert_eq!(i, 4);
    }
}
<|endoftext|>


<commit_before>use nom::multispace;
use std::fmt;
use std::str;

use column::Column;
use common::{
    assignment_expr_list, field_list, opt_multispace, statement_terminator, table_reference,
    value_list, FieldValueExpression, Literal,
};
use keywords::escape_if_keyword;
use table::Table;

#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct InsertStatement {
    pub table: Table,
    pub fields: Option<Vec<Column>>,
    pub data: Vec<Vec<Literal>>,
    pub ignore: bool,
    pub on_duplicate: Option<Vec<(Column, FieldValueExpression)>>,
}

impl fmt::Display for InsertStatement {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "INSERT INTO {}", escape_if_keyword(&self.table.name))?;
        if let Some(ref fields) = self.fields {
            write!(
                f,
                " ({})",
                fields
                    .iter()
                    .map(|ref col| col.name.to_owned())
                    .collect::<Vec<_>>()
                    .join(", ")
            )?;
        }
        write!(
            f,
            " VALUES {}",
            self.data
                .iter()
                .map(|datas| format!(
                    "({})",
                    datas
                        .into_iter()
                        .map(|l| l.to_string())
                        .collect::<Vec<_>>()
                        .join(", ")
                ))
                .collect::<Vec<_>>()
                .join(", ")
        )
    }
}

// Parse rule for a SQL insert query.
// TODO(malte): support REPLACE, nested selection, DEFAULT VALUES
named!(pub insertion<&[u8], InsertStatement>,
    do_parse!(
        tag_no_case!("insert") >>
        ignore: opt!(preceded!(multispace, tag_no_case!("ignore"))) >>
        multispace >>
        tag_no_case!("into") >>
        multispace >>
        table: table_reference >>
        opt_multispace >>
        fields: opt!(do_parse!(
                tag!("(") >>
                opt_multispace >>
                fields: field_list >>
                opt_multispace >>
                tag!(")") >>
                multispace >>
                (fields)
                )
            ) >>
        tag_no_case!("values") >>
        opt_multispace >>
        data: many1!(
            do_parse!(
                tag!("(") >>
                values: value_list >>
                tag!(")") >>
                opt!(
                    do_parse!(
                            opt_multispace >>
                            tag!(",") >>
                            opt_multispace >>
                            ()
                    )
                ) >>
                (values)
            )
        ) >>
        upd_if_dup: opt!(do_parse!(
                opt_multispace >>
                tag_no_case!("on duplicate key update") >>
                multispace >>
                assigns: assignment_expr_list >>
                (assigns)
        )) >>
        statement_terminator >>
        ({
            // "table AS alias" isn't legal in INSERT statements
            assert!(table.alias.is_none());
            InsertStatement {
                table: table,
                fields: fields,
                data: data,
                ignore: ignore.is_some(),
                on_duplicate: upd_if_dup,
            }
        })
    )
);

#[cfg(test)]
mod tests {
    use super::*;
    use arithmetic::{ArithmeticBase, ArithmeticExpression, ArithmeticOperator};
    use column::Column;
    use table::Table;

    #[test]
    fn simple_insert() {
        let qstring = "INSERT INTO users VALUES (42, \"test\");";

        let res = insertion(&[u8](qstring.as_bytes()));
        assert_eq!(
            res.unwrap().1,
            InsertStatement {
                table: Table::from("users"),
                fields: None,
                data: vec![vec![42.into(), "test".into()]],
                ..Default::default()
            }
        );
    }

    #[test]
    fn complex_insert() {
        let qstring = "INSERT INTO users VALUES (42, 'test', \"test\", CURRENT_TIMESTAMP);";

        let res = insertion(&[u8](qstring.as_bytes()));
        assert_eq!(
            res.unwrap().1,
            InsertStatement {
                table: Table::from("users"),
                fields: None,
                data: vec![vec![
                    42.into(),
                    "test".into(),
                    "test".into(),
                    Literal::CurrentTimestamp,
                ],],
                ..Default::default()
            }
        );
    }

    #[test]
    fn insert_with_field_names() {
        let qstring = "INSERT INTO users (id, name) VALUES (42, \"test\");";

        let res = insertion(&[u8](qstring.as_bytes()));
        assert_eq!(
            res.unwrap().1,
            InsertStatement {
                table: Table::from("users"),
                fields: Some(vec![Column::from("id"), Column::from("name")]),
                data: vec![vec![42.into(), "test".into()]],
                ..Default::default()
            }
        );
    }

    // Issue #3
    #[test]
    fn insert_without_spaces() {
        let qstring = "INSERT INTO users(id, name) VALUES(42, \"test\");";

        let res = insertion(&[u8](qstring.as_bytes()));
        assert_eq!(
            res.unwrap().1,
            InsertStatement {
                table: Table::from("users"),
                fields: Some(vec![Column::from("id"), Column::from("name")]),
                data: vec![vec![42.into(), "test".into()]],
                ..Default::default()
            }
        );
    }

    #[test]
    fn multi_insert() {
        let qstring = "INSERT INTO users (id, name) VALUES (42, \"test\"),(21, \"test2\");";

        let res = insertion(&[u8](qstring.as_bytes()));
        assert_eq!(
            res.unwrap().1,
            InsertStatement {
                table: Table::from("users"),
                fields: Some(vec![Column::from("id"), Column::from("name")]),
                data: vec![
                    vec![42.into(), "test".into()],
                    vec![21.into(), "test2".into()],
                ],
                ..Default::default()
            }
        );
    }

    #[test]
    fn insert_with_parameters() {
        let qstring = "INSERT INTO users (id, name) VALUES (?, ?);";

        let res = insertion(&[u8](qstring.as_bytes()));
        assert_eq!(
            res.unwrap().1,
            InsertStatement {
                table: Table::from("users"),
                fields: Some(vec![Column::from("id"), Column::from("name")]),
                data: vec![vec![Literal::Placeholder, Literal::Placeholder]],
                ..Default::default()
            }
        );
    }

    #[test]
    fn insert_with_on_dup_update() {
        let qstring = "INSERT INTO keystores (`key`, `value`) VALUES (?, ?) \
                       ON DUPLICATE KEY UPDATE `value` = `value` + 1";

        let res = insertion(&[u8](qstring.as_bytes()));
        let expected_ae = ArithmeticExpression {
            op: ArithmeticOperator::Add,
            left: ArithmeticBase::Column(Column::from("value")),
            right: ArithmeticBase::Scalar(1.into()),
            alias: None,
        };
        assert_eq!(
            res.unwrap().1,
            InsertStatement {
                table: Table::from("keystores"),
                fields: Some(vec![Column::from("key"), Column::from("value")]),
                data: vec![vec![Literal::Placeholder, Literal::Placeholder]],
                on_duplicate: Some(vec![(
                    Column::from("value"),
                    FieldValueExpression::Arithmetic(expected_ae),
                ),]),
                ..Default::default()
            }
        );
    }

    #[test]
    fn insert_with_leading_value_whitespace() {
        let qstring = "INSERT INTO users (id, name) VALUES ( 42, \"test\");";

        let res = insertion(&[u8](qstring.as_bytes()));
        assert_eq!(
            res.unwrap().1,
            InsertStatement {
                table: Table::from("users"),
                fields: Some(vec![Column::from("id"), Column::from("name")]),
                data: vec![vec![42.into(), "test".into()]],
                ..Default::default()
            }
        );
    }

}
<commit_msg>Fixed compile time errors in insert.rs<commit_after>use nom::character::complete::multispace1;
use std::fmt;
use std::str;

use column::Column;
use common::{
    assignment_expr_list, field_list, opt_multispace, statement_terminator, table_reference,
    value_list, FieldValueExpression, Literal,
};
use keywords::escape_if_keyword;
use table::Table;

#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, Serialize, Deserialize)]
pub struct InsertStatement {
    pub table: Table,
    pub fields: Option<Vec<Column>>,
    pub data: Vec<Vec<Literal>>,
    pub ignore: bool,
    pub on_duplicate: Option<Vec<(Column, FieldValueExpression)>>,
}

impl fmt::Display for InsertStatement {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "INSERT INTO {}", escape_if_keyword(&self.table.name))?;
        if let Some(ref fields) = self.fields {
            write!(
                f,
                " ({})",
                fields
                    .iter()
                    .map(|ref col| col.name.to_owned())
                    .collect::<Vec<_>>()
                    .join(", ")
            )?;
        }
        write!(
            f,
            " VALUES {}",
            self.data
                .iter()
                .map(|datas| format!(
                    "({})",
                    datas
                        .into_iter()
                        .map(|l| l.to_string())
                        .collect::<Vec<_>>()
                        .join(", ")
                ))
                .collect::<Vec<_>>()
                .join(", ")
        )
    }
}

// Parse rule for a SQL insert query.
// TODO(malte): support REPLACE, nested selection, DEFAULT VALUES
named!(pub insertion<&[u8], InsertStatement>,
    do_parse!(
        tag_no_case!("insert") >>
        ignore: opt!(preceded!(multispace1, tag_no_case!("ignore"))) >>
        multispace1 >>
        tag_no_case!("into") >>
        multispace1 >>
        table: table_reference >>
        opt_multispace >>
        fields: opt!(do_parse!(
                tag!("(") >>
                opt_multispace >>
                fields: field_list >>
                opt_multispace >>
                tag!(")") >>
                multispace1 >>
                (fields)
                )
            ) >>
        tag_no_case!("values") >>
        opt_multispace >>
        data: many1!(
            do_parse!(
                tag!("(") >>
                values: value_list >>
                tag!(")") >>
                opt!(
                    do_parse!(
                            opt_multispace >>
                            tag!(",") >>
                            opt_multispace >>
                            ()
                    )
                ) >>
                (values)
            )
        ) >>
        upd_if_dup: opt!(do_parse!(
                opt_multispace >>
                tag_no_case!("on duplicate key update") >>
                multispace1 >>
                assigns: assignment_expr_list >>
                (assigns)
        )) >>
        statement_terminator >>
        ({
            // "table AS alias" isn't legal in INSERT statements
            assert!(table.alias.is_none());
            InsertStatement {
                table: table,
                fields: fields,
                data: data,
                ignore: ignore.is_some(),
                on_duplicate: upd_if_dup,
            }
        })
    )
);

#[cfg(test)]
mod tests {
    use super::*;
    use arithmetic::{ArithmeticBase, ArithmeticExpression, ArithmeticOperator};
    use column::Column;
    use table::Table;

    #[test]
    fn simple_insert() {
        let qstring = "INSERT INTO users VALUES (42, \"test\");";

        let res = insertion(qstring.as_bytes());
        assert_eq!(
            res.unwrap().1,
            InsertStatement {
                table: Table::from("users"),
                fields: None,
                data: vec![vec![42.into(), "test".into()]],
                ..Default::default()
            }
        );
    }

    #[test]
    fn complex_insert() {
        let qstring = "INSERT INTO users VALUES (42, 'test', \"test\", CURRENT_TIMESTAMP);";

        let res = insertion(qstring.as_bytes());
        assert_eq!(
            res.unwrap().1,
            InsertStatement {
                table: Table::from("users"),
                fields: None,
                data: vec![vec![
                    42.into(),
                    "test".into(),
                    "test".into(),
                    Literal::CurrentTimestamp,
                ],],
                ..Default::default()
            }
        );
    }

    #[test]
    fn insert_with_field_names() {
        let qstring = "INSERT INTO users (id, name) VALUES (42, \"test\");";

        let res = insertion(qstring.as_bytes());
        assert_eq!(
            res.unwrap().1,
            InsertStatement {
                table: Table::from("users"),
                fields: Some(vec![Column::from("id"), Column::from("name")]),
                data: vec![vec![42.into(), "test".into()]],
                ..Default::default()
            }
        );
    }

    // Issue #3
    #[test]
    fn insert_without_spaces() {
        let qstring = "INSERT INTO users(id, name) VALUES(42, \"test\");";

        let res = insertion(qstring.as_bytes());
        assert_eq!(
            res.unwrap().1,
            InsertStatement {
                table: Table::from("users"),
                fields: Some(vec![Column::from("id"), Column::from("name")]),
                data: vec![vec![42.into(), "test".into()]],
                ..Default::default()
            }
        );
    }

    #[test]
    fn multi_insert() {
        let qstring = "INSERT INTO users (id, name) VALUES (42, \"test\"),(21, \"test2\");";

        let res = insertion(qstring.as_bytes());
        assert_eq!(
            res.unwrap().1,
            InsertStatement {
                table: Table::from("users"),
                fields: Some(vec![Column::from("id"), Column::from("name")]),
                data: vec![
                    vec![42.into(), "test".into()],
                    vec![21.into(), "test2".into()],
                ],
                ..Default::default()
            }
        );
    }

    #[test]
    fn insert_with_parameters() {
        let qstring = "INSERT INTO users (id, name) VALUES (?, ?);";

        let res = insertion(qstring.as_bytes());
        assert_eq!(
            res.unwrap().1,
            InsertStatement {
                table: Table::from("users"),
                fields: Some(vec![Column::from("id"), Column::from("name")]),
                data: vec![vec![Literal::Placeholder, Literal::Placeholder]],
                ..Default::default()
            }
        );
    }

    #[test]
    fn insert_with_on_dup_update() {
        let qstring = "INSERT INTO keystores (`key`, `value`) VALUES (?, ?) \
                       ON DUPLICATE KEY UPDATE `value` = `value` + 1";

        let res = insertion(qstring.as_bytes());
        let expected_ae = ArithmeticExpression {
            op: ArithmeticOperator::Add,
            left: ArithmeticBase::Column(Column::from("value")),
            right: ArithmeticBase::Scalar(1.into()),
            alias: None,
        };
        assert_eq!(
            res.unwrap().1,
            InsertStatement {
                table: Table::from("keystores"),
                fields: Some(vec![Column::from("key"), Column::from("value")]),
                data: vec![vec![Literal::Placeholder, Literal::Placeholder]],
                on_duplicate: Some(vec![(
                    Column::from("value"),
                    FieldValueExpression::Arithmetic(expected_ae),
                ),]),
                ..Default::default()
            }
        );
    }

    #[test]
    fn insert_with_leading_value_whitespace() {
        let qstring = "INSERT INTO users (id, name) VALUES ( 42, \"test\");";

        let res = insertion(qstring.as_bytes());
        assert_eq!(
            res.unwrap().1,
            InsertStatement {
                table: Table::from("users"),
                fields: Some(vec![Column::from("id"), Column::from("name")]),
                data: vec![vec![42.into(), "test".into()]],
                ..Default::default()
            }
        );
    }
}
<|endoftext|>


<commit_before>/// The Toml Insert extensions

use toml::Value;

use tokenizer::Token;
use tokenizer::tokenize_with_seperator;
use error::*;

pub trait TomlValueInsertExt {

    /// Extension function for inserting a value in the current toml::Value document
    /// using a custom seperator.
    ///
    /// For difference to TomlSetExt::set() and friends, read [#semantics].
    ///
    /// # Semantics
    ///
    /// The function automatically creates intermediate data structures based on the query string.
    /// That means, if the query string is `"a.b.c.[0]"`, but only a table `"a"` exists in the
    /// document, the function automatically creates a table `"b"` inside `"a"` and `"c"` inside
    /// `"b"`, and an array in `"c"`. The array index is ignored if the array is created.
    ///
    /// If an Array exists, but the specified index is larger than the last index, the array will
    /// be expanded by one element: If the array has a length of 3, but the query string specifies
    /// that the element should be put at 1000, the function ignores the large index and simply
    /// appends the value to the index.
    ///
    /// # Return value
    ///
    /// If the insert operation worked correctly, `Ok(None)` is returned.
    /// If the insert operation replaced an existing value `Ok(Some(old_value))` is returned
    /// On failure, `Err(e)` is returned
    ///
    fn insert_with_seperator(&mut self, query: &String, sep: char, value: Value) -> Result<Option<Value>>;

    /// Extension function for inserting a value from the current toml::Value document
    ///
    /// See documentation of `TomlValueinsertExt::insert_with_seperator`
    fn insert(&mut self, query: &String, value: Value) -> Result<Option<Value>> {
        self.insert_with_seperator(query, '.', value)
    }

}

impl TomlValueInsertExt for Value {

    fn insert_with_seperator(&mut self, query: &String, sep: char, value: Value) -> Result<Option<Value>> {
        use resolver::mut_resolver::resolve;

        let mut tokens = try!(tokenize_with_seperator(query, sep));
        let last       = tokens.pop_last().unwrap();
        let mut val    = try!(resolve(self, &tokens));

        match *last {
            Token::Identifier { ident, .. } => {
                match val {
                    &mut Value::Table(ref mut t) => {
                        Ok(t.insert(ident, value))
                    },
                    _ => unimplemented!()
                }
            },

            Token::Index { idx , .. } => {
                match val {
                    &mut Value::Array(ref mut a) => {
                        if a.len() > idx {
                            unimplemented!()
                        } else {
                            a.push(value);
                            Ok(None)
                        }
                    },
                    _ => unimplemented!()
                }
            },
        }
    }

}

#[cfg(test)]
mod test {
    use super::*;
    use error::*;
    use toml::Value;
    use toml::from_str as toml_from_str;

    #[test]
    fn test_insert_with_seperator_into_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        "#).unwrap();

        let res = toml.insert_with_seperator(&String::from("table.a"), '.', Value::Integer(1));

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_none());

        assert!(is_match!(toml, Value::Table(_)));
        match toml {
            Value::Table(ref t) => {
                assert!(!t.is_empty());

                let table = t.get("table");
                assert!(table.is_some());

                let table = table.unwrap();
                assert!(is_match!(table, &Value::Table(_)));
                match table {
                    &Value::Table(ref t) => {
                        assert!(!t.is_empty());

                        let a = t.get("a");
                        assert!(a.is_some());

                        let a = a.unwrap();
                        assert!(is_match!(a, &Value::Integer(1)));
                    },
                    _ => panic!("What just happenend?"),
                }
            },
            _ => panic!("What just happenend?"),
        }
    }

    #[test]
    fn test_insert_with_seperator_into_array() {
        use std::ops::Index;

        let mut toml : Value = toml_from_str(r#"
        array = []
        "#).unwrap();

        let res = toml.insert_with_seperator(&String::from("array.[0]"), '.', Value::Integer(1));

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_none());

        assert!(is_match!(toml, Value::Table(_)));
        match toml {
            Value::Table(ref t) => {
                assert!(!t.is_empty());

                let array = t.get("array");
                assert!(array.is_some());

                let array = array.unwrap();
                assert!(is_match!(array, &Value::Array(_)));
                match array {
                    &Value::Array(ref a) => {
                        assert!(!a.is_empty());
                        assert!(is_match!(a.index(0), &Value::Integer(1)));
                    },
                    _ => panic!("What just happenend?"),
                }
            },
            _ => panic!("What just happenend?"),
        }
    }

    #[test]
    fn test_insert_with_seperator_into_nested_table() {
        let mut toml : Value = toml_from_str(r#"
        [a.b.c]
        "#).unwrap();

        let res = toml.insert_with_seperator(&String::from("a.b.c.d"), '.', Value::Integer(1));

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_none());

        assert!(is_match!(toml, Value::Table(_)));
        match toml {
            Value::Table(ref outer) => {
                assert!(!outer.is_empty());
                let a_tab = outer.get("a");
                assert!(a_tab.is_some());

                let a_tab = a_tab.unwrap();
                assert!(is_match!(a_tab, &Value::Table(_)));
                match a_tab {
                    &Value::Table(ref a) => {
                        assert!(!a.is_empty());

                        let b_tab = a.get("b");
                        assert!(b_tab.is_some());

                        let b_tab = b_tab.unwrap();
                        assert!(is_match!(b_tab, &Value::Table(_)));
                        match b_tab {
                            &Value::Table(ref b) => {
                                assert!(!b.is_empty());

                                let c_tab = b.get("c");
                                assert!(c_tab.is_some());

                                let c_tab = c_tab.unwrap();
                                assert!(is_match!(c_tab, &Value::Table(_)));
                                match c_tab {
                                    &Value::Table(ref c) => {
                                        assert!(!c.is_empty());

                                        let d = c.get("d");
                                        assert!(d.is_some());

                                        let d = d.unwrap();
                                        assert!(is_match!(d, &Value::Integer(1)));
                                    },
                                    _ => panic!("What just happenend?"),
                                }
                            },
                            _ => panic!("What just happenend?"),
                        }
                    },
                    _ => panic!("What just happenend?"),
                }
            },
            _ => panic!("What just happened?"),
        }
    }

    #[test]
    fn test_insert_with_seperator_into_table_where_array_is() {
        let mut toml : Value = toml_from_str(r#"
        table = []
        "#).unwrap();

        let res = toml.insert_with_seperator(&String::from("table.a"), '.', Value::Integer(1));

        assert!(res.is_err());

        let err = res.unwrap_err();
        assert!(is_match!(err.kind(), &ErrorKind::NoIdentifierInArray(_)));
    }

    #[test]
    fn test_insert_with_seperator_into_array_where_table_is() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        "#).unwrap();

        let res = toml.insert_with_seperator(&String::from("table.[0]"), '.', Value::Integer(1));

        assert!(res.is_err());

        let err = res.unwrap_err();
        assert!(is_match!(err.kind(), &ErrorKind::NoIndexInTable(_)));
    }

}

<commit_msg>Impl Value::insert_with_seperator to fix test_insert_with_seperator_into_table_where_array_is<commit_after>/// The Toml Insert extensions

use toml::Value;

use tokenizer::Token;
use tokenizer::tokenize_with_seperator;
use error::*;

pub trait TomlValueInsertExt {

    /// Extension function for inserting a value in the current toml::Value document
    /// using a custom seperator.
    ///
    /// For difference to TomlSetExt::set() and friends, read [#semantics].
    ///
    /// # Semantics
    ///
    /// The function automatically creates intermediate data structures based on the query string.
    /// That means, if the query string is `"a.b.c.[0]"`, but only a table `"a"` exists in the
    /// document, the function automatically creates a table `"b"` inside `"a"` and `"c"` inside
    /// `"b"`, and an array in `"c"`. The array index is ignored if the array is created.
    ///
    /// If an Array exists, but the specified index is larger than the last index, the array will
    /// be expanded by one element: If the array has a length of 3, but the query string specifies
    /// that the element should be put at 1000, the function ignores the large index and simply
    /// appends the value to the index.
    ///
    /// # Return value
    ///
    /// If the insert operation worked correctly, `Ok(None)` is returned.
    /// If the insert operation replaced an existing value `Ok(Some(old_value))` is returned
    /// On failure, `Err(e)` is returned
    ///
    fn insert_with_seperator(&mut self, query: &String, sep: char, value: Value) -> Result<Option<Value>>;

    /// Extension function for inserting a value from the current toml::Value document
    ///
    /// See documentation of `TomlValueinsertExt::insert_with_seperator`
    fn insert(&mut self, query: &String, value: Value) -> Result<Option<Value>> {
        self.insert_with_seperator(query, '.', value)
    }

}

impl TomlValueInsertExt for Value {

    fn insert_with_seperator(&mut self, query: &String, sep: char, value: Value) -> Result<Option<Value>> {
        use resolver::mut_resolver::resolve;

        let mut tokens = try!(tokenize_with_seperator(query, sep));
        let last       = tokens.pop_last().unwrap();
        let mut val    = try!(resolve(self, &tokens));

        match *last {
            Token::Identifier { ident, .. } => {
                match val {
                    &mut Value::Table(ref mut t) => {
                        Ok(t.insert(ident, value))
                    },
                    _ => Err(Error::from(ErrorKind::NoIdentifierInArray(ident.clone())))
                }
            },

            Token::Index { idx , .. } => {
                match val {
                    &mut Value::Array(ref mut a) => {
                        if a.len() > idx {
                            unimplemented!()
                        } else {
                            a.push(value);
                            Ok(None)
                        }
                    },
                    _ => unimplemented!()
                }
            },
        }
    }

}

#[cfg(test)]
mod test {
    use super::*;
    use error::*;
    use toml::Value;
    use toml::from_str as toml_from_str;

    #[test]
    fn test_insert_with_seperator_into_table() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        "#).unwrap();

        let res = toml.insert_with_seperator(&String::from("table.a"), '.', Value::Integer(1));

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_none());

        assert!(is_match!(toml, Value::Table(_)));
        match toml {
            Value::Table(ref t) => {
                assert!(!t.is_empty());

                let table = t.get("table");
                assert!(table.is_some());

                let table = table.unwrap();
                assert!(is_match!(table, &Value::Table(_)));
                match table {
                    &Value::Table(ref t) => {
                        assert!(!t.is_empty());

                        let a = t.get("a");
                        assert!(a.is_some());

                        let a = a.unwrap();
                        assert!(is_match!(a, &Value::Integer(1)));
                    },
                    _ => panic!("What just happenend?"),
                }
            },
            _ => panic!("What just happenend?"),
        }
    }

    #[test]
    fn test_insert_with_seperator_into_array() {
        use std::ops::Index;

        let mut toml : Value = toml_from_str(r#"
        array = []
        "#).unwrap();

        let res = toml.insert_with_seperator(&String::from("array.[0]"), '.', Value::Integer(1));

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_none());

        assert!(is_match!(toml, Value::Table(_)));
        match toml {
            Value::Table(ref t) => {
                assert!(!t.is_empty());

                let array = t.get("array");
                assert!(array.is_some());

                let array = array.unwrap();
                assert!(is_match!(array, &Value::Array(_)));
                match array {
                    &Value::Array(ref a) => {
                        assert!(!a.is_empty());
                        assert!(is_match!(a.index(0), &Value::Integer(1)));
                    },
                    _ => panic!("What just happenend?"),
                }
            },
            _ => panic!("What just happenend?"),
        }
    }

    #[test]
    fn test_insert_with_seperator_into_nested_table() {
        let mut toml : Value = toml_from_str(r#"
        [a.b.c]
        "#).unwrap();

        let res = toml.insert_with_seperator(&String::from("a.b.c.d"), '.', Value::Integer(1));

        assert!(res.is_ok());

        let res = res.unwrap();
        assert!(res.is_none());

        assert!(is_match!(toml, Value::Table(_)));
        match toml {
            Value::Table(ref outer) => {
                assert!(!outer.is_empty());
                let a_tab = outer.get("a");
                assert!(a_tab.is_some());

                let a_tab = a_tab.unwrap();
                assert!(is_match!(a_tab, &Value::Table(_)));
                match a_tab {
                    &Value::Table(ref a) => {
                        assert!(!a.is_empty());

                        let b_tab = a.get("b");
                        assert!(b_tab.is_some());

                        let b_tab = b_tab.unwrap();
                        assert!(is_match!(b_tab, &Value::Table(_)));
                        match b_tab {
                            &Value::Table(ref b) => {
                                assert!(!b.is_empty());

                                let c_tab = b.get("c");
                                assert!(c_tab.is_some());

                                let c_tab = c_tab.unwrap();
                                assert!(is_match!(c_tab, &Value::Table(_)));
                                match c_tab {
                                    &Value::Table(ref c) => {
                                        assert!(!c.is_empty());

                                        let d = c.get("d");
                                        assert!(d.is_some());

                                        let d = d.unwrap();
                                        assert!(is_match!(d, &Value::Integer(1)));
                                    },
                                    _ => panic!("What just happenend?"),
                                }
                            },
                            _ => panic!("What just happenend?"),
                        }
                    },
                    _ => panic!("What just happenend?"),
                }
            },
            _ => panic!("What just happened?"),
        }
    }

    #[test]
    fn test_insert_with_seperator_into_table_where_array_is() {
        let mut toml : Value = toml_from_str(r#"
        table = []
        "#).unwrap();

        let res = toml.insert_with_seperator(&String::from("table.a"), '.', Value::Integer(1));

        assert!(res.is_err());

        let err = res.unwrap_err();
        assert!(is_match!(err.kind(), &ErrorKind::NoIdentifierInArray(_)));
    }

    #[test]
    fn test_insert_with_seperator_into_array_where_table_is() {
        let mut toml : Value = toml_from_str(r#"
        [table]
        "#).unwrap();

        let res = toml.insert_with_seperator(&String::from("table.[0]"), '.', Value::Integer(1));

        assert!(res.is_err());

        let err = res.unwrap_err();
        assert!(is_match!(err.kind(), &ErrorKind::NoIndexInTable(_)));
    }

}

<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

//! Element an snapshot common logic.

use crate::gecko_bindings::bindings;
use crate::gecko_bindings::structs::{self, nsAtom};
use crate::string_cache::WeakAtom;
use crate::values::AtomIdent;
use crate::Atom;
use crate::CaseSensitivityExt;
use selectors::attr::CaseSensitivity;

/// A function that, given an element of type `T`, allows you to get a single
/// class or a class list.
enum Class<'a> {
    None,
    One(*const nsAtom),
    More(&'a [structs::RefPtr<nsAtom>]),
}

#[inline(always)]
fn base_type(attr: &structs::nsAttrValue) -> structs::nsAttrValue_ValueBaseType {
    (attr.mBits & structs::NS_ATTRVALUE_BASETYPE_MASK) as structs::nsAttrValue_ValueBaseType
}

#[inline(always)]
unsafe fn ptr<T>(attr: &structs::nsAttrValue) -> *const T {
    (attr.mBits & !structs::NS_ATTRVALUE_BASETYPE_MASK) as *const T
}

#[inline(always)]
unsafe fn get_class_or_part_from_attr(attr: &structs::nsAttrValue) -> Class {
    debug_assert!(bindings::Gecko_AssertClassAttrValueIsSane(attr));
    let base_type = base_type(attr);
    if base_type == structs::nsAttrValue_ValueBaseType_eAtomBase {
        return Class::One(ptr::<nsAtom>(attr));
    }
    if base_type == structs::nsAttrValue_ValueBaseType_eOtherBase {
        let container = ptr::<structs::MiscContainer>(attr);
        debug_assert_eq!(
            (*container).mType,
            structs::nsAttrValue_ValueType_eAtomArray
        );
        let array = (*container)
            .__bindgen_anon_1
            .mValue
            .as_ref()
            .__bindgen_anon_1
            .mAtomArray
            .as_ref();
        return Class::More(&***array)
    }
    debug_assert_eq!(base_type, structs::nsAttrValue_ValueBaseType_eStringBase);
    Class::None
}

#[inline(always)]
unsafe fn get_id_from_attr(attr: &structs::nsAttrValue) -> &WeakAtom {
    debug_assert_eq!(
        base_type(attr),
        structs::nsAttrValue_ValueBaseType_eAtomBase
    );
    WeakAtom::new(ptr::<nsAtom>(attr))
}

/// Find an attribute value with a given name and no namespace.
#[inline(always)]
pub fn find_attr<'a>(
    attrs: &'a [structs::AttrArray_InternalAttr],
    name: &Atom,
) -> Option<&'a structs::nsAttrValue> {
    attrs
        .iter()
        .find(|attr| attr.mName.mBits == name.as_ptr() as usize)
        .map(|attr| &attr.mValue)
}

/// Finds the id attribute from a list of attributes.
#[inline(always)]
pub fn get_id(attrs: &[structs::AttrArray_InternalAttr]) -> Option<&WeakAtom> {
    Some(unsafe { get_id_from_attr(find_attr(attrs, &atom!("id"))?) })
}

#[inline(always)]
pub(super) fn each_exported_part(
    attrs: &[structs::AttrArray_InternalAttr],
    name: &AtomIdent,
    mut callback: impl FnMut(&AtomIdent),
) {
    let attr = match find_attr(attrs, &atom!("exportparts")) {
        Some(attr) => attr,
        None => return,
    };
    let mut length = 0;
    let atoms = unsafe { bindings::Gecko_Element_ExportedParts(attr, name.as_ptr(), &mut length) };
    if atoms.is_null() {
        return;
    }

    unsafe {
        for atom in std::slice::from_raw_parts(atoms, length) {
            AtomIdent::with(*atom, &mut callback)
        }
    }
}

#[inline(always)]
pub(super) fn imported_part(
    attrs: &[structs::AttrArray_InternalAttr],
    name: &AtomIdent,
) -> Option<AtomIdent> {
    let attr = find_attr(attrs, &atom!("exportparts"))?;
    let atom = unsafe { bindings::Gecko_Element_ImportedPart(attr, name.as_ptr()) };
    if atom.is_null() {
        return None;
    }
    Some(AtomIdent(unsafe { Atom::from_raw(atom) }))
}

/// Given a class or part name, a case sensitivity, and an array of attributes,
/// returns whether the attribute has that name.
#[inline(always)]
pub fn has_class_or_part(
    name: &AtomIdent,
    case_sensitivity: CaseSensitivity,
    attr: &structs::nsAttrValue,
) -> bool {
    match unsafe { get_class_or_part_from_attr(attr) } {
        Class::None => false,
        Class::One(atom) => unsafe { case_sensitivity.eq_atom(name, WeakAtom::new(atom)) },
        Class::More(atoms) => match case_sensitivity {
            CaseSensitivity::CaseSensitive => {
                atoms.iter().any(|atom| atom.mRawPtr == name.as_ptr())
            },
            CaseSensitivity::AsciiCaseInsensitive => unsafe {
                atoms
                    .iter()
                    .any(|atom| WeakAtom::new(atom.mRawPtr).eq_ignore_ascii_case(name))
            },
        },
    }
}

/// Given an item, a callback, and a getter, execute `callback` for each class
/// or part name this `item` has.
#[inline(always)]
pub fn each_class_or_part<F>(attr: &structs::nsAttrValue, mut callback: F)
where
    F: FnMut(&AtomIdent),
{
    unsafe {
        match get_class_or_part_from_attr(attr) {
            Class::None => {},
            Class::One(atom) => AtomIdent::with(atom, callback),
            Class::More(atoms) => {
                for atom in atoms {
                    AtomIdent::with(atom.mRawPtr, &mut callback)
                }
            },
        }
    }
}
<commit_msg>style: Use an AutoTArray for atom arrays in attributes (class / part).<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

//! Element an snapshot common logic.

use crate::gecko_bindings::bindings;
use crate::gecko_bindings::structs::{self, nsAtom};
use crate::string_cache::WeakAtom;
use crate::values::AtomIdent;
use crate::Atom;
use crate::CaseSensitivityExt;
use selectors::attr::CaseSensitivity;

/// A function that, given an element of type `T`, allows you to get a single
/// class or a class list.
enum Class<'a> {
    None,
    One(*const nsAtom),
    More(&'a [structs::RefPtr<nsAtom>]),
}

#[inline(always)]
fn base_type(attr: &structs::nsAttrValue) -> structs::nsAttrValue_ValueBaseType {
    (attr.mBits & structs::NS_ATTRVALUE_BASETYPE_MASK) as structs::nsAttrValue_ValueBaseType
}

#[inline(always)]
unsafe fn ptr<T>(attr: &structs::nsAttrValue) -> *const T {
    (attr.mBits & !structs::NS_ATTRVALUE_BASETYPE_MASK) as *const T
}

#[inline(always)]
unsafe fn get_class_or_part_from_attr(attr: &structs::nsAttrValue) -> Class {
    debug_assert!(bindings::Gecko_AssertClassAttrValueIsSane(attr));
    let base_type = base_type(attr);
    if base_type == structs::nsAttrValue_ValueBaseType_eAtomBase {
        return Class::One(ptr::<nsAtom>(attr));
    }
    if base_type == structs::nsAttrValue_ValueBaseType_eOtherBase {
        let container = ptr::<structs::MiscContainer>(attr);
        debug_assert_eq!(
            (*container).mType,
            structs::nsAttrValue_ValueType_eAtomArray
        );
        // NOTE: Bindgen doesn't deal with AutoTArray, so cast it below.
        let array: *mut u8 = *(*container)
            .__bindgen_anon_1
            .mValue
            .as_ref()
            .__bindgen_anon_1
            .mAtomArray
            .as_ref();
        let array = array as *const structs::nsTArray<structs::RefPtr<nsAtom>>;
        return Class::More(&**array)
    }
    debug_assert_eq!(base_type, structs::nsAttrValue_ValueBaseType_eStringBase);
    Class::None
}

#[inline(always)]
unsafe fn get_id_from_attr(attr: &structs::nsAttrValue) -> &WeakAtom {
    debug_assert_eq!(
        base_type(attr),
        structs::nsAttrValue_ValueBaseType_eAtomBase
    );
    WeakAtom::new(ptr::<nsAtom>(attr))
}

/// Find an attribute value with a given name and no namespace.
#[inline(always)]
pub fn find_attr<'a>(
    attrs: &'a [structs::AttrArray_InternalAttr],
    name: &Atom,
) -> Option<&'a structs::nsAttrValue> {
    attrs
        .iter()
        .find(|attr| attr.mName.mBits == name.as_ptr() as usize)
        .map(|attr| &attr.mValue)
}

/// Finds the id attribute from a list of attributes.
#[inline(always)]
pub fn get_id(attrs: &[structs::AttrArray_InternalAttr]) -> Option<&WeakAtom> {
    Some(unsafe { get_id_from_attr(find_attr(attrs, &atom!("id"))?) })
}

#[inline(always)]
pub(super) fn each_exported_part(
    attrs: &[structs::AttrArray_InternalAttr],
    name: &AtomIdent,
    mut callback: impl FnMut(&AtomIdent),
) {
    let attr = match find_attr(attrs, &atom!("exportparts")) {
        Some(attr) => attr,
        None => return,
    };
    let mut length = 0;
    let atoms = unsafe { bindings::Gecko_Element_ExportedParts(attr, name.as_ptr(), &mut length) };
    if atoms.is_null() {
        return;
    }

    unsafe {
        for atom in std::slice::from_raw_parts(atoms, length) {
            AtomIdent::with(*atom, &mut callback)
        }
    }
}

#[inline(always)]
pub(super) fn imported_part(
    attrs: &[structs::AttrArray_InternalAttr],
    name: &AtomIdent,
) -> Option<AtomIdent> {
    let attr = find_attr(attrs, &atom!("exportparts"))?;
    let atom = unsafe { bindings::Gecko_Element_ImportedPart(attr, name.as_ptr()) };
    if atom.is_null() {
        return None;
    }
    Some(AtomIdent(unsafe { Atom::from_raw(atom) }))
}

/// Given a class or part name, a case sensitivity, and an array of attributes,
/// returns whether the attribute has that name.
#[inline(always)]
pub fn has_class_or_part(
    name: &AtomIdent,
    case_sensitivity: CaseSensitivity,
    attr: &structs::nsAttrValue,
) -> bool {
    match unsafe { get_class_or_part_from_attr(attr) } {
        Class::None => false,
        Class::One(atom) => unsafe { case_sensitivity.eq_atom(name, WeakAtom::new(atom)) },
        Class::More(atoms) => match case_sensitivity {
            CaseSensitivity::CaseSensitive => {
                let name_ptr = name.as_ptr();
                atoms.iter().any(|atom| atom.mRawPtr == name_ptr)
            },
            CaseSensitivity::AsciiCaseInsensitive => unsafe {
                atoms
                    .iter()
                    .any(|atom| WeakAtom::new(atom.mRawPtr).eq_ignore_ascii_case(name))
            },
        },
    }
}

/// Given an item, a callback, and a getter, execute `callback` for each class
/// or part name this `item` has.
#[inline(always)]
pub fn each_class_or_part<F>(attr: &structs::nsAttrValue, mut callback: F)
where
    F: FnMut(&AtomIdent),
{
    unsafe {
        match get_class_or_part_from_attr(attr) {
            Class::None => {},
            Class::One(atom) => AtomIdent::with(atom, callback),
            Class::More(atoms) => {
                for atom in atoms {
                    AtomIdent::with(atom.mRawPtr, &mut callback)
                }
            },
        }
    }
}
<|endoftext|>