<commit_before>//! A library for parsing and processing COLLADA documents.
//!
//! [COLLADA][COLLADA] is a COLLAborative Design Activity that defines an XML-based schema to
//! enable 3D authoring applications to freely exchange digital assets. It supports a vast array of
//! features used in 3D modeling, animation, and VFX work, and provides and open, non-proprietary
//! alternative to common formats like [FBX][FBX].
//!
//! This provides functionality for parsing a COLLADA document and utilities for processing the
//! contained data, with the intention of enable direct usage of COLLADA data as well as
//! interchange of document data into other formats.
//!
//! # Quick Start
//!
//! The easiest way to parse a COLLADA document is to load it from a file and use
//! [`Collada::read()`][Collada::read]:
//!
//! ```
//! # #![allow(unused_variables)]
//! use std::fs::File;
//! use parse_collada::Collada;
//!
//! let file = File::open("resources/blender_cube.dae").unwrap();
//! let collada = Collada::read(file).unwrap();
//! ```
//!
//! The resulting [`Collada`][Collada] object provides direct access to all data in the document,
//! directly recreating the logical structure of the document as a Rust type.
//!
//! # COLLADA Versions
//!
//! Currently there are 3 COLLADA versions supported by this library: `1.4.0`, `1.4.1`, and
//! `1.5.0`. Older versions are not supported, but may be added if there is reason to do so. This
//! library attempts to normalize data across versions by "upgrading" all documents to match the
//! `1.5.0` specification. This removes the need for client code to be aware of the specification
//! version used by documents it handles. This conversion is done transparently without the need
//! for user specification.
//!
//! # 3rd Party Extensions
//!
//! The COLLADA format allows for semi-arbitrary extensions to the standard, allowing applications
//! to include application-specific data. This extra data is considered "optional", but may allow
//! applications consuming the COLLADA document to more accurately recreate the scene contained
//! in the document. This library attempts to directly support common 3rd party extensions,
//! primarily those for Blender and Maya. In the case that the 3rd party extension is not
//! directly supported, the underlying XML will be preserved so that the client code can attempt
//! to still use the data.
//!
//! [COLLADA]: https://www.khronos.org/collada/
//! [FBX]: https://en.wikipedia.org/wiki/FBX
//! [Collada]: struct.Collada.html
//! [Collada::read]: struct.Collada.html#method.read

extern crate xml;

pub use xml::common::TextPosition;
pub use xml::reader::Error as XmlError;

use std::io::Read;
use std::fmt::{self, Display, Formatter};
use xml::common::Position;
use xml::EventReader;
use xml::reader::XmlEvent::*;

pub static COLLADA_ATTRIBS: &'static [&'static str] = &["version", "xmlns", "base"];

/// Represents a parsed COLLADA document.
#[derive(Debug, Clone)]
pub struct Collada {
    /// The version string for the COLLADA specification used by the document.
    ///
    /// Only "1.4.0", "1.4.1", and "1.5.0" are supported currently.
    pub version: String,

    /// The base uri for any relative URIs in the document.
    ///
    /// Specified by the `base` attribute on the root `<COLLADA>` element.
    base_uri: Option<AnyUri>,
}

impl Collada {
    /// Read a COLLADA document from a string.
    ///
    /// # Examples
    ///
    /// ```
    /// # #![allow(unused_variables)]
    /// use parse_collada::Collada;
    ///
    /// static DOCUMENT: &'static str = r#"
    ///     <?xml version="1.0" encoding="utf-8"?>
    ///     <COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1">
    ///     </COLLADA>
    /// "#;
    ///
    /// let collada = Collada::from_str(DOCUMENT).unwrap();
    /// ```
    ///
    /// # Errors
    ///
    /// Returns `Err` if the document is invalid or malformed in some way. For details about
    /// COLLADA versions, 3rd party extensions, and any other details that could influence how
    /// a document is parsed see the [crate-level documentation][crate].
    ///
    /// [crate]: index.html
    pub fn from_str(source: &str) -> Result<Collada> {
        let reader = EventReader::from_str(source);
        Collada::parse(reader)
    }

    /// Attempts to parse the contents of a COLLADA document.
    ///
    /// # Examples
    ///
    /// ```
    /// # #![allow(unused_variables)]
    /// use std::fs::File;
    /// use parse_collada::Collada;
    ///
    /// let file = File::open("resources/blender_cube.dae").unwrap();
    /// let collada = Collada::read(file).unwrap();
    /// ```
    ///
    /// # Errors
    ///
    /// Returns `Err` if the document is invalid or malformed in some way. For details about
    /// COLLADA versions, 3rd party extensions, and any other details that could influence how
    /// a document is parsed see the [crate-level documentation][crate].
    ///
    /// [crate]: index.html
    pub fn read<R: Read>(reader: R) -> Result<Collada> {
        let reader = EventReader::new(reader);
        Collada::parse(reader)
    }

    /// The logic behind parsing the COLLADA document.
    ///
    /// `from_str()` and `read()` just create the `xml::EventReader` and then defer to `parse()`.
    fn parse<R: Read>(mut reader: EventReader<R>) -> Result<Collada> {
        let mut collada = Collada {
            version: String::new(),
            base_uri: None,
        };

        // Eat the `StartDocument` event. It has no useful information for our purposes, but it
        // will always be the first event emitted, even if there's no XML declaration at the
        // beginning of the document. This is defined as part of the xml-rs API as of v0.3.5,
        // but it's possible this can will change in the future.
        match reader.next()? {
            StartDocument { .. } => {},
            _ => panic!("First event from EventReader wasn't StartDocument"),
        }

        // The next element will always be the `<COLLADA>` tag. This will specify what version of
        // the COLLADA spec is being used, which is how we'll determine our sub-parser.
        match reader.next()? {
            StartElement { name, attributes, namespace: _ } => {
                // If the element isn't the `<COLLADA>` tag then the document is malformed,
                // return an error.
                if name.local_name != "COLLADA" {
                    return Err(Error {
                        position: reader.position(),
                        kind: ErrorKind::UnexpectedRootElement {
                            element: name.local_name,
                        }
                    })
                }

                // Valide the attributes on the `<COLLADA>` tag.
                // Use boolean flags to track if specific attributes have been encountered.
                let mut has_found_version = false;
                let mut has_found_base = false;

                for attribute in attributes {
                    // NOTE: I'm using `if` blocks instead of `match` here because using `match`
                    // won't allow for the name to be moved out of `attribute`. Using `if` saves
                    // some unnecessary allocations. I expect at some point Rust will get smart
                    // enough that this will no longer be an issue, at which point we should
                    // change this to use `match`, as that keeps better with Rust best practices.
                    if attribute.name.local_name == "version" {
                        if !has_found_version {
                            collada.version = attribute.value;
                            has_found_version = true;
                        } else {
                            return Err(Error {
                                position: reader.position(),
                                kind: ErrorKind::UnexpectedAttribute {
                                    element: name.local_name,
                                    attribute: "version".to_owned(),
                                    expected: COLLADA_ATTRIBS,
                                },
                            })
                        }
                    } else if attribute.name.local_name == "base" {
                        if !has_found_base {
                            // TODO: Do we need to validate the URI?
                            collada.base_uri = Some(AnyUri(attribute.value));
                            has_found_base = true;
                        } else {
                            return Err(Error {
                                position: reader.position(),
                                kind: ErrorKind::UnexpectedAttribute {
                                    element: name.local_name,
                                    attribute: "base".to_owned(),
                                    expected: COLLADA_ATTRIBS,
                                },
                            })
                        }
                    } else {
                        return Err(Error {
                            position: reader.position(),
                            kind: ErrorKind::UnexpectedAttribute {
                                element: name.local_name,
                                attribute: attribute.name.local_name,
                                expected: COLLADA_ATTRIBS,
                            },
                        })
                    }
                }

                // Verify that all required attributes have been found.
                if !has_found_version {
                    return Err(Error {
                        position: reader.position(),
                        kind: ErrorKind::MissingAttribute {
                            element: name.local_name,
                            attribute: "version",
                        },
                    })
                }
            }

            // I'm *almost* 100% certain that the only event that can follow the `StartDocument`
            // event is a `StartElement` event. As of v0.3.5, xml-rs doesn't support
            // `<!DOCTYPE>` or processing instructions, and it ignores whitespace and comments
            // (according to how we configure the parser), and those are the only things allowed
            // between `StartDocument` and the first `StartElement`. If xml-rs changes its
            // behavior this will need to be updated.
            event @ _ => panic!("Unexpected event: {:?}", event),
        }

        // Eat any events until we get to the `</COLLADA>` tag.
        // TODO: Actually parse the body of the document.
        loop {
            match reader.next()? {
                EndElement { ref name } if name.local_name == "COLLADA" => { break }
                _ => {}
            }
        }

        // TODO: Verify the next event is the `EndDocument` event.
        match reader.next()? {
            EndDocument => {}

            // Same logic here as with the starting event. The only thing that can come after the
            // close tag are comments, white space, and processing instructions, all of which we
            // ignore. This can change with future versions of xml-rs, though.
            event @ _ => { panic!("Unexpected event: {:?}", event); }
        }

        Ok(collada)
    }
}

/// A COLLADA parsing error.
///
/// Contains where in the document the error occurred (i.e. line number and column), and
/// details about the nature of the error.
#[derive(Debug)]
pub struct Error {
    position: TextPosition,
    kind: ErrorKind,
}

impl Error {
    /// Gets the position in the document where the error occurred.
    pub fn position(&self) -> TextPosition { self.position }

    /// Gets detailed information about the error.
    pub fn kind(&self) -> &ErrorKind { &self.kind }
}

impl From<xml::reader::Error> for Error {
    fn from(from: xml::reader::Error) -> Error {
        Error {
            position: from.position(),
            kind: ErrorKind::XmlError(from),
        }
    }
}

impl Display for Error {
    fn fmt(&self, formatter: &mut Formatter) -> ::std::result::Result<(), fmt::Error> {
        write!(formatter, "Error at {}: {}", self.position, self.kind)
    }
}

/// The specific error variant.
#[derive(Debug, PartialEq)]
pub enum ErrorKind {
    /// An element was missing a required attribute.
    MissingAttribute {
        /// The element that was missing an attribute.
        element: String,

        /// The attribute that expected to be present.
        attribute: &'static str,
    },

    /// An element had an attribute that isn't allowed.
    UnexpectedAttribute {
        /// The element that had the unexpected attribute.
        element: String,

        /// The unexpected attribute.
        attribute: String,

        /// The set of attributes allowed for this element.
        expected: &'static [&'static str],
    },

    /// An element had a child element that wasn't allowed.
    UnexpectedElement {
        parent: String,
        element: String,
        expected: &'static [&'static str],
    },

    /// The document started with an element other than `<COLLADA>`.
    UnexpectedRootElement {
        element: String,
    },

    /// The XML in the document was malformed in some way.
    XmlError(XmlError),
}

impl Display for ErrorKind {
    fn fmt(&self, formatter: &mut Formatter) -> ::std::result::Result<(), fmt::Error> {
        match *self {
            ErrorKind::MissingAttribute { ref element, ref attribute } => {
                write!(formatter, "<{}> is missing the required attribute \"{}\"", element, attribute)
            }

            ErrorKind::UnexpectedAttribute { ref element, ref attribute, expected } => {
                write!(
                    formatter,
                    "<{}> had an an attribute \"{}\" that is not allowed, only the following attributes are allowed for <{0}>: {}",
                    element,
                    attribute,
                    StringListDisplay(expected),
                )
            }

            ErrorKind::UnexpectedElement { ref parent, ref element, expected } => {
                write!(
                    formatter,
                    "<{}> had a child <{}> which is not allowed, <{0}> may only have the following children: {}",
                    parent,
                    element,
                    StringListDisplay(expected),
                )
            }

            ErrorKind::UnexpectedRootElement { ref element } => {
                write!(formatter, "Document began with <{}> instead of <COLLADA>", element)
            }

            ErrorKind::XmlError(ref error) => {
                write!(formatter, "{}", error.msg())
            }
        }
    }
}

/// A specialized result type for COLLADA parsing.
///
/// Specializes [`std::result::Result`][std::result::Result] to [`Error`][Error] for the purpose
/// of simplifying the signature of any falible COLLADA operation.
///
/// [std::result::Result]: https://doc.rust-lang.org/std/result/enum.Result.html
/// [Error]: struct.Error.html
pub type Result<T> = std::result::Result<T, Error>;

/// A URI in the COLLADA document.
///
/// Represents the [`xs:anyURI`][anyURI] XML data type.
///
/// [anyURI]: http://www.datypic.com/sc/xsd/t-xsd_anyURI.html
#[derive(Debug, Clone)]
pub struct AnyUri(String);

/// Helper struct for pretty-printing lists of strings.
struct StringListDisplay<'a>(&'a [&'a str]);

impl<'a> Display for StringListDisplay<'a> {
    fn fmt(&self, formatter: &mut Formatter) -> ::std::result::Result<(), fmt::Error> {
        if self.0.len() > 0 {
            write!(formatter, "{}", self.0[0])?;

            for string in &self.0[1..] {
                write!(formatter, ", {}", string)?;
            }
        }

        Ok(())
    }
}
<commit_msg>Add WIP Asset type<commit_after>//! A library for parsing and processing COLLADA documents.
//!
//! [COLLADA][COLLADA] is a COLLAborative Design Activity that defines an XML-based schema to
//! enable 3D authoring applications to freely exchange digital assets. It supports a vast array of
//! features used in 3D modeling, animation, and VFX work, and provides and open, non-proprietary
//! alternative to common formats like [FBX][FBX].
//!
//! This provides functionality for parsing a COLLADA document and utilities for processing the
//! contained data, with the intention of enable direct usage of COLLADA data as well as
//! interchange of document data into other formats.
//!
//! # Quick Start
//!
//! The easiest way to parse a COLLADA document is to load it from a file and use
//! [`Collada::read()`][Collada::read]:
//!
//! ```
//! # #![allow(unused_variables)]
//! use std::fs::File;
//! use parse_collada::Collada;
//!
//! let file = File::open("resources/blender_cube.dae").unwrap();
//! let collada = Collada::read(file).unwrap();
//! ```
//!
//! The resulting [`Collada`][Collada] object provides direct access to all data in the document,
//! directly recreating the logical structure of the document as a Rust type.
//!
//! # COLLADA Versions
//!
//! Currently there are 3 COLLADA versions supported by this library: `1.4.0`, `1.4.1`, and
//! `1.5.0`. Older versions are not supported, but may be added if there is reason to do so. This
//! library attempts to normalize data across versions by "upgrading" all documents to match the
//! `1.5.0` specification. This removes the need for client code to be aware of the specification
//! version used by documents it handles. This conversion is done transparently without the need
//! for user specification.
//!
//! # 3rd Party Extensions
//!
//! The COLLADA format allows for semi-arbitrary extensions to the standard, allowing applications
//! to include application-specific data. This extra data is considered "optional", but may allow
//! applications consuming the COLLADA document to more accurately recreate the scene contained
//! in the document. This library attempts to directly support common 3rd party extensions,
//! primarily those for Blender and Maya. In the case that the 3rd party extension is not
//! directly supported, the underlying XML will be preserved so that the client code can attempt
//! to still use the data.
//!
//! [COLLADA]: https://www.khronos.org/collada/
//! [FBX]: https://en.wikipedia.org/wiki/FBX
//! [Collada]: struct.Collada.html
//! [Collada::read]: struct.Collada.html#method.read

extern crate xml;

pub use xml::common::TextPosition;
pub use xml::reader::Error as XmlError;

use std::io::Read;
use std::fmt::{self, Display, Formatter};
use xml::common::Position;
use xml::EventReader;
use xml::reader::XmlEvent::*;

pub static COLLADA_ATTRIBS: &'static [&'static str] = &["version", "xmlns", "base"];

/// Represents a parsed COLLADA document.
#[derive(Debug, Clone)]
pub struct Collada {
    /// The version string for the COLLADA specification used by the document.
    ///
    /// Only "1.4.0", "1.4.1", and "1.5.0" are supported currently.
    pub version: String,

    /// Global metadata about the COLLADA document.
    pub asset: Asset,

    /// The base uri for any relative URIs in the document.
    ///
    /// Specified by the `base` attribute on the root `<COLLADA>` element.
    base_uri: Option<AnyUri>,
}

impl Collada {
    /// Read a COLLADA document from a string.
    ///
    /// # Examples
    ///
    /// ```
    /// # #![allow(unused_variables)]
    /// use parse_collada::Collada;
    ///
    /// static DOCUMENT: &'static str = r#"
    ///     <?xml version="1.0" encoding="utf-8"?>
    ///     <COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1">
    ///     </COLLADA>
    /// "#;
    ///
    /// let collada = Collada::from_str(DOCUMENT).unwrap();
    /// ```
    ///
    /// # Errors
    ///
    /// Returns `Err` if the document is invalid or malformed in some way. For details about
    /// COLLADA versions, 3rd party extensions, and any other details that could influence how
    /// a document is parsed see the [crate-level documentation][crate].
    ///
    /// [crate]: index.html
    pub fn from_str(source: &str) -> Result<Collada> {
        let reader = EventReader::from_str(source);
        Collada::parse(reader)
    }

    /// Attempts to parse the contents of a COLLADA document.
    ///
    /// # Examples
    ///
    /// ```
    /// # #![allow(unused_variables)]
    /// use std::fs::File;
    /// use parse_collada::Collada;
    ///
    /// let file = File::open("resources/blender_cube.dae").unwrap();
    /// let collada = Collada::read(file).unwrap();
    /// ```
    ///
    /// # Errors
    ///
    /// Returns `Err` if the document is invalid or malformed in some way. For details about
    /// COLLADA versions, 3rd party extensions, and any other details that could influence how
    /// a document is parsed see the [crate-level documentation][crate].
    ///
    /// [crate]: index.html
    pub fn read<R: Read>(reader: R) -> Result<Collada> {
        let reader = EventReader::new(reader);
        Collada::parse(reader)
    }

    /// The logic behind parsing the COLLADA document.
    ///
    /// `from_str()` and `read()` just create the `xml::EventReader` and then defer to `parse()`.
    fn parse<R: Read>(mut reader: EventReader<R>) -> Result<Collada> {
        let mut collada = Collada {
            version: String::new(),
            asset: Asset::default(),
            base_uri: None,
        };

        // Eat the `StartDocument` event. It has no useful information for our purposes, but it
        // will always be the first event emitted, even if there's no XML declaration at the
        // beginning of the document. This is defined as part of the xml-rs API as of v0.3.5,
        // but it's possible this can will change in the future.
        match reader.next()? {
            StartDocument { .. } => {},
            _ => panic!("First event from EventReader wasn't StartDocument"),
        }

        // The next element will always be the `<COLLADA>` tag. This will specify what version of
        // the COLLADA spec is being used, which is how we'll determine our sub-parser.
        match reader.next()? {
            StartElement { name, attributes, namespace: _ } => {
                // If the element isn't the `<COLLADA>` tag then the document is malformed,
                // return an error.
                if name.local_name != "COLLADA" {
                    return Err(Error {
                        position: reader.position(),
                        kind: ErrorKind::UnexpectedRootElement {
                            element: name.local_name,
                        }
                    })
                }

                // Valide the attributes on the `<COLLADA>` tag.
                // Use boolean flags to track if specific attributes have been encountered.
                let mut has_found_version = false;
                let mut has_found_base = false;

                for attribute in attributes {
                    // NOTE: I'm using `if` blocks instead of `match` here because using `match`
                    // won't allow for the name to be moved out of `attribute`. Using `if` saves
                    // some unnecessary allocations. I expect at some point Rust will get smart
                    // enough that this will no longer be an issue, at which point we should
                    // change this to use `match`, as that keeps better with Rust best practices.
                    if attribute.name.local_name == "version" {
                        if !has_found_version {
                            collada.version = attribute.value;
                            has_found_version = true;
                        } else {
                            return Err(Error {
                                position: reader.position(),
                                kind: ErrorKind::UnexpectedAttribute {
                                    element: name.local_name,
                                    attribute: "version".to_owned(),
                                    expected: COLLADA_ATTRIBS,
                                },
                            })
                        }
                    } else if attribute.name.local_name == "base" {
                        if !has_found_base {
                            // TODO: Do we need to validate the URI?
                            collada.base_uri = Some(AnyUri(attribute.value));
                            has_found_base = true;
                        } else {
                            return Err(Error {
                                position: reader.position(),
                                kind: ErrorKind::UnexpectedAttribute {
                                    element: name.local_name,
                                    attribute: "base".to_owned(),
                                    expected: COLLADA_ATTRIBS,
                                },
                            })
                        }
                    } else {
                        return Err(Error {
                            position: reader.position(),
                            kind: ErrorKind::UnexpectedAttribute {
                                element: name.local_name,
                                attribute: attribute.name.local_name,
                                expected: COLLADA_ATTRIBS,
                            },
                        })
                    }
                }

                // Verify that all required attributes have been found.
                if !has_found_version {
                    return Err(Error {
                        position: reader.position(),
                        kind: ErrorKind::MissingAttribute {
                            element: name.local_name,
                            attribute: "version",
                        },
                    })
                }
            }

            // I'm *almost* 100% certain that the only event that can follow the `StartDocument`
            // event is a `StartElement` event. As of v0.3.5, xml-rs doesn't support
            // `<!DOCTYPE>` or processing instructions, and it ignores whitespace and comments
            // (according to how we configure the parser), and those are the only things allowed
            // between `StartDocument` and the first `StartElement`. If xml-rs changes its
            // behavior this will need to be updated.
            event @ _ => panic!("Unexpected event: {:?}", event),
        }

        // Eat any events until we get to the `</COLLADA>` tag.
        // TODO: Actually parse the body of the document.
        loop {
            match reader.next()? {
                EndElement { ref name } if name.local_name == "COLLADA" => { break }
                _ => {}
            }
        }

        // TODO: Verify the next event is the `EndDocument` event.
        match reader.next()? {
            EndDocument => {}

            // Same logic here as with the starting event. The only things that can come after the
            // close tag are comments, white space, and processing instructions, all of which we
            // ignore. This can change with future versions of xml-rs, though.
            event @ _ => { panic!("Unexpected event: {:?}", event); }
        }

        Ok(collada)
    }
}

/// A COLLADA parsing error.
///
/// Contains where in the document the error occurred (i.e. line number and column), and
/// details about the nature of the error.
#[derive(Debug)]
pub struct Error {
    position: TextPosition,
    kind: ErrorKind,
}

impl Error {
    /// Gets the position in the document where the error occurred.
    pub fn position(&self) -> TextPosition { self.position }

    /// Gets detailed information about the error.
    pub fn kind(&self) -> &ErrorKind { &self.kind }
}

impl From<xml::reader::Error> for Error {
    fn from(from: xml::reader::Error) -> Error {
        Error {
            position: from.position(),
            kind: ErrorKind::XmlError(from),
        }
    }
}

impl Display for Error {
    fn fmt(&self, formatter: &mut Formatter) -> ::std::result::Result<(), fmt::Error> {
        write!(formatter, "Error at {}: {}", self.position, self.kind)
    }
}

/// The specific error variant.
#[derive(Debug, PartialEq)]
pub enum ErrorKind {
    /// An element was missing a required attribute.
    MissingAttribute {
        /// The element that was missing an attribute.
        element: String,

        /// The attribute that expected to be present.
        attribute: &'static str,
    },

    /// An element had an attribute that isn't allowed.
    UnexpectedAttribute {
        /// The element that had the unexpected attribute.
        element: String,

        /// The unexpected attribute.
        attribute: String,

        /// The set of attributes allowed for this element.
        expected: &'static [&'static str],
    },

    /// An element had a child element that wasn't allowed.
    UnexpectedElement {
        parent: String,
        element: String,
        expected: &'static [&'static str],
    },

    /// The document started with an element other than `<COLLADA>`.
    UnexpectedRootElement {
        element: String,
    },

    /// The XML in the document was malformed in some way.
    XmlError(XmlError),
}

impl Display for ErrorKind {
    fn fmt(&self, formatter: &mut Formatter) -> ::std::result::Result<(), fmt::Error> {
        match *self {
            ErrorKind::MissingAttribute { ref element, ref attribute } => {
                write!(formatter, "<{}> is missing the required attribute \"{}\"", element, attribute)
            }

            ErrorKind::UnexpectedAttribute { ref element, ref attribute, expected } => {
                write!(
                    formatter,
                    "<{}> had an an attribute \"{}\" that is not allowed, only the following attributes are allowed for <{0}>: {}",
                    element,
                    attribute,
                    StringListDisplay(expected),
                )
            }

            ErrorKind::UnexpectedElement { ref parent, ref element, expected } => {
                write!(
                    formatter,
                    "<{}> had a child <{}> which is not allowed, <{0}> may only have the following children: {}",
                    parent,
                    element,
                    StringListDisplay(expected),
                )
            }

            ErrorKind::UnexpectedRootElement { ref element } => {
                write!(formatter, "Document began with <{}> instead of <COLLADA>", element)
            }

            ErrorKind::XmlError(ref error) => {
                write!(formatter, "{}", error.msg())
            }
        }
    }
}

/// A specialized result type for COLLADA parsing.
///
/// Specializes [`std::result::Result`][std::result::Result] to [`Error`][Error] for the purpose
/// of simplifying the signature of any falible COLLADA operation.
///
/// [std::result::Result]: https://doc.rust-lang.org/std/result/enum.Result.html
/// [Error]: struct.Error.html
pub type Result<T> = std::result::Result<T, Error>;

/// A URI in the COLLADA document.
///
/// Represents the [`xs:anyURI`][anyURI] XML data type.
///
/// [anyURI]: http://www.datypic.com/sc/xsd/t-xsd_anyURI.html
#[derive(Debug, Clone)]
pub struct AnyUri(String);

/// Helper struct for pretty-printing lists of strings.
struct StringListDisplay<'a>(&'a [&'a str]);

impl<'a> Display for StringListDisplay<'a> {
    fn fmt(&self, formatter: &mut Formatter) -> ::std::result::Result<(), fmt::Error> {
        if self.0.len() > 0 {
            write!(formatter, "{}", self.0[0])?;

            for string in &self.0[1..] {
                write!(formatter, ", {}", string)?;
            }
        }

        Ok(())
    }
}

/// Asset-management information about an element.
///
/// Includes both asset metadata, such as a list of contributors and keywords, as well
/// as functional information, such as units of distance and the up axis for the asset.
#[derive(Debug, Clone, Default)]
pub struct Asset {
    // /// The list of contributors to this asset.
    // contributors: Vec<Contributor>,
}
<|endoftext|>


<commit_before>#![allow(non_snake_case)]

#[derive(Debug)]
struct Queue<T> {
    data: Vec<Option<T>>
}

impl<T> Queue<T> {

    fn new() -> Queue<T> {
        Queue {
            data: Vec::new()
        }
    }

    fn enqueue(&mut self, val: T) {

        self.data.push(Some(val));
    }

    fn dequeue(&mut self) -> Option<T> {

        self.data.remove(0)
    }
}

pub fn test() {

    #[derive(PartialEq, Eq, Debug)]
    struct Test {
        value: i8
    }

    let a = Test {value: 1};
    let b = Test {value: 2};
    let c = Test {value: 3};

    let mut queue = Queue::<Test>::new();

    println!("{:?}", queue);

    queue.enqueue(a);
    queue.enqueue(b);
    queue.enqueue(c);

    println!("{:?}", queue);

    queue.dequeue();

    println!("{:?}", queue);

    queue.dequeue();

    println!("{:?}", queue);

    queue.dequeue();

    println!("{:?}", queue);
}<commit_msg>fix: queue中vec越界<commit_after>#![allow(non_snake_case)]

#[derive(Debug)]
struct Queue<T> {
    data: Vec<Option<T>>
}

impl<T> Queue<T> {

    fn new() -> Queue<T> {
        Queue {
            data: Vec::new()
        }
    }

    fn enqueue(&mut self, val: T) {

        self.data.push(Some(val));
    }

    fn dequeue(&mut self) -> Option<T> {

        if self.data.len() > 0 {

            self.data.remove(0)

        } else {

            None
        }
    }
}

pub fn test() {

    #[derive(PartialEq, Eq, Debug)]
    struct Test {
        value: i8
    }

    let a = Test {value: 1};
    let b = Test {value: 2};
    let c = Test {value: 3};

    let mut queue = Queue::<Test>::new();

    queue.enqueue(a);
    queue.enqueue(b);
    queue.enqueue(c);

    println!("{:?}", queue);

    queue.dequeue();

    println!("{:?}", queue);

    queue.dequeue();

    println!("{:?}", queue);

    queue.dequeue();

    println!("{:?}", queue);

    queue.dequeue();

    println!("{:?}", queue);
}<|endoftext|>


<commit_before>use std::collections::HashMap;
use nix::libc::{pid_t, c_long};
use nix::Result;
use ptrace_control::*;

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const INT: u64 = 0xCC;

/// Breakpoint construct used to monitor program execution. As tarpaulin is an
/// automated process, this will likely have less functionality than most
/// breakpoint implementations.
#[derive(Debug)]
pub struct Breakpoint {
    /// Program counter
    pub pc: u64,
    /// Bottom byte of address data.
    /// This is replaced to enable the interrupt. Rest of data is never changed.
    data: u8,
    /// Reading from memory with ptrace gives addresses aligned to bytes.
    /// We therefore need to know the shift to place the breakpoint in the right place
    shift: u64,
    /// When execution pauses.
    is_running: HashMap<pid_t, bool>
}

impl Breakpoint {
    /// Creates a new breakpoint for the given process and program counter.
    pub fn new(pid:pid_t, pc:u64) ->Result<Breakpoint> {
        let aligned = pc & !0x7u64;
        let data = read_address(pid, aligned)?;
        let shift = 8 * (pc - aligned);
        let data = ((data >> shift) & 0xFF) as u8;

        let mut b = Breakpoint{
            pc: pc,
            data: data,
            shift: shift,
            is_running: HashMap::new(),
        };
        match b.enable(pid) {
            Ok(_) => Ok(b),
            Err(e) => Err(e)
        }
    }

    /// Attaches the current breakpoint.
    pub fn enable(&mut self, pid: pid_t) -> Result<c_long> {
        let data  = read_address(pid, self.aligned_address())?;
        self.is_running.insert(pid, true);
        let mut intdata = data & (!(0xFFu64 << self.shift) as i64);
        intdata |= (INT << self.shift) as i64;
        write_to_address(pid, self.aligned_address(), intdata)
    }

    fn disable(&self, pid: pid_t) -> Result<c_long> {
        // I require the bit fiddlin this end.
        let data = read_address(pid, self.aligned_address())?;
        let mut orgdata = data & (!(0xFFu64 << self.shift) as i64);
        orgdata |= (self.data as i64) << self.shift;
        write_to_address(pid, self.aligned_address(), orgdata)
    }

    /// Processes the breakpoint. This steps over the breakpoint
    pub fn process(&mut self, pid: pid_t) -> Result<bool> {
        let is_running = match self.is_running.get(&pid) {
            Some(r) => *r,
            None => true,
        };
        if is_running {
            self.step(pid)?;
            self.is_running.insert(pid, false);
            Ok(true)
        } else {
            self.enable(pid)?;
            continue_exec(pid, None)?;
            self.is_running.insert(pid, true);
            Ok(false)
        }
    }

    /// Steps past the current breakpoint.
    /// For more advanced coverage may interrogate the variables of a branch.
    fn step(&mut self, pid: pid_t) -> Result<c_long> {
        // Remove the breakpoint, reset the program counter to step before it
        // hit the breakpoint then step to execute the original instruction.
        self.disable(pid)?;
        // Need to set the program counter back one.
        set_instruction_pointer(pid, self.pc)?;
        single_step(pid)
    }

    fn aligned_address(&self) -> u64 {
        self.pc & !0x7u64
    }
}
<commit_msg>Tweaked multithreaded-bp support.<commit_after>use std::collections::HashMap;
use nix::libc::{pid_t, c_long};
use nix::Result;
use ptrace_control::*;

#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const INT: u64 = 0xCC;

/// Breakpoint construct used to monitor program execution. As tarpaulin is an
/// automated process, this will likely have less functionality than most
/// breakpoint implementations.
#[derive(Debug)]
pub struct Breakpoint {
    /// Program counter
    pub pc: u64,
    /// Bottom byte of address data.
    /// This is replaced to enable the interrupt. Rest of data is never changed.
    data: u8,
    /// Reading from memory with ptrace gives addresses aligned to bytes.
    /// We therefore need to know the shift to place the breakpoint in the right place
    shift: u64,
    /// When execution pauses.
    is_running: HashMap<pid_t, bool>
}

impl Breakpoint {
    /// Creates a new breakpoint for the given process and program counter.
    pub fn new(pid:pid_t, pc:u64) ->Result<Breakpoint> {
        let aligned = pc & !0x7u64;
        let data = read_address(pid, aligned)?;
        let shift = 8 * (pc - aligned);
        let data = ((data >> shift) & 0xFF) as u8;

        let mut b = Breakpoint{
            pc: pc,
            data: data,
            shift: shift,
            is_running: HashMap::new(),
        };
        match b.enable(pid) {
            Ok(_) => Ok(b),
            Err(e) => Err(e)
        }
    }

    /// Attaches the current breakpoint.
    pub fn enable(&mut self, pid: pid_t) -> Result<c_long> {
        let data  = read_address(pid, self.aligned_address())?;
        self.is_running.insert(pid, true);
        let mut intdata = data & (!(0xFFu64 << self.shift) as i64);
        intdata |= (INT << self.shift) as i64;
        write_to_address(pid, self.aligned_address(), intdata)
    }

    fn disable(&self, pid: pid_t) -> Result<c_long> {
        // I require the bit fiddlin this end.
        let data = read_address(pid, self.aligned_address())?;
        let mut orgdata = data & (!(0xFFu64 << self.shift) as i64);
        orgdata |= (self.data as i64) << self.shift;
        write_to_address(pid, self.aligned_address(), orgdata)
    }

    /// Processes the breakpoint. This steps over the breakpoint
    pub fn process(&mut self, pid: pid_t) -> Result<bool> {
        let is_running = match self.is_running.get(&pid) {
            Some(r) => *r,
            None => true,
        };
        if is_running {
            self.enable(pid)?;
            self.step(pid)?;
            self.is_running.insert(pid, false);
            Ok(true)
        } else {
            self.disable(pid)?;
            self.enable(pid)?;
            single_step(pid)?;
            self.is_running.insert(pid, true);
            Ok(false)
        }
    }

    /// Steps past the current breakpoint.
    /// For more advanced coverage may interrogate the variables of a branch.
    fn step(&mut self, pid: pid_t) -> Result<c_long> {
        // Remove the breakpoint, reset the program counter to step before it
        // hit the breakpoint then step to execute the original instruction.
        self.disable(pid)?;
        // Need to set the program counter back one.
        set_instruction_pointer(pid, self.pc)?;
        single_step(pid)
    }

    fn aligned_address(&self) -> u64 {
        self.pc & !0x7u64
    }
}
<|endoftext|>


<commit_before>use std::fmt;

/// `Rank` is a type the represents the rank of a playing card.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Rank {
    Ace,
    Two,
    Three,
    Four,
    Five,
    Six,
    Seven,
    Eight,
    Nine,
    Ten,
    Jack,
    Queen,
    King,
}

/// `Suit` is a type the represents the suit of a playing card.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Suit {
    Hearts,
    Clubs,
    Diamonds,
    Spades,
}

/// `Card` is a struct that holds the [`Rank`] and [`Suit`] type of a playing card.
///
/// [`Rank`]: enum.Rank.html
/// [`Suit`]: enum.Suit.html
#[derive(Debug, PartialEq, Eq)]
pub struct Card {
    rank: Rank,
    suit: Suit,
}

impl Card {
    /// Constructs a new `Card`.
    ///
    /// # Examples
    ///
    /// ```
    /// use libterminal_cribbage::cards::{Card, Rank, Suit};
    ///
    /// let playing_card = Card::new(Rank::Ace, Suit::Spades);
    ///
    /// println!("Played card: {}", playing_card);
    /// ```
    pub fn new(rank: Rank, suit: Suit) -> Card {
        Card {
            rank: rank,
            suit: suit,
        }
    }
}

impl fmt::Display for Card {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        let rank_str = match self.rank {
            Rank::Two => "2",
            Rank::Three => "3",
            Rank::Four => "4",
            Rank::Five => "5",
            Rank::Six => "6",
            Rank::Seven => "7",
            Rank::Eight => "8",
            Rank::Nine => "9",
            Rank::Ten => "10",
            Rank::Jack => "J",
            Rank::Queen => "Q",
            Rank::King => "K",
            Rank::Ace => "A",
        };

        let suit_char = match self.suit {
            Suit::Hearts => '\u{2665}',
            Suit::Clubs => '\u{2663}',
            Suit::Diamonds => '\u{2666}',
            Suit::Spades => '\u{2660}',
        };

        write!(formatter, "[{}{}]", rank_str, suit_char)
    }
}

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

    #[test]
    fn test_new() {
        let test_card = Card::new(Rank::Ace, Suit::Clubs);

        assert_eq!(test_card.rank, Rank::Ace);
        assert_eq!(test_card.suit, Suit::Clubs);
    }
}<commit_msg>Remove Eq derivation from Card and Suit.<commit_after>use std::fmt;

/// `Rank` is a type the represents the rank of a playing card.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum Rank {
    Ace,
    Two,
    Three,
    Four,
    Five,
    Six,
    Seven,
    Eight,
    Nine,
    Ten,
    Jack,
    Queen,
    King,
}

/// `Suit` is a type the represents the suit of a playing card.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum Suit {
    Hearts,
    Clubs,
    Diamonds,
    Spades,
}

/// `Card` is a struct that holds the [`Rank`] and [`Suit`] type of a playing card.
///
/// [`Rank`]: enum.Rank.html
/// [`Suit`]: enum.Suit.html
#[derive(Debug, PartialEq)]
pub struct Card {
    rank: Rank,
    suit: Suit,
}

impl Card {
    /// Constructs a new `Card`.
    ///
    /// # Examples
    ///
    /// ```
    /// use libterminal_cribbage::cards::{Card, Rank, Suit};
    ///
    /// let playing_card = Card::new(Rank::Ace, Suit::Spades);
    ///
    /// println!("Played card: {}", playing_card);
    /// ```
    pub fn new(rank: Rank, suit: Suit) -> Card {
        Card {
            rank: rank,
            suit: suit,
        }
    }
}

impl fmt::Display for Card {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        let rank_str = match self.rank {
            Rank::Two => "2",
            Rank::Three => "3",
            Rank::Four => "4",
            Rank::Five => "5",
            Rank::Six => "6",
            Rank::Seven => "7",
            Rank::Eight => "8",
            Rank::Nine => "9",
            Rank::Ten => "10",
            Rank::Jack => "J",
            Rank::Queen => "Q",
            Rank::King => "K",
            Rank::Ace => "A",
        };

        let suit_char = match self.suit {
            Suit::Hearts => '\u{2665}',
            Suit::Clubs => '\u{2663}',
            Suit::Diamonds => '\u{2666}',
            Suit::Spades => '\u{2660}',
        };

        write!(formatter, "[{}{}]", rank_str, suit_char)
    }
}

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

    #[test]
    fn test_new() {
        let test_card = Card::new(Rank::Ace, Suit::Clubs);

        assert_eq!(test_card.rank, Rank::Ace);
        assert_eq!(test_card.suit, Suit::Clubs);
    }
}<|endoftext|>


<commit_before>extern crate git2;

use git2::*;
use std::path::Path;
use scratch::Scratch;
use super::Hooks;
use super::ReviewUploadResult;
use scratch::module_ref;

pub struct CentralGit;

impl Hooks for CentralGit
{
    fn review_upload(&self, scratch: &Scratch, newrev: Object, module: &str) -> ReviewUploadResult
    {
        debug!(".\n\n==== Doing review upload for module {}", &module);

        let new = newrev.id();
        let old = scratch.tracking(&module, "master").expect("no tracking branch 1").id();

        if old == new {
            return ReviewUploadResult::NoChanges;
        }

        match scratch.repo.graph_descendant_of(new, old) {
            Err(_) => return ReviewUploadResult::RejectNoFF,
            Ok(false) => return ReviewUploadResult::RejectNoFF,
            Ok(true) => (),
        }

        debug!("==== walking commits from {} to {}", old, new);

        let walk = {
            let mut walk = scratch.repo.revwalk().expect("walk: can't create revwalk");
            walk.set_sorting(SORT_REVERSE | SORT_TIME);
            let range = format!("{}..{}", old, new);
            walk.push_range(&range).expect(&format!("walk: invalid range: {}", range));;
            walk
        };

        let mut current =
            scratch.tracking(scratch.host.central(), "master").expect("no central tracking").id();

        for rev in walk {
            let rev = rev.expect("walk: invalid rev");
            if old == rev {
                continue;
            }

            debug!("==== walking commit {}", rev);

            let module_commit = scratch.repo
                .find_commit(rev)
                .expect("walk: object is not actually a commit");

            if module_commit.parents().count() > 1 {
                // TODO: also do this check on pushes to cenral refs/for/master
                // TODO: invectigate the possibility of allowing merge commits
                return ReviewUploadResult::RejectMerge;
            }

            if module != scratch.host.central() {
                debug!("==== Rewriting commit {}", rev);

                let tree = module_commit.tree().expect("walk: commit has no tree");
                let parent =
                    scratch.repo.find_commit(current).expect("walk: current object is no commit");

                let new_tree = scratch.replace_subtree(Path::new(module),
                                                       tree.id(),
                                                       parent.tree()
                                                           .expect("walk: parent has no tree"));

                current = scratch.rewrite(&module_commit, &vec![&parent], &new_tree);
            }
        }

        if module != scratch.host.central() {
            return ReviewUploadResult::Uploaded(current);
        }
        else {
            return ReviewUploadResult::Central;
        }
    }

    fn pre_create_project(&self, scratch: &Scratch, rev: Oid, project: &str)
    {
        if let Ok(_) = scratch.repo.revparse_single(&module_ref(project)) {}
        else {
                let commit = scratch.split_subdir(&project, rev);
                scratch.repo
                    .reference(&module_ref(project),
                               commit.id(),
                               true,
                               "subtree_split")
                    .expect("can't create reference");
            }
    }

    fn project_created(&self, scratch: &Scratch, _project: &str)
    {
        if let Some(rev) = scratch.tracking(scratch.host.central(), "master") {
            self.central_submit(scratch, rev);
        }
    }

    fn central_submit(&self, scratch: &Scratch, newrev: Object)
    {
        debug!(" ---> central_submit (sha1 of commit: {})", &newrev.id());

        let central_commit = newrev.as_commit().expect("could not get commit from obj");
        let central_tree = central_commit.tree().expect("commit has no tree");

        for module in scratch.host.projects() {
            if module == scratch.host.central() {
                continue;
            }
            debug!("");
            debug!("==== fetching tracking branch for module: {}", &module);
            match scratch.tracking(&module, "master") {
                Some(_) => (),
                None => {
                    debug!("====    no tracking branch for module {} => project does not exist \
                            or is empty",
                           &module);
                    debug!("====    initializing with subdir history");

                    self.pre_create_project(scratch, newrev.id(), &module);
                }
            };

            let module_master_commit_obj = scratch.repo
                .revparse_single(&module_ref(&module))
                .expect("can't get module_ref");

            let parents = vec![module_master_commit_obj.as_commit()
                                   .expect("could not get commit from obj")];

            debug!("==== checking for changes in module: {:?}", module);

            // new tree is sub-tree of complete central tree
            let old_tree_id = if let Ok(tree) = parents[0].tree() {
                tree.id()
            }
            else {
                Oid::from_str("0000000000000000000000000000000000000000").unwrap()
            };

            let new_tree_id = if let Ok(tree_entry) = central_tree.get_path(&Path::new(&module)) {
                tree_entry.id()
            }
            else {
                Oid::from_str("0000000000000000000000000000000000000000").unwrap()
            };

            // if sha1's are equal the content is equal
            if new_tree_id != old_tree_id && !new_tree_id.is_zero() {
                let new_tree =
                    scratch.repo.find_tree(new_tree_id).expect("central_submit: can't find tree");
                debug!("====    commit changes module => make commit on module");
                let module_commit = scratch.rewrite(central_commit, &parents, &new_tree);
                scratch.repo
                    .reference(&module_ref(&module),
                               module_commit,
                               true,
                               "rewrite")
                    .expect("can't create reference");
            }
            else {
                debug!("====    commit does not change module => skipping");
            }
        }

        for module in scratch.host.projects() {
            if let Ok(module_commit) = scratch.repo.revparse_single(&module_ref(&module)) {
                let output = scratch.push(module_commit.id(), &module, "refs/heads/master");
                debug!("{}", output);
            }
        }
    }
}
<commit_msg>use SORT_TOPOLOGICAL<commit_after>extern crate git2;

use git2::*;
use std::path::Path;
use scratch::Scratch;
use super::Hooks;
use super::ReviewUploadResult;
use scratch::module_ref;

pub struct CentralGit;

impl Hooks for CentralGit
{
    fn review_upload(&self, scratch: &Scratch, newrev: Object, module: &str) -> ReviewUploadResult
    {
        debug!(".\n\n==== Doing review upload for module {}", &module);

        let new = newrev.id();
        let old = scratch.tracking(&module, "master").expect("no tracking branch 1").id();

        if old == new {
            return ReviewUploadResult::NoChanges;
        }

        match scratch.repo.graph_descendant_of(new, old) {
            Err(_) => return ReviewUploadResult::RejectNoFF,
            Ok(false) => return ReviewUploadResult::RejectNoFF,
            Ok(true) => (),
        }

        debug!("==== walking commits from {} to {}", old, new);

        let walk = {
            let mut walk = scratch.repo.revwalk().expect("walk: can't create revwalk");
            walk.set_sorting(SORT_REVERSE | SORT_TOPOLOGICAL);
            let range = format!("{}..{}", old, new);
            walk.push_range(&range).expect(&format!("walk: invalid range: {}", range));;
            walk
        };

        let mut current =
            scratch.tracking(scratch.host.central(), "master").expect("no central tracking").id();

        for rev in walk {
            let rev = rev.expect("walk: invalid rev");
            if old == rev {
                continue;
            }

            debug!("==== walking commit {}", rev);

            let module_commit = scratch.repo
                .find_commit(rev)
                .expect("walk: object is not actually a commit");

            if module_commit.parents().count() > 1 {
                // TODO: also do this check on pushes to cenral refs/for/master
                // TODO: invectigate the possibility of allowing merge commits
                return ReviewUploadResult::RejectMerge;
            }

            if module != scratch.host.central() {
                debug!("==== Rewriting commit {}", rev);

                let tree = module_commit.tree().expect("walk: commit has no tree");
                let parent =
                    scratch.repo.find_commit(current).expect("walk: current object is no commit");

                let new_tree = scratch.replace_subtree(Path::new(module),
                                                       tree.id(),
                                                       parent.tree()
                                                           .expect("walk: parent has no tree"));

                current = scratch.rewrite(&module_commit, &vec![&parent], &new_tree);
            }
        }

        if module != scratch.host.central() {
            return ReviewUploadResult::Uploaded(current);
        }
        else {
            return ReviewUploadResult::Central;
        }
    }

    fn pre_create_project(&self, scratch: &Scratch, rev: Oid, project: &str)
    {
        if let Ok(_) = scratch.repo.revparse_single(&module_ref(project)) {}
        else {
                let commit = scratch.split_subdir(&project, rev);
                scratch.repo
                    .reference(&module_ref(project),
                               commit.id(),
                               true,
                               "subtree_split")
                    .expect("can't create reference");
            }
    }

    fn project_created(&self, scratch: &Scratch, _project: &str)
    {
        if let Some(rev) = scratch.tracking(scratch.host.central(), "master") {
            self.central_submit(scratch, rev);
        }
    }

    fn central_submit(&self, scratch: &Scratch, newrev: Object)
    {
        debug!(" ---> central_submit (sha1 of commit: {})", &newrev.id());

        let central_commit = newrev.as_commit().expect("could not get commit from obj");
        let central_tree = central_commit.tree().expect("commit has no tree");

        for module in scratch.host.projects() {
            if module == scratch.host.central() {
                continue;
            }
            debug!("");
            debug!("==== fetching tracking branch for module: {}", &module);
            match scratch.tracking(&module, "master") {
                Some(_) => (),
                None => {
                    debug!("====    no tracking branch for module {} => project does not exist \
                            or is empty",
                           &module);
                    debug!("====    initializing with subdir history");

                    self.pre_create_project(scratch, newrev.id(), &module);
                }
            };

            let module_master_commit_obj = scratch.repo
                .revparse_single(&module_ref(&module))
                .expect("can't get module_ref");

            let parents = vec![module_master_commit_obj.as_commit()
                                   .expect("could not get commit from obj")];

            debug!("==== checking for changes in module: {:?}", module);

            // new tree is sub-tree of complete central tree
            let old_tree_id = if let Ok(tree) = parents[0].tree() {
                tree.id()
            }
            else {
                Oid::from_str("0000000000000000000000000000000000000000").unwrap()
            };

            let new_tree_id = if let Ok(tree_entry) = central_tree.get_path(&Path::new(&module)) {
                tree_entry.id()
            }
            else {
                Oid::from_str("0000000000000000000000000000000000000000").unwrap()
            };

            // if sha1's are equal the content is equal
            if new_tree_id != old_tree_id && !new_tree_id.is_zero() {
                let new_tree =
                    scratch.repo.find_tree(new_tree_id).expect("central_submit: can't find tree");
                debug!("====    commit changes module => make commit on module");
                let module_commit = scratch.rewrite(central_commit, &parents, &new_tree);
                scratch.repo
                    .reference(&module_ref(&module),
                               module_commit,
                               true,
                               "rewrite")
                    .expect("can't create reference");
            }
            else {
                debug!("====    commit does not change module => skipping");
            }
        }

        for module in scratch.host.projects() {
            if let Ok(module_commit) = scratch.repo.revparse_single(&module_ref(&module)) {
                let output = scratch.push(module_commit.id(), &module, "refs/heads/master");
                debug!("{}", output);
            }
        }
    }
}
<|endoftext|>


<commit_before>use super::super::services::collection_service;
use super::super::rocket_contrib::JSON;
use super::super::models::{Collection, NewCollection, Image};
use std::error::Error;
use rocket::response::status;

#[get("/collections")]
fn get_collections() -> Result<JSON<Vec<Collection>>, Box<Error>> {
    let result = try!(collection_service::get_all());
    Ok(JSON(result))
}

#[get("/collections/<id>")]
fn get_collection(id: i32) -> Option<JSON<Collection>> {
    match collection_service::find(id) {
        Ok(t) => Some(JSON(t)),
        Err(_) => None,
    }
}

#[post("/collections/new?<new_collection>")]
fn new(new_collection: NewCollection) -> Result<status::Created<JSON<Collection>>, Box<Error>> {
    let result = try!(collection_service::create(&new_collection));
    Ok(status::Created(
        format!("/collections/{}", result.id),
        Some(JSON(result)),
    ))
}

#[put("/collections/update?<collection>")]
fn update(collection: Collection) -> Option<JSON<Collection>> {
    match collection_service::update(&collection) {
        Ok(t) => Some(JSON(t)),
        Err(_) => None,
    }
}

#[post("/collections/<collection_id>/images/<image_id>")]
fn new_image_collection(image_id: i32, collection_id: i32) -> Option<status::NoContent> {
    match collection_service::add_image_to_collection(image_id, collection_id) {
        Ok(_) => Some(status::NoContent),
        Err(_) => None,
    }
}

#[get("/images/<id>/tags")]
fn get_images_of_collection(id: i32) -> Option<JSON<Vec<Image>>> {
    match collection_service::get_images_of_collection(id) {
        Ok(t) => Some(JSON(t)),
        Err(_) => None,
    }
}
<commit_msg>Fix false route def<commit_after>use super::super::services::collection_service;
use super::super::rocket_contrib::JSON;
use super::super::models::{Collection, NewCollection, Image};
use std::error::Error;
use rocket::response::status;

#[get("/collections")]
fn get_collections() -> Result<JSON<Vec<Collection>>, Box<Error>> {
    let result = try!(collection_service::get_all());
    Ok(JSON(result))
}

#[get("/collections/<id>")]
fn get_collection(id: i32) -> Option<JSON<Collection>> {
    match collection_service::find(id) {
        Ok(t) => Some(JSON(t)),
        Err(_) => None,
    }
}

#[post("/collections/new?<new_collection>")]
fn new(new_collection: NewCollection) -> Result<status::Created<JSON<Collection>>, Box<Error>> {
    let result = try!(collection_service::create(&new_collection));
    Ok(status::Created(
        format!("/collections/{}", result.id),
        Some(JSON(result)),
    ))
}

#[put("/collections/update?<collection>")]
fn update(collection: Collection) -> Option<JSON<Collection>> {
    match collection_service::update(&collection) {
        Ok(t) => Some(JSON(t)),
        Err(_) => None,
    }
}

#[post("/collections/<collection_id>/images/<image_id>")]
fn new_image_collection(image_id: i32, collection_id: i32) -> Option<status::NoContent> {
    match collection_service::add_image_to_collection(image_id, collection_id) {
        Ok(_) => Some(status::NoContent),
        Err(_) => None,
    }
}

#[get("/collections/<id>/images")]
fn get_images_of_collection(id: i32) -> Option<JSON<Vec<Image>>> {
    match collection_service::get_images_of_collection(id) {
        Ok(t) => Some(JSON(t)),
        Err(_) => None,
    }
}
<|endoftext|>


<commit_before>// Copyright 2015 Nathan Sizemore <nathanrsizemore@gmail.com>
//
// 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
// http://mozilla.org/MPL/2.0/.
//
// This Source Code Form is "Incompatible
// With Secondary Licenses", as defined by
// the Mozilla Public License, v. 2.0.

use std::{str, ptr};
use std::ffi::{CStr, CString};
use std::thread;
use std::net::TcpStream;
use std::sync::mpsc::{channel, Sender, Receiver};

use super::libc::{c_int, c_char};
use super::simple_stream::bstream::Bstream;

extern "C" {
    fn register_writer_tx(tx: *mut Sender<Vec<u8>>);
    fn register_stop_tx(tx: *mut Sender<()>);
}

#[no_mangle]
pub extern "C" fn testing_fn() -> c_int {
    println!("Hello from Rust :)");
    78 as c_int
}

/// Connects to the provided address, (eg "123.123.123.123:3000") and registers
/// the on data received handler
#[no_mangle]
pub extern "C" fn connect(address: *const c_char,
    handler: extern fn(*const c_char, c_int),
    on_connect_handler: extern fn(),
    on_disconnect_handler: extern fn()) -> c_int {

    println!("Rust - connect");

    78 as c_int

    // let mut r_address;
    // unsafe {
    //     r_address = CStr::from_ptr(address);
    // }
    // let s_address = r_address.to_bytes();
    // let host_address = match str::from_utf8(s_address) {
    //     Ok(safe_str) => safe_str,
    //     Err(_) => {
    //         println!("Invalid host address");
    //         return -1 as c_int;
    //     }
    // };
    //
    // // Create and register a way to kill this client
    // let (k_tx, kill_rx): (Sender<()>, Receiver<()>) = channel();
    // let kill_tx = k_tx.clone();
    // let mut k_tx_ptr = Box::new(k_tx);
    // unsafe {
    //     register_stop_tx(&mut *k_tx_ptr);
    // }
    //
    // // Writer thread's channel
    // let (w_tx, w_rx): (Sender<Vec<u8>>, Receiver<Vec<u8>>) = channel();
    // let mut w_tx_ptr = Box::new(w_tx);
    // unsafe {
    //     register_writer_tx(&mut *w_tx_ptr);
    // }
    //
    // println!("Attempting connect to: {}", host_address);
    //
    // let result = TcpStream::connect(host_address);
    // if result.is_err() {
    //     println!("Error connecting to {} - {}", host_address, result.unwrap_err());
    //     return -1 as c_int;
    // }
    // println!("Connected");
    // on_connect_handler();
    //
    // let stream = result.unwrap();
    // let client = Bstream::new(stream);
    //
    // let r_client = client.clone();
    // let r_kill_tx = kill_tx.clone();
    //
    // let w_client = client.clone();
    // let w_kill_tx = kill_tx.clone();
    //
    // // Start the reader thread
    // thread::Builder::new()
    //     .name("ReaderThread".to_string())
    //     .spawn(move||{
    //         reader_thread(r_client, handler, r_kill_tx)
    //     }).unwrap();
    //
    // // Start the writer thread
    // thread::Builder::new()
    //     .name("WriterThread".to_string())
    //     .spawn(move||{
    //         writer_thread(w_rx, w_client, w_kill_tx)
    //     }).unwrap();
    //
    // // Wait for the kill signal
    // match kill_rx.recv() {
    //     Ok(_) => { }
    //     Err(e) => {
    //         println!("Error on kill channel: {}", e);
    //         return -1 as c_int;
    //     }
    // };
    // on_disconnect_handler();
    //
    // // Exit out in standard C fashion
    // 0 as c_int
}

/// Writes the complete contents of buffer to the server
/// Returns -1 on error
#[no_mangle]
pub extern "C" fn send_to_writer(w_tx: *mut Sender<Vec<u8>>,
                                 buffer: *const c_char,
                                 count: c_int,
                                 k_tx: *mut Sender<()>) -> c_int {
    if count < 1 {
        println!("Error - count must be greater than zero");
        return -1 as c_int;
    }

    let num_elts = count as usize;
    let mut n_buffer = Vec::<u8>::with_capacity(num_elts);
    for x in 0..num_elts as isize {
        unsafe {
            n_buffer.push(ptr::read(buffer.offset(x)) as u8);
        }
    }

    unsafe {
        match (*w_tx).send(n_buffer) {
            Ok(_) => { }
            Err(e) => {
                println!("Error sending buffer: {}", e);
                let _ = (*k_tx).send(());
                return -1 as c_int;
            }
        };
    }

    0 as c_int
}

/// Forever listens to incoming data and when a complete message is received,
/// the passed callback is hit
fn reader_thread(client: Bstream,
                 event_handler: extern fn(*const c_char, c_int),
                 kill_tx: Sender<()>) {

    let mut reader = client.clone();
    loop {
        match reader.read() {
            Ok(buffer) => {
                // Launch the handler in a new thread
                thread::Builder::new()
                    .name("Reader-Worker".to_string())
                    .spawn(move||{
                        let slice = &buffer[..];
                        let c_buffer = CString::new(slice).unwrap();
                        event_handler(c_buffer.as_ptr(), buffer.len() as c_int);
                    }).unwrap();
            }
            Err(e) => {
                println!("Error: {}", e);
                break;
            }
        };
    }
    println!("Reader thread finished");
    let _ = kill_tx.send(());
}

/// Forever listens to Receiver<Vec<u8>> waiting on messages to come in
/// Once available, blocks until the entire message has been written
fn writer_thread(rx: Receiver<Vec<u8>>, client: Bstream, kill_tx: Sender<()>) {
    let mut writer = client.clone();
    loop {
        match rx.recv() {
            Ok(ref mut buffer) => {
                match writer.write(buffer) {
                    Ok(_) => { }
                    Err(e) => {
                        println!("Error: {}", e);
                        break;
                    }
                };
            }
            Err(e) => {
                println!("Error: {}", e);
                break;
            }
        };
    }
    println!("Writer thread finished");
    let _ = kill_tx.send(());
}
<commit_msg>Debugging<commit_after>// Copyright 2015 Nathan Sizemore <nathanrsizemore@gmail.com>
//
// 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
// http://mozilla.org/MPL/2.0/.
//
// This Source Code Form is "Incompatible
// With Secondary Licenses", as defined by
// the Mozilla Public License, v. 2.0.

use std::{str, ptr};
use std::ffi::{CStr, CString};
use std::thread;
use std::net::TcpStream;
use std::sync::mpsc::{channel, Sender, Receiver};

use super::libc::{c_int, c_char};
use super::simple_stream::bstream::Bstream;

extern "C" {
    fn register_writer_tx(tx: *mut Sender<Vec<u8>>);
    fn register_stop_tx(tx: *mut Sender<()>);
}

/// Connects to the provided address, (eg "123.123.123.123:3000") and registers
/// the on data received handler
#[no_mangle]
pub extern "C" fn connect(address: *const c_char,
    handler: extern fn(*const c_char, c_int),
    on_connect_handler: extern fn(),
    on_disconnect_handler: extern fn()) -> c_int {

    println!("Rust - connect");

    let mut r_address;
    unsafe {
        r_address = CStr::from_ptr(address);
    }
    let s_address = r_address.to_bytes();
    let host_address = match str::from_utf8(s_address) {
        Ok(safe_str) => safe_str,
        Err(_) => {
            println!("Invalid host address");
            return -1 as c_int;
        }
    };

    // Create and register a way to kill this client
    let (k_tx, kill_rx): (Sender<()>, Receiver<()>) = channel();
    let kill_tx = k_tx.clone();
    let mut k_tx_ptr = Box::new(k_tx);
    unsafe {
        register_stop_tx(&mut *k_tx_ptr);
    }

    // Writer thread's channel
    let (w_tx, w_rx): (Sender<Vec<u8>>, Receiver<Vec<u8>>) = channel();
    let mut w_tx_ptr = Box::new(w_tx);
    unsafe {
        register_writer_tx(&mut *w_tx_ptr);
    }

    println!("Attempting connect to: {}", host_address);

    let result = TcpStream::connect(host_address);
    if result.is_err() {
        println!("Error connecting to {} - {}", host_address, result.unwrap_err());
        return -1 as c_int;
    }
    println!("Connected");
    on_connect_handler();

    let stream = result.unwrap();
    let client = Bstream::new(stream);

    let r_client = client.clone();
    let r_kill_tx = kill_tx.clone();

    let w_client = client.clone();
    let w_kill_tx = kill_tx.clone();

    // Start the reader thread
    thread::Builder::new()
        .name("ReaderThread".to_string())
        .spawn(move||{
            reader_thread(r_client, handler, r_kill_tx)
        }).unwrap();

    // Start the writer thread
    thread::Builder::new()
        .name("WriterThread".to_string())
        .spawn(move||{
            writer_thread(w_rx, w_client, w_kill_tx)
        }).unwrap();

    // Wait for the kill signal
    match kill_rx.recv() {
        Ok(_) => { }
        Err(e) => {
            println!("Error on kill channel: {}", e);
            return -1 as c_int;
        }
    };
    on_disconnect_handler();

    // Exit out in standard C fashion
    0 as c_int
}

/// Writes the complete contents of buffer to the server
/// Returns -1 on error
#[no_mangle]
pub extern "C" fn send_to_writer(w_tx: *mut Sender<Vec<u8>>,
                                 buffer: *const c_char,
                                 count: c_int,
                                 k_tx: *mut Sender<()>) -> c_int {
    if count < 1 {
        println!("Error - count must be greater than zero");
        return -1 as c_int;
    }

    let num_elts = count as usize;
    let mut n_buffer = Vec::<u8>::with_capacity(num_elts);
    for x in 0..num_elts as isize {
        unsafe {
            n_buffer.push(ptr::read(buffer.offset(x)) as u8);
        }
    }

    unsafe {
        match (*w_tx).send(n_buffer) {
            Ok(_) => { }
            Err(e) => {
                println!("Error sending buffer: {}", e);
                let _ = (*k_tx).send(());
                return -1 as c_int;
            }
        };
    }

    0 as c_int
}

/// Forever listens to incoming data and when a complete message is received,
/// the passed callback is hit
fn reader_thread(client: Bstream,
                 event_handler: extern fn(*const c_char, c_int),
                 kill_tx: Sender<()>) {

    let mut reader = client.clone();
    loop {
        match reader.read() {
            Ok(buffer) => {
                // Launch the handler in a new thread
                thread::Builder::new()
                    .name("Reader-Worker".to_string())
                    .spawn(move||{
                        let slice = &buffer[..];
                        let c_buffer = CString::new(slice).unwrap();
                        event_handler(c_buffer.as_ptr(), buffer.len() as c_int);
                    }).unwrap();
            }
            Err(e) => {
                println!("Error: {}", e);
                break;
            }
        };
    }
    println!("Reader thread finished");
    let _ = kill_tx.send(());
}

/// Forever listens to Receiver<Vec<u8>> waiting on messages to come in
/// Once available, blocks until the entire message has been written
fn writer_thread(rx: Receiver<Vec<u8>>, client: Bstream, kill_tx: Sender<()>) {
    let mut writer = client.clone();
    loop {
        match rx.recv() {
            Ok(ref mut buffer) => {
                match writer.write(buffer) {
                    Ok(_) => { }
                    Err(e) => {
                        println!("Error: {}", e);
                        break;
                    }
                };
            }
            Err(e) => {
                println!("Error: {}", e);
                break;
            }
        };
    }
    println!("Writer thread finished");
    let _ = kill_tx.send(());
}
<|endoftext|>


<commit_before>//! Compiler plugin defining macros that create PHF data structures.
//!
//! # Example
//!
//! ```rust
//! #![feature(plugin, core)]
//! #![plugin(phf_macros)]
//!
//! extern crate phf;
//!
//! #[derive(Clone)]
//! pub enum Keyword {
//!     Loop,
//!     Continue,
//!     Break,
//!     Fn,
//!     Extern,
//! }
//!
//! static KEYWORDS: phf::Map<&'static str, Keyword> = phf_map! {
//!     "loop" => Keyword::Loop,
//!     "continue" => Keyword::Continue,
//!     "break" => Keyword::Break,
//!     "fn" => Keyword::Fn,
//!     "extern" => Keyword::Extern,
//! };
//!
//! pub fn parse_keyword(keyword: &str) -> Option<Keyword> {
//!     KEYWORDS.get(keyword).cloned()
//! }
//! # fn main() {}
//! ```
#![doc(html_root_url="http://sfackler.github.io/rust-phf/doc/v0.7.18")]
#![feature(plugin_registrar, quote, rustc_private)]

extern crate syntax;
#[cfg(feature = "stats")]
extern crate time;
extern crate rustc_plugin;
extern crate phf_shared;
extern crate phf_generator;
#[cfg(feature = "unicase_support")]
extern crate unicase;

use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use syntax::ast::{self, Expr, ExprKind, Mutability, TyKind};
use syntax::tokenstream::TokenTree;
use syntax::codemap::Span;
use syntax::ext::base::{DummyResult, ExtCtxt, MacResult};
use syntax::ext::build::AstBuilder;
use syntax::fold::Folder;
use syntax::parse;
use syntax::parse::token::{InternedString, Comma, Eof, FatArrow};
use syntax::print::pprust;
use syntax::ptr::P;
use rustc_plugin::Registry;
use phf_generator::HashState;
use std::env;
#[cfg(feature = "unicase_support")]
use unicase::UniCase;

use util::{Entry, Key};
use util::{create_map, create_set, create_ordered_map, create_ordered_set};

pub mod util;
mod macros;

#[plugin_registrar]
#[doc(hidden)]
pub fn macro_registrar(reg: &mut Registry) {
    reg.register_macro("phf_map", expand_phf_map);
    reg.register_macro("phf_set", expand_phf_set);
    reg.register_macro("phf_ordered_map", expand_phf_ordered_map);
    reg.register_macro("phf_ordered_set", expand_phf_ordered_set);
}

fn generate_hash(cx: &mut ExtCtxt, sp: Span, entries: &[Entry]) -> HashState {
    #[cfg(feature = "stats")]
    use time::precise_time_s;
    #[cfg(not(feature = "stats"))]
    fn precise_time_s() -> f64 {
        0.
    }

    let start = precise_time_s();
    let state = phf_generator::generate_hash(entries);
    let time = precise_time_s() - start;

    if cfg!(feature = "stats") && env::var_os("PHF_STATS").is_some() {
        cx.parse_sess
          .span_diagnostic
          .span_note_without_error(sp, &format!("PHF generation took {} seconds", time));
    }

    state
}

fn expand_phf_map(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
    let entries = match parse_map(cx, tts) {
        Some(entries) => entries,
        None => return DummyResult::expr(sp),
    };

    if has_duplicates(cx, sp, &*entries) {
        return DummyResult::expr(sp);
    }

    let state = generate_hash(cx, sp, &*entries);

    create_map(cx, sp, entries, state)
}

fn expand_phf_set(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
    let entries = match parse_set(cx, tts) {
        Some(entries) => entries,
        None => return DummyResult::expr(sp),
    };

    if has_duplicates(cx, sp, &*entries) {
        return DummyResult::expr(sp);
    }

    let state = generate_hash(cx, sp, &*entries);

    create_set(cx, sp, entries, state)
}

fn expand_phf_ordered_map(cx: &mut ExtCtxt,
                          sp: Span,
                          tts: &[TokenTree])
                          -> Box<MacResult + 'static> {
    let entries = match parse_map(cx, tts) {
        Some(entries) => entries,
        None => return DummyResult::expr(sp),
    };

    if has_duplicates(cx, sp, &*entries) {
        return DummyResult::expr(sp);
    }

    let state = generate_hash(cx, sp, &*entries);

    create_ordered_map(cx, sp, entries, state)
}

fn expand_phf_ordered_set(cx: &mut ExtCtxt,
                          sp: Span,
                          tts: &[TokenTree])
                          -> Box<MacResult + 'static> {
    let entries = match parse_set(cx, tts) {
        Some(entries) => entries,
        None => return DummyResult::expr(sp),
    };

    if has_duplicates(cx, sp, &*entries) {
        return DummyResult::expr(sp);
    }

    let state = generate_hash(cx, sp, &*entries);

    create_ordered_set(cx, sp, entries, state)
}

fn parse_map(cx: &mut ExtCtxt, tts: &[TokenTree]) -> Option<Vec<Entry>> {
    let mut parser = parse::new_parser_from_tts(cx.parse_sess(), cx.cfg().clone(), tts.to_vec());
    let mut entries = Vec::new();

    let mut bad = false;
    while parser.token != Eof {
        let key = cx.expander().fold_expr(parser.parse_expr().unwrap());
        let key_contents = parse_key(cx, &*key).unwrap_or_else(|| {
            bad = true;
            Key::Str(InternedString::new(""))
        });
        let key = adjust_key(cx, key);

        if !parser.eat(&FatArrow) {
            cx.span_err(parser.span, "expected `=>`");
            return None;
        }

        let value = parser.parse_expr().unwrap();

        entries.push(Entry {
            key_contents: key_contents,
            key: key,
            value: value,
        });

        if !parser.eat(&Comma) && parser.token != Eof {
            cx.span_err(parser.span, "expected `,`");
            return None;
        }
    }

    if bad {
        return None;
    }

    Some(entries)
}

fn parse_set(cx: &mut ExtCtxt, tts: &[TokenTree]) -> Option<Vec<Entry>> {
    let mut parser = parse::new_parser_from_tts(cx.parse_sess(), cx.cfg().clone(), tts.to_vec());
    let mut entries = Vec::new();
    let value = quote_expr!(&*cx, ());

    let mut bad = false;
    while parser.token != Eof {
        let key = cx.expander().fold_expr(parser.parse_expr().unwrap());
        let key_contents = parse_key(cx, &*key).unwrap_or_else(|| {
            bad = true;
            Key::Str(InternedString::new(""))
        });
        let key = adjust_key(cx, key);

        entries.push(Entry {
            key_contents: key_contents,
            key: key,
            value: value.clone(),
        });

        if !parser.eat(&Comma) && parser.token != Eof {
            cx.span_err(parser.span, "expected `,`");
            return None;
        }
    }

    if bad {
        return None;
    }

    Some(entries)
}

fn parse_key(cx: &mut ExtCtxt, e: &Expr) -> Option<Key> {
    match e.node {
        ExprKind::Lit(ref lit) => {
            match lit.node {
                ast::LitKind::Str(ref s, _) => Some(Key::Str(s.clone())),
                ast::LitKind::ByteStr(ref b) => Some(Key::Binary(b.clone())),
                ast::LitKind::Byte(b) => Some(Key::U8(b)),
                ast::LitKind::Char(c) => Some(Key::Char(c)),
                ast::LitKind::Int(i, ast::LitIntType::Signed(ast::IntTy::I8)) =>
                    Some(Key::I8(i as i8)),
                ast::LitKind::Int(i, ast::LitIntType::Signed(ast::IntTy::I16)) =>
                    Some(Key::I16(i as i16)),
                ast::LitKind::Int(i, ast::LitIntType::Signed(ast::IntTy::I32)) =>
                    Some(Key::I32(i as i32)),
                ast::LitKind::Int(i, ast::LitIntType::Signed(ast::IntTy::I64)) =>
                    Some(Key::I64(i as i64)),
                ast::LitKind::Int(i, ast::LitIntType::Unsigned(ast::UintTy::U8)) =>
                    Some(Key::U8(i as u8)),
                ast::LitKind::Int(i, ast::LitIntType::Unsigned(ast::UintTy::U16)) =>
                    Some(Key::U16(i as u16)),
                ast::LitKind::Int(i, ast::LitIntType::Unsigned(ast::UintTy::U32)) =>
                    Some(Key::U32(i as u32)),
                ast::LitKind::Int(i, ast::LitIntType::Unsigned(ast::UintTy::U64)) =>
                    Some(Key::U64(i as u64)),
                ast::LitKind::Bool(b) => Some(Key::Bool(b)),
                _ => {
                    cx.span_err(e.span, "unsupported literal type");
                    None
                }
            }
        }
        ExprKind::Vec(ref v) => {
            let bytes: Vec<Option<u8>> = v.iter().map(|expr|
                if let ExprKind::Lit(ref p) = expr.node {
                    match p.node {
                        ast::LitKind::Int(val, ast::LitIntType::Unsigned(ast::UintTy::U8)) if val < 256 =>
                            Some(val as u8),
                        ast::LitKind::Int(val, ast::LitIntType::Unsuffixed) if val < 256 =>
                            Some(val as u8),
                        _ => None,
                    }
                } else {
                    None
            }).collect();
            if bytes.iter().all(|x| x.is_some()) {
                Some(Key::Binary(std::rc::Rc::new(bytes.iter().map(|x| x.unwrap()).collect())))
            } else {
                cx.span_err(e.span,
                            "not all elements of an expected u8 array literal were u8 literals");
                None
            }
        }
        #[cfg(feature = "unicase_support")]
        ExprKind::Call(ref f, ref args) => {
            if let ExprKind::Path(_, ref path) = f.node {
                if path.segments.last().unwrap().identifier.name.as_str() == "UniCase" {
                    if args.len() == 1 {
                        if let ExprKind::Lit(ref lit) = args.first().unwrap().node {
                            if let ast::LitKind::Str(ref s, _) = lit.node {
                                return Some(Key::UniCase(UniCase(s.to_string())));
                            } else {
                                cx.span_err(e.span, "only a str literal is allowed in UniCase");
                                return None;
                            }
                        }
                    } else {
                        cx.span_err(e.span, "only one str literal is allowed in UniCase");
                        return None;
                    }
                }
            }
            cx.span_err(e.span, "only UniCase is allowed besides literals");
            None
        },
        _ => {
            cx.span_err(e.span, "expected a literal (or a UniCase if the unicase_support feature is enabled)");
            None
        }
    }
}

fn adjust_key(cx: &mut ExtCtxt, e: P<Expr>) -> P<Expr> {
    let coerce_as_slice = match e.node {
        ExprKind::Lit(ref lit) => {
            match lit.node {
                ast::LitKind::ByteStr(_) => true,
                _ => false,
            }
        },
        _ => false,
    };
    if coerce_as_slice {
        let u8_type = cx.ty_path(cx.path_ident(e.span, cx.ident_of("u8")));
        let array_type = cx.ty(e.span, TyKind::Slice(u8_type));
        let slice_type = cx.ty_rptr(e.span, array_type, None, Mutability::Immutable);
        cx.expr_cast(e.span, e, slice_type)
    } else {
        e
    }
}

fn has_duplicates(cx: &mut ExtCtxt, sp: Span, entries: &[Entry]) -> bool {
    let mut dups = false;
    let mut strings = HashMap::new();
    for entry in entries.iter() {
        let &mut (ref mut spans, _) = match strings.entry(&entry.key_contents) {
            Occupied(e) => e.into_mut(),
            Vacant(e) => e.insert((vec![], &entry.key)),
        };
        spans.push(entry.key.span);
    }

    for &(ref spans, key) in strings.values() {
        if spans.len() == 1 {
            continue;
        }

        dups = true;
        let mut err = cx.struct_span_err(sp, &*format!("duplicate key {}",
                                                       pprust::expr_to_string(&**key)));
        for span in spans.iter() {
            err.span_note(*span, "one occurrence here");
        }
        err.emit();
    }

    dups
}
<commit_msg>Update phf_macros to Rust 1.14.0-nightly (7c69b0d5a 2016-11-01)<commit_after>//! Compiler plugin defining macros that create PHF data structures.
//!
//! # Example
//!
//! ```rust
//! #![feature(plugin, core)]
//! #![plugin(phf_macros)]
//!
//! extern crate phf;
//!
//! #[derive(Clone)]
//! pub enum Keyword {
//!     Loop,
//!     Continue,
//!     Break,
//!     Fn,
//!     Extern,
//! }
//!
//! static KEYWORDS: phf::Map<&'static str, Keyword> = phf_map! {
//!     "loop" => Keyword::Loop,
//!     "continue" => Keyword::Continue,
//!     "break" => Keyword::Break,
//!     "fn" => Keyword::Fn,
//!     "extern" => Keyword::Extern,
//! };
//!
//! pub fn parse_keyword(keyword: &str) -> Option<Keyword> {
//!     KEYWORDS.get(keyword).cloned()
//! }
//! # fn main() {}
//! ```
#![doc(html_root_url="http://sfackler.github.io/rust-phf/doc/v0.7.18")]
#![feature(plugin_registrar, quote, rustc_private)]

extern crate syntax;
#[cfg(feature = "stats")]
extern crate time;
extern crate rustc_plugin;
extern crate phf_shared;
extern crate phf_generator;
#[cfg(feature = "unicase_support")]
extern crate unicase;

use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};
use syntax::ast::{self, Expr, ExprKind, Mutability, TyKind};
use syntax::tokenstream::TokenTree;
use syntax::codemap::Span;
use syntax::ext::base::{DummyResult, ExtCtxt, MacResult};
use syntax::ext::build::AstBuilder;
use syntax::fold::Folder;
use syntax::parse;
use syntax::parse::token::{InternedString, Comma, Eof, FatArrow};
use syntax::print::pprust;
use syntax::ptr::P;
use rustc_plugin::Registry;
use phf_generator::HashState;
use std::env;
#[cfg(feature = "unicase_support")]
use unicase::UniCase;

use util::{Entry, Key};
use util::{create_map, create_set, create_ordered_map, create_ordered_set};

pub mod util;
mod macros;

#[plugin_registrar]
#[doc(hidden)]
pub fn macro_registrar(reg: &mut Registry) {
    reg.register_macro("phf_map", expand_phf_map);
    reg.register_macro("phf_set", expand_phf_set);
    reg.register_macro("phf_ordered_map", expand_phf_ordered_map);
    reg.register_macro("phf_ordered_set", expand_phf_ordered_set);
}

fn generate_hash(cx: &mut ExtCtxt, sp: Span, entries: &[Entry]) -> HashState {
    #[cfg(feature = "stats")]
    use time::precise_time_s;
    #[cfg(not(feature = "stats"))]
    fn precise_time_s() -> f64 {
        0.
    }

    let start = precise_time_s();
    let state = phf_generator::generate_hash(entries);
    let time = precise_time_s() - start;

    if cfg!(feature = "stats") && env::var_os("PHF_STATS").is_some() {
        cx.parse_sess
          .span_diagnostic
          .span_note_without_error(sp, &format!("PHF generation took {} seconds", time));
    }

    state
}

fn expand_phf_map(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
    let entries = match parse_map(cx, tts) {
        Some(entries) => entries,
        None => return DummyResult::expr(sp),
    };

    if has_duplicates(cx, sp, &*entries) {
        return DummyResult::expr(sp);
    }

    let state = generate_hash(cx, sp, &*entries);

    create_map(cx, sp, entries, state)
}

fn expand_phf_set(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) -> Box<MacResult + 'static> {
    let entries = match parse_set(cx, tts) {
        Some(entries) => entries,
        None => return DummyResult::expr(sp),
    };

    if has_duplicates(cx, sp, &*entries) {
        return DummyResult::expr(sp);
    }

    let state = generate_hash(cx, sp, &*entries);

    create_set(cx, sp, entries, state)
}

fn expand_phf_ordered_map(cx: &mut ExtCtxt,
                          sp: Span,
                          tts: &[TokenTree])
                          -> Box<MacResult + 'static> {
    let entries = match parse_map(cx, tts) {
        Some(entries) => entries,
        None => return DummyResult::expr(sp),
    };

    if has_duplicates(cx, sp, &*entries) {
        return DummyResult::expr(sp);
    }

    let state = generate_hash(cx, sp, &*entries);

    create_ordered_map(cx, sp, entries, state)
}

fn expand_phf_ordered_set(cx: &mut ExtCtxt,
                          sp: Span,
                          tts: &[TokenTree])
                          -> Box<MacResult + 'static> {
    let entries = match parse_set(cx, tts) {
        Some(entries) => entries,
        None => return DummyResult::expr(sp),
    };

    if has_duplicates(cx, sp, &*entries) {
        return DummyResult::expr(sp);
    }

    let state = generate_hash(cx, sp, &*entries);

    create_ordered_set(cx, sp, entries, state)
}

fn parse_map(cx: &mut ExtCtxt, tts: &[TokenTree]) -> Option<Vec<Entry>> {
    let mut parser = parse::new_parser_from_tts(cx.parse_sess(), tts.to_vec());
    let mut entries = Vec::new();

    let mut bad = false;
    while parser.token != Eof {
        let key = cx.expander().fold_expr(parser.parse_expr().unwrap());
        let key_contents = parse_key(cx, &*key).unwrap_or_else(|| {
            bad = true;
            Key::Str(InternedString::new(""))
        });
        let key = adjust_key(cx, key);

        if !parser.eat(&FatArrow) {
            cx.span_err(parser.span, "expected `=>`");
            return None;
        }

        let value = parser.parse_expr().unwrap();

        entries.push(Entry {
            key_contents: key_contents,
            key: key,
            value: value,
        });

        if !parser.eat(&Comma) && parser.token != Eof {
            cx.span_err(parser.span, "expected `,`");
            return None;
        }
    }

    if bad {
        return None;
    }

    Some(entries)
}

fn parse_set(cx: &mut ExtCtxt, tts: &[TokenTree]) -> Option<Vec<Entry>> {
    let mut parser = parse::new_parser_from_tts(cx.parse_sess(), tts.to_vec());
    let mut entries = Vec::new();
    let value = quote_expr!(&*cx, ());

    let mut bad = false;
    while parser.token != Eof {
        let key = cx.expander().fold_expr(parser.parse_expr().unwrap());
        let key_contents = parse_key(cx, &*key).unwrap_or_else(|| {
            bad = true;
            Key::Str(InternedString::new(""))
        });
        let key = adjust_key(cx, key);

        entries.push(Entry {
            key_contents: key_contents,
            key: key,
            value: value.clone(),
        });

        if !parser.eat(&Comma) && parser.token != Eof {
            cx.span_err(parser.span, "expected `,`");
            return None;
        }
    }

    if bad {
        return None;
    }

    Some(entries)
}

fn parse_key(cx: &mut ExtCtxt, e: &Expr) -> Option<Key> {
    match e.node {
        ExprKind::Lit(ref lit) => {
            match lit.node {
                ast::LitKind::Str(ref s, _) => Some(Key::Str(s.clone())),
                ast::LitKind::ByteStr(ref b) => Some(Key::Binary(b.clone())),
                ast::LitKind::Byte(b) => Some(Key::U8(b)),
                ast::LitKind::Char(c) => Some(Key::Char(c)),
                ast::LitKind::Int(i, ast::LitIntType::Signed(ast::IntTy::I8)) =>
                    Some(Key::I8(i as i8)),
                ast::LitKind::Int(i, ast::LitIntType::Signed(ast::IntTy::I16)) =>
                    Some(Key::I16(i as i16)),
                ast::LitKind::Int(i, ast::LitIntType::Signed(ast::IntTy::I32)) =>
                    Some(Key::I32(i as i32)),
                ast::LitKind::Int(i, ast::LitIntType::Signed(ast::IntTy::I64)) =>
                    Some(Key::I64(i as i64)),
                ast::LitKind::Int(i, ast::LitIntType::Unsigned(ast::UintTy::U8)) =>
                    Some(Key::U8(i as u8)),
                ast::LitKind::Int(i, ast::LitIntType::Unsigned(ast::UintTy::U16)) =>
                    Some(Key::U16(i as u16)),
                ast::LitKind::Int(i, ast::LitIntType::Unsigned(ast::UintTy::U32)) =>
                    Some(Key::U32(i as u32)),
                ast::LitKind::Int(i, ast::LitIntType::Unsigned(ast::UintTy::U64)) =>
                    Some(Key::U64(i as u64)),
                ast::LitKind::Bool(b) => Some(Key::Bool(b)),
                _ => {
                    cx.span_err(e.span, "unsupported literal type");
                    None
                }
            }
        }
        ExprKind::Vec(ref v) => {
            let bytes: Vec<Option<u8>> = v.iter().map(|expr|
                if let ExprKind::Lit(ref p) = expr.node {
                    match p.node {
                        ast::LitKind::Int(val, ast::LitIntType::Unsigned(ast::UintTy::U8)) if val < 256 =>
                            Some(val as u8),
                        ast::LitKind::Int(val, ast::LitIntType::Unsuffixed) if val < 256 =>
                            Some(val as u8),
                        _ => None,
                    }
                } else {
                    None
            }).collect();
            if bytes.iter().all(|x| x.is_some()) {
                Some(Key::Binary(std::rc::Rc::new(bytes.iter().map(|x| x.unwrap()).collect())))
            } else {
                cx.span_err(e.span,
                            "not all elements of an expected u8 array literal were u8 literals");
                None
            }
        }
        #[cfg(feature = "unicase_support")]
        ExprKind::Call(ref f, ref args) => {
            if let ExprKind::Path(_, ref path) = f.node {
                if path.segments.last().unwrap().identifier.name.as_str() == "UniCase" {
                    if args.len() == 1 {
                        if let ExprKind::Lit(ref lit) = args.first().unwrap().node {
                            if let ast::LitKind::Str(ref s, _) = lit.node {
                                return Some(Key::UniCase(UniCase(s.to_string())));
                            } else {
                                cx.span_err(e.span, "only a str literal is allowed in UniCase");
                                return None;
                            }
                        }
                    } else {
                        cx.span_err(e.span, "only one str literal is allowed in UniCase");
                        return None;
                    }
                }
            }
            cx.span_err(e.span, "only UniCase is allowed besides literals");
            None
        },
        _ => {
            cx.span_err(e.span, "expected a literal (or a UniCase if the unicase_support feature is enabled)");
            None
        }
    }
}

fn adjust_key(cx: &mut ExtCtxt, e: P<Expr>) -> P<Expr> {
    let coerce_as_slice = match e.node {
        ExprKind::Lit(ref lit) => {
            match lit.node {
                ast::LitKind::ByteStr(_) => true,
                _ => false,
            }
        },
        _ => false,
    };
    if coerce_as_slice {
        let u8_type = cx.ty_path(cx.path_ident(e.span, cx.ident_of("u8")));
        let array_type = cx.ty(e.span, TyKind::Slice(u8_type));
        let slice_type = cx.ty_rptr(e.span, array_type, None, Mutability::Immutable);
        cx.expr_cast(e.span, e, slice_type)
    } else {
        e
    }
}

fn has_duplicates(cx: &mut ExtCtxt, sp: Span, entries: &[Entry]) -> bool {
    let mut dups = false;
    let mut strings = HashMap::new();
    for entry in entries.iter() {
        let &mut (ref mut spans, _) = match strings.entry(&entry.key_contents) {
            Occupied(e) => e.into_mut(),
            Vacant(e) => e.insert((vec![], &entry.key)),
        };
        spans.push(entry.key.span);
    }

    for &(ref spans, key) in strings.values() {
        if spans.len() == 1 {
            continue;
        }

        dups = true;
        let mut err = cx.struct_span_err(sp, &*format!("duplicate key {}",
                                                       pprust::expr_to_string(&**key)));
        for span in spans.iter() {
            err.span_note(*span, "one occurrence here");
        }
        err.emit();
    }

    dups
}
<|endoftext|>