<commit_before>use ammonia::{Builder, UrlRelative};
use comrak;
use htmlescape::encode_minimal;
use std::borrow::Cow;
use url::Url;

use util::CargoResult;

/// Context for markdown to HTML rendering.
#[allow(missing_debug_implementations)]
struct MarkdownRenderer<'a> {
    html_sanitizer: Builder<'a>,
}

impl<'a> MarkdownRenderer<'a> {
    /// Creates a new renderer instance.
    ///
    /// Per `markdown_to_html`, `base_url` is the base URL prepended to any
    /// relative links in the input document.  See that function for more detail.
    fn new(base_url: Option<&'a str>) -> MarkdownRenderer<'a> {
        let tags = [
            "a",
            "b",
            "blockquote",
            "br",
            "code",
            "dd",
            "del",
            "dl",
            "dt",
            "em",
            "h1",
            "h2",
            "h3",
            "hr",
            "i",
            "img",
            "input",
            "kbd",
            "li",
            "ol",
            "p",
            "pre",
            "s",
            "strike",
            "strong",
            "sub",
            "sup",
            "table",
            "tbody",
            "td",
            "th",
            "thead",
            "tr",
            "ul",
            "hr",
            "span",
        ].iter()
            .cloned()
            .collect();
        let tag_attributes = [
            ("a", ["href", "id", "target"].iter().cloned().collect()),
            (
                "img",
                ["width", "height", "src", "alt", "align"]
                    .iter()
                    .cloned()
                    .collect(),
            ),
            (
                "input",
                ["checked", "disabled", "type"].iter().cloned().collect(),
            ),
        ].iter()
            .cloned()
            .collect();
        let allowed_classes = [
            (
                "code",
                [
                    "language-bash",
                    "language-clike",
                    "language-glsl",
                    "language-go",
                    "language-ini",
                    "language-javascript",
                    "language-json",
                    "language-markup",
                    "language-protobuf",
                    "language-ruby",
                    "language-rust",
                    "language-scss",
                    "language-sql",
                    "yaml",
                ].iter()
                    .cloned()
                    .collect(),
            ),
        ].iter()
            .cloned()
            .collect();

        let sanitizer_base_url = base_url.map(|s| s.to_string());

        fn constrain_closure<F>(f: F) -> F
        where
            F: for<'a> Fn(&'a str) -> Option<Cow<'a, str>> + Send + Sync,
        {
            f
        }

        let unrelative_url_sanitizer = constrain_closure(|url| {
            if url.starts_with('#') {
                return Some(Cow::Borrowed(url));
            }

            None
        });

        let relative_url_sanitizer = constrain_closure(move |url| {
            if url.starts_with('#') {
                return Some(Cow::Borrowed(url));
            }

            let mut new_url = sanitizer_base_url.clone().unwrap();
            if !new_url.ends_with('/') {
                new_url.push('/');
            }
            new_url += "blob/master";
            if !url.starts_with('/') {
                new_url.push('/');
            }
            new_url += url;
            Some(Cow::Owned(new_url))
        });

        let use_relative = if let Some(base_url) = base_url {
            if let Ok(url) = Url::parse(base_url) {
                url.host_str() == Some("github.com") || url.host_str() == Some("gitlab.com")
                    || url.host_str() == Some("bitbucket.org")
            } else {
                false
            }
        } else {
            false
        };

        let mut html_sanitizer = Builder::new();
        html_sanitizer
            .link_rel(Some("nofollow noopener noreferrer"))
            .tags(tags)
            .tag_attributes(tag_attributes)
            .allowed_classes(allowed_classes)
            .url_relative(if use_relative {
                UrlRelative::Custom(Box::new(relative_url_sanitizer))
            } else {
                UrlRelative::Custom(Box::new(unrelative_url_sanitizer))
            })
            .id_prefix(Some("user-content-"));

        MarkdownRenderer {
            html_sanitizer: html_sanitizer,
        }
    }

    /// Renders the given markdown to HTML using the current settings.
    fn to_html(&self, text: &str) -> CargoResult<String> {
        let options = comrak::ComrakOptions {
            ext_autolink: true,
            ext_strikethrough: true,
            ext_table: true,
            ext_tagfilter: true,
            ext_tasklist: true,
            ext_header_ids: Some("user-content-".to_string()),
            ..comrak::ComrakOptions::default()
        };
        let rendered = comrak::markdown_to_html(text, &options);
        Ok(self.html_sanitizer.clean(&rendered).to_string())
    }
}

/// Renders Markdown text to sanitized HTML.
fn markdown_to_html(text: &str, base_url: Option<&str>) -> CargoResult<String> {
    let renderer = MarkdownRenderer::new(base_url);
    renderer.to_html(text)
}

/// Any readme with a filename ending in one of these extensions will be rendered as Markdown.
/// Note we also render a readme as Markdown if _no_ extension is on the filename.
static MARKDOWN_EXTENSIONS: [&'static str; 7] = [
    ".md",
    ".markdown",
    ".mdown",
    ".mdwn",
    ".mkd",
    ".mkdn",
    ".mkdown",
];

/// Renders a readme to sanitized HTML.  An appropriate rendering method is chosen depending
/// on the extension of the supplied filename.
///
/// The returned text should not contain any harmful HTML tag or attribute (such as iframe,
/// onclick, onmouseover, etc.).
///
/// The `base_url` parameter will be used as the base for any relative links found in the
/// Markdown, as long as its host part is github.com, gitlab.com, or bitbucket.org.  The
/// supplied URL will be used as a directory base whether or not the relative link is
/// prefixed with '/'.  If `None` is passed, relative links will be omitted.
///
/// # Examples
///
/// ```
/// use render::render_to_html;
///
/// let text = "[Rust](https://rust-lang.org/) is an awesome *systems programming* language!";
/// let rendered = readme_to_html(text, "README.md", None)?;
/// ```
pub fn readme_to_html(text: &str, filename: &str, base_url: Option<&str>) -> CargoResult<String> {
    let filename = filename.to_lowercase();

    if !filename.contains('.') || MARKDOWN_EXTENSIONS.iter().any(|e| filename.ends_with(e)) {
        return markdown_to_html(text, base_url);
    }

    Ok(encode_minimal(text).replace("\n", "
\n"))
}

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

    #[test]
    fn empty_text() {
        let text = "";
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(result, "");
    }

    #[test]
    fn text_with_script_tag() {
        let text = "foo_readme\n\n";
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(
            result,
            "
foo_readme
\n<script>alert(\'Hello World\')</script>\n"
        );
    }

    #[test]
    fn text_with_iframe_tag() {
        let text = "foo_readme\n\nalert('Hello World')";
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(
            result,
            "
foo_readme
\n<iframe>alert(\'Hello World\')</iframe>\n"
        );
    }

    #[test]
    fn text_with_unknown_tag() {
        let text = "foo_readme\n\n<unknown>alert('Hello World')</unknown>";
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(result, "
foo_readme
\n
alert(\'Hello World\')
\n");
    }

    #[test]
    fn text_with_inline_javascript() {
        let text = r#"foo_readme\n\nCrate page"#;
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(
            result,
            "
foo_readme\\n\\nCrate page
\n"
        );
    }

    // See https://github.com/kivikakk/comrak/issues/37. This panic happened
    // in comrak 0.1.8 but was fixed in 0.1.9.
    #[test]
    fn text_with_fancy_single_quotes() {
        let text = r#"wb’"#;
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(result, "
wb’
\n");
    }

    #[test]
    fn code_block_with_syntax_highlighting() {
        let code_block = r#"```rust \
                            println!("Hello World"); \
                           ```"#;
        let result = markdown_to_html(code_block, None).unwrap();
        assert!(result.contains(""));
    }

    #[test]
    fn text_with_forbidden_class_attribute() {
        let text = "
Hello World!
";
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(result, "
Hello World!
\n");
    }

    #[test]
    fn relative_links() {
        let absolute = "[hi](/hi)";
        let relative = "[there](there)";

        for host in &["github.com", "gitlab.com", "bitbucket.org"] {
            for &extra_slash in &[true, false] {
                let url = format!(
                    "https://{}/rust-lang/test{}",
                    host,
                    if extra_slash { "/" } else { "" }
                );

                let result = markdown_to_html(absolute, Some(&url)).unwrap();
                assert_eq!(
                    result,
                    format!(
                        "
hi
\n",
                        host
                    )
                );

                let result = markdown_to_html(relative, Some(&url)).unwrap();
                assert_eq!(
                    result,
                    format!(
                        "
there
\n",
                        host
                    )
                );
            }
        }

        let result = markdown_to_html(absolute, Some("https://google.com/")).unwrap();
        assert_eq!(
            result,
            "
hi
\n"
        );
    }

    #[test]
    fn absolute_links_dont_get_resolved() {
        let readme_text = "[![Crates.io](https://img.shields.io/crates/v/clap.svg)](https://crates.io/crates/clap)";
        let repository = "https://github.com/kbknapp/clap-rs/";
        let result = markdown_to_html(readme_text, Some(&repository)).unwrap();

        assert_eq!(
            result,
            "

\n"
        );
    }

    #[test]
    fn readme_to_html_renders_markdown() {
        for f in &["README", "readme.md", "README.MARKDOWN", "whatever.mkd"] {
            assert_eq!(
                readme_to_html("*lobster*", f, None).unwrap(),
                "
lobster
\n"
            );
        }
    }

    #[test]
    fn readme_to_html_renders_other_things() {
        for f in &["readme.exe", "readem.org", "blah.adoc"] {
            assert_eq!(
                readme_to_html("\n\nis my friend\n", f, None).unwrap(),
                "<script>lobster</script>
\n
\nis my friend
\n"
            );
        }
    }

    #[test]
    fn header_has_tags() {
        let text = "# My crate\n\nHello, world!\n";
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(
            result,
            "
My crate
\n
Hello, world!
\n"
        );
    }

    #[test]
    fn manual_anchor_is_sanitized() {
        let text = "
My crate
\n
Hello, world!
\n";
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(
            result,
            "
My crate
\n
Hello, world!
\n"
        );
    }
}
<commit_msg>some more docs<commit_after>use ammonia::{Builder, UrlRelative};
use comrak;
use htmlescape::encode_minimal;
use std::borrow::Cow;
use url::Url;

use util::CargoResult;

/// Context for markdown to HTML rendering.
#[allow(missing_debug_implementations)]
struct MarkdownRenderer<'a> {
    html_sanitizer: Builder<'a>,
}

impl<'a> MarkdownRenderer<'a> {
    /// Creates a new renderer instance.
    ///
    /// Per `readme_to_html`, `base_url` is the base URL prepended to any
    /// relative links in the input document.  See that function for more detail.
    fn new(base_url: Option<&'a str>) -> MarkdownRenderer<'a> {
        let tags = [
            "a",
            "b",
            "blockquote",
            "br",
            "code",
            "dd",
            "del",
            "dl",
            "dt",
            "em",
            "h1",
            "h2",
            "h3",
            "hr",
            "i",
            "img",
            "input",
            "kbd",
            "li",
            "ol",
            "p",
            "pre",
            "s",
            "strike",
            "strong",
            "sub",
            "sup",
            "table",
            "tbody",
            "td",
            "th",
            "thead",
            "tr",
            "ul",
            "hr",
            "span",
        ].iter()
            .cloned()
            .collect();
        let tag_attributes = [
            ("a", ["href", "id", "target"].iter().cloned().collect()),
            (
                "img",
                ["width", "height", "src", "alt", "align"]
                    .iter()
                    .cloned()
                    .collect(),
            ),
            (
                "input",
                ["checked", "disabled", "type"].iter().cloned().collect(),
            ),
        ].iter()
            .cloned()
            .collect();
        let allowed_classes = [
            (
                "code",
                [
                    "language-bash",
                    "language-clike",
                    "language-glsl",
                    "language-go",
                    "language-ini",
                    "language-javascript",
                    "language-json",
                    "language-markup",
                    "language-protobuf",
                    "language-ruby",
                    "language-rust",
                    "language-scss",
                    "language-sql",
                    "yaml",
                ].iter()
                    .cloned()
                    .collect(),
            ),
        ].iter()
            .cloned()
            .collect();

        let sanitizer_base_url = base_url.map(|s| s.to_string());

        // Constrain the type of the closures given to the HTML sanitizer.
        fn constrain_closure<F>(f: F) -> F
        where
            F: for<'a> Fn(&'a str) -> Option<Cow<'a, str>> + Send + Sync,
        {
            f
        }

        let unrelative_url_sanitizer = constrain_closure(|url| {
            // We have no base URL; allow fragment links only.
            if url.starts_with('#') {
                return Some(Cow::Borrowed(url));
            }

            None
        });

        let relative_url_sanitizer = constrain_closure(move |url| {
            // sanitizer_base_url is Some(String); use it to fix the relative URL.
            if url.starts_with('#') {
                return Some(Cow::Borrowed(url));
            }

            let mut new_url = sanitizer_base_url.clone().unwrap();
            if !new_url.ends_with('/') {
                new_url.push('/');
            }
            new_url += "blob/master";
            if !url.starts_with('/') {
                new_url.push('/');
            }
            new_url += url;
            Some(Cow::Owned(new_url))
        });

        let use_relative = if let Some(base_url) = base_url {
            if let Ok(url) = Url::parse(base_url) {
                url.host_str() == Some("github.com") || url.host_str() == Some("gitlab.com")
                    || url.host_str() == Some("bitbucket.org")
            } else {
                false
            }
        } else {
            false
        };

        let mut html_sanitizer = Builder::new();
        html_sanitizer
            .link_rel(Some("nofollow noopener noreferrer"))
            .tags(tags)
            .tag_attributes(tag_attributes)
            .allowed_classes(allowed_classes)
            .url_relative(if use_relative {
                UrlRelative::Custom(Box::new(relative_url_sanitizer))
            } else {
                UrlRelative::Custom(Box::new(unrelative_url_sanitizer))
            })
            .id_prefix(Some("user-content-"));

        MarkdownRenderer {
            html_sanitizer: html_sanitizer,
        }
    }

    /// Renders the given markdown to HTML using the current settings.
    fn to_html(&self, text: &str) -> CargoResult<String> {
        let options = comrak::ComrakOptions {
            ext_autolink: true,
            ext_strikethrough: true,
            ext_table: true,
            ext_tagfilter: true,
            ext_tasklist: true,
            ext_header_ids: Some("user-content-".to_string()),
            ..comrak::ComrakOptions::default()
        };
        let rendered = comrak::markdown_to_html(text, &options);
        Ok(self.html_sanitizer.clean(&rendered).to_string())
    }
}

/// Renders Markdown text to sanitized HTML with a given `base_url`.
/// See `readme_to_html` for the interpretation of `base_url`.
fn markdown_to_html(text: &str, base_url: Option<&str>) -> CargoResult<String> {
    let renderer = MarkdownRenderer::new(base_url);
    renderer.to_html(text)
}

/// Any readme with a filename ending in one of these extensions will be rendered as Markdown.
/// Note we also render a readme as Markdown if _no_ extension is on the filename.
static MARKDOWN_EXTENSIONS: [&'static str; 7] = [
    ".md",
    ".markdown",
    ".mdown",
    ".mdwn",
    ".mkd",
    ".mkdn",
    ".mkdown",
];

/// Renders a readme to sanitized HTML.  An appropriate rendering method is chosen depending
/// on the extension of the supplied `filename`.
///
/// The returned text will not contain any harmful HTML tag or attribute (such as iframe,
/// onclick, onmouseover, etc.).
///
/// The `base_url` parameter will be used as the base for any relative links found in the
/// Markdown, as long as its host part is github.com, gitlab.com, or bitbucket.org.  The
/// supplied URL will be used as a directory base whether or not the relative link is
/// prefixed with '/'.  If `None` is passed, relative links will be omitted.
///
/// # Examples
///
/// ```
/// use render::render_to_html;
///
/// let text = "[Rust](https://rust-lang.org/) is an awesome *systems programming* language!";
/// let rendered = readme_to_html(text, "README.md", None)?;
/// ```
pub fn readme_to_html(text: &str, filename: &str, base_url: Option<&str>) -> CargoResult<String> {
    let filename = filename.to_lowercase();

    if !filename.contains('.') || MARKDOWN_EXTENSIONS.iter().any(|e| filename.ends_with(e)) {
        return markdown_to_html(text, base_url);
    }

    Ok(encode_minimal(text).replace("\n", "
\n"))
}

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

    #[test]
    fn empty_text() {
        let text = "";
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(result, "");
    }

    #[test]
    fn text_with_script_tag() {
        let text = "foo_readme\n\n";
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(
            result,
            "
foo_readme
\n<script>alert(\'Hello World\')</script>\n"
        );
    }

    #[test]
    fn text_with_iframe_tag() {
        let text = "foo_readme\n\nalert('Hello World')";
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(
            result,
            "
foo_readme
\n<iframe>alert(\'Hello World\')</iframe>\n"
        );
    }

    #[test]
    fn text_with_unknown_tag() {
        let text = "foo_readme\n\n<unknown>alert('Hello World')</unknown>";
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(result, "
foo_readme
\n
alert(\'Hello World\')
\n");
    }

    #[test]
    fn text_with_inline_javascript() {
        let text = r#"foo_readme\n\nCrate page"#;
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(
            result,
            "
foo_readme\\n\\nCrate page
\n"
        );
    }

    // See https://github.com/kivikakk/comrak/issues/37. This panic happened
    // in comrak 0.1.8 but was fixed in 0.1.9.
    #[test]
    fn text_with_fancy_single_quotes() {
        let text = r#"wb’"#;
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(result, "
wb’
\n");
    }

    #[test]
    fn code_block_with_syntax_highlighting() {
        let code_block = r#"```rust \
                            println!("Hello World"); \
                           ```"#;
        let result = markdown_to_html(code_block, None).unwrap();
        assert!(result.contains(""));
    }

    #[test]
    fn text_with_forbidden_class_attribute() {
        let text = "
Hello World!
";
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(result, "
Hello World!
\n");
    }

    #[test]
    fn relative_links() {
        let absolute = "[hi](/hi)";
        let relative = "[there](there)";

        for host in &["github.com", "gitlab.com", "bitbucket.org"] {
            for &extra_slash in &[true, false] {
                let url = format!(
                    "https://{}/rust-lang/test{}",
                    host,
                    if extra_slash { "/" } else { "" }
                );

                let result = markdown_to_html(absolute, Some(&url)).unwrap();
                assert_eq!(
                    result,
                    format!(
                        "
hi
\n",
                        host
                    )
                );

                let result = markdown_to_html(relative, Some(&url)).unwrap();
                assert_eq!(
                    result,
                    format!(
                        "
there
\n",
                        host
                    )
                );
            }
        }

        let result = markdown_to_html(absolute, Some("https://google.com/")).unwrap();
        assert_eq!(
            result,
            "
hi
\n"
        );
    }

    #[test]
    fn absolute_links_dont_get_resolved() {
        let readme_text = "[![Crates.io](https://img.shields.io/crates/v/clap.svg)](https://crates.io/crates/clap)";
        let repository = "https://github.com/kbknapp/clap-rs/";
        let result = markdown_to_html(readme_text, Some(&repository)).unwrap();

        assert_eq!(
            result,
            "

\n"
        );
    }

    #[test]
    fn readme_to_html_renders_markdown() {
        for f in &["README", "readme.md", "README.MARKDOWN", "whatever.mkd"] {
            assert_eq!(
                readme_to_html("*lobster*", f, None).unwrap(),
                "
lobster
\n"
            );
        }
    }

    #[test]
    fn readme_to_html_renders_other_things() {
        for f in &["readme.exe", "readem.org", "blah.adoc"] {
            assert_eq!(
                readme_to_html("\n\nis my friend\n", f, None).unwrap(),
                "<script>lobster</script>
\n
\nis my friend
\n"
            );
        }
    }

    #[test]
    fn header_has_tags() {
        let text = "# My crate\n\nHello, world!\n";
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(
            result,
            "
My crate
\n
Hello, world!
\n"
        );
    }

    #[test]
    fn manual_anchor_is_sanitized() {
        let text = "
My crate
\n
Hello, world!
\n";
        let result = markdown_to_html(text, None).unwrap();
        assert_eq!(
            result,
            "
My crate
\n
Hello, world!
\n"
        );
    }
}
<|endoftext|>


<commit_before>use std::ascii::OwnedAsciiExt;
use std::collections::HashMap;

use chrono::{
    DateTime,
    FixedOffset,
    Offset,
};

use super::rfc5322::Rfc5322Parser;
use super::results::{ParsingError, ParsingResult};

static DAYS_OF_WEEK: [&'static str; 7] = [
    "mon", "tue", "wed", "thu",
    "fri", "sat", "sun",
];

static MONTHS: [&'static str; 12] = [
    "jan", "feb", "mar", "apr",
    "may", "jun", "jul", "aug",
    "sep", "oct", "nov", "dec"
];

// Lazily build TZ_DATA when we need it.
lazy_static!{
    static ref TZ_DATA: HashMap<&'static str, i32> = {
        let mut map = HashMap::new();
        map.insert("Z",    0); // Zulu
        map.insert("UT",   0);
        map.insert("GMT",  0);
        map.insert("PST", -28800); // UTC-8
        map.insert("PDT", -25200); // UTC-7
        map.insert("MST", -25200); // UTC-7
        map.insert("MDT", -21600); // UTC-6
        map.insert("CST", -21600); // UTC-6
        map.insert("CDT", -18000); // UTC-5
        map.insert("EST", -18000); // UTC-5
        map.insert("EDT", -14400); // UTC-4
        map
    };
}

/// Parser for RFC822 style dates, as defined by Section 5.
///
/// Note that this also supports the additions as specified in
/// RFC5322 Section 3.3 while still being backward compatible.
#[unstable]
pub struct Rfc822DateParser<'s> {
    parser: Rfc5322Parser<'s>,
}

impl<'s> Rfc822DateParser<'s> {
    #[unstable]
    pub fn new(s: &'s str) -> Rfc822DateParser<'s> {
        Rfc822DateParser {
            parser: Rfc5322Parser::new(s),
        }
    }

    #[inline]
    fn consume_u32(&mut self) -> Option<u32> {
        match self.parser.consume_word(false) {
            Some(s) => match s.parse() {  // FIXME
                Ok(x) => Some(x),
                Err(_) => None
            },
            None => None,
        }
    }

    fn consume_time(&mut self) -> Option<(u32, u32, u32)> {
        let hour = self.consume_u32();
        if !self.parser.eof() && self.parser.peek() == ':' {
            self.parser.consume_char();
            let minute = self.consume_u32();
            // Seconds are optional, only try to parse if we see the next seperator.
            let second = if !self.parser.eof() && self.parser.peek() == ':' {
                self.parser.consume_char();
                self.consume_u32()
            } else {
                None
            };

            match (hour, minute, second) {
                (Some(hour), Some(minute), Some(second)) => Some((hour, minute, second)),
                // No seconds - default to 0
                (Some(hour), Some(minute), None) => Some((hour, minute, 0)),
                _ => None,
            }
        } else {
            None
        }
    }

    fn consume_timezone_offset(&mut self) -> ParsingResult<i32> {
        match self.parser.consume_word(false) {
            Some(s) => {
                // from_str doesn't like leading '+' to indicate positive,
                // so strip it off if it's there.
                let mut s_slice = s.as_slice();
                s_slice = if s_slice.starts_with("+") {
                    &s_slice[1..]
                } else {
                    s_slice
                };
                // Try to parse zone as an int
                match s_slice.parse::<i32>() {
                    Ok(i) => {
                        let offset_hours = i / 100;
                        let offset_mins = i % 100;
                        Ok(offset_hours * 3600 + offset_mins * 60)
                    },
                    Err(_) => {
                        // Isn't an int, so try to use the strings->TZ hash.
                        match TZ_DATA.get(s_slice) {
                            Some(offset) => Ok(offset.clone()),
                            None => Err(ParsingError::new(format!("Invalid timezone: {}", s_slice)))
                        }
                    }
                }
            },
            None => Err(ParsingError::new("Expected timezone offset.".to_string()))
        }
    }

    /// Consume a DateTime from the input.
    ///
    /// If successful, returns a DateTime with a fixed offset based on the
    /// timezone parsed. You may wish to deal with this in UTC, in which case
    /// you may want something like
    ///
    /// ```
    /// extern crate chrono;
    /// extern crate email;
    ///
    /// use email::rfc822::Rfc822DateParser;
    /// use chrono::UTC;
    ///
    /// fn main() {
    ///     let mut p = Rfc822DateParser::new("Thu, 18 Dec 2014 21:07:22 +0100");
    ///     let d = p.consume_datetime().unwrap();
    ///     let as_utc = d.with_offset(UTC);
    ///
    ///     assert_eq!(d, as_utc);
    /// }
    /// ```
    #[unstable]
    pub fn consume_datetime(&mut self) -> ParsingResult<DateTime<FixedOffset>> {
        // Handle the optional day ","
        self.parser.push_position();
        let day_of_week = self.parser.consume_word(false);
        if day_of_week.is_some() {
            let lower_dow = day_of_week.unwrap().into_ascii_lowercase();
            if DAYS_OF_WEEK.position_elem(&lower_dow.as_slice()).is_some() {
                // Lose the ","
                self.parser.consume_while(|c| { c == ',' || c.is_whitespace() });
            } else {
                // What we read doesn't look like a day, so ignore it,
                // go back to the start and continue on.
                self.parser.pop_position();
            }
        } else {
            // We don't have a leading day "," so go back to the start.
            self.parser.pop_position();
        }

        let day_of_month = match self.consume_u32() {
            Some(x) => x,
            None => return Err(ParsingError::new("Expected day of month, a number.".to_string()))
        };
        self.parser.consume_linear_whitespace();

        let month = match self.parser.consume_word(false) {
            // FIXME: Move into consume_month?
            Some(s) => {
                let lower_month = s.into_ascii_lowercase();
                // Add one because months are 1 indexed, array is 0 indexed.
                match MONTHS.position_elem(&lower_month.as_slice()).map(|i| { (i + 1) as u32 }) {
                    Some(x) => x,
                    None => return Err(ParsingError::new(format!("Invalid month: {}", lower_month)))
                }
            },
            None => return Err(ParsingError::new("Expected month.".to_string()))
        };
        self.parser.consume_linear_whitespace();

        let year = match self.consume_u32() {
            Some(i) => {
                // See RFC5322 4.3 for justification of obsolete year format handling.
                match i {
                    // 2 digit year between 0 and 49 is assumed to be in the 2000s
                    0...49 => { i + 2000 },
                    // 2 digit year greater than 50 and 3 digit years are added to 1900
                    50...999 => { i + 1900 },
                    _ => { i },
                }
            },
            None => return Err(ParsingError::new("Expected year.".to_string())),
        };
        self.parser.consume_linear_whitespace();

        let time = match self.consume_time() {  // FIXME
            Some(x) => x,
            None => return Err(ParsingError::new("Expected time.".to_string()))
        };
        self.parser.consume_linear_whitespace();
        let tz_offset = try!(self.consume_timezone_offset());

        let (hour, minute, second) = time;

        Ok(FixedOffset::east(tz_offset).ymd(
            year as i32, month, day_of_month
        ).and_hms(
            hour, minute, second
        ))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::{
        DateTime,
        FixedOffset,
        Offset,
    };

    #[test]
    fn test_time_parse() {
        struct TimeParseTest<'s> {
            input: &'s str,
            result: Option<DateTime<FixedOffset>>,
        }

        let edt = FixedOffset::east(-14400); // UTC-0400
        let cet = FixedOffset::east(3600);  // UTC+0100
        let napal = FixedOffset::east(20700); // UTC+0545
        let utc = FixedOffset::east(0); // UTC+0000
        let tests = vec![
            TimeParseTest {
                input: "Mon, 20 Jun 1982 10:01:59 EDT",
                result: Some(edt.ymd(1982, 6, 20).and_hms(10, 1, 59))
            },
            TimeParseTest {
                // Check the 2 digit date parsing logic, >=50
                input: "Mon, 20 Jun 82 10:01:59 EDT",
                result: Some(edt.ymd(1982, 6, 20).and_hms(10, 1, 59))
            },
            TimeParseTest {
                // Check the 2 digit date parsing logic, <50
                input: "Mon, 20 Jun 02 10:01:59 EDT",
                result: Some(edt.ymd(2002, 6, 20).and_hms(10, 1, 59))
            },
            TimeParseTest {
                // Check the optional seconds
                input: "Mon, 20 Jun 1982 10:01 EDT",
                result: Some(edt.ymd(1982, 6, 20).and_hms(10, 1, 0))
            },
            TimeParseTest {
                // Check different TZ parsing
                input: "Mon, 20 Jun 1982 10:01:59 +0100",
                result: Some(cet.ymd(1982, 6, 20).and_hms(10, 1, 59))
            },
            TimeParseTest {
                input: "Mon, 20 Jun 1982 10:01:59 -0400",
                result: Some(edt.ymd(1982, 6, 20).and_hms(10, 1, 59))
            },
            TimeParseTest {
                // Test for wierd minute offsets in TZ
                input: "Mon, 20 Jun 1982 10:01:59 +0545",
                result: Some(napal.ymd(1982, 6, 20).and_hms(10, 1, 59))
            },
            TimeParseTest {
                // Test for being able to skip day of week
                input: "09 Jan 2012 21:20:00 +0000",
                result: Some(utc.ymd(2012, 1, 9).and_hms(21, 20, 00))
            },
        ];

        for test in tests.into_iter() {
            let mut parser = Rfc822DateParser::new(test.input);
            assert_eq!(parser.consume_datetime(), test.result);
        }
    }

}
<commit_msg>Switch consume_time to Results<commit_after>use std::ascii::OwnedAsciiExt;
use std::collections::HashMap;

use chrono::{
    DateTime,
    FixedOffset,
    Offset,
};

use super::rfc5322::Rfc5322Parser;
use super::results::{ParsingError, ParsingResult};

static DAYS_OF_WEEK: [&'static str; 7] = [
    "mon", "tue", "wed", "thu",
    "fri", "sat", "sun",
];

static MONTHS: [&'static str; 12] = [
    "jan", "feb", "mar", "apr",
    "may", "jun", "jul", "aug",
    "sep", "oct", "nov", "dec"
];

// Lazily build TZ_DATA when we need it.
lazy_static!{
    static ref TZ_DATA: HashMap<&'static str, i32> = {
        let mut map = HashMap::new();
        map.insert("Z",    0); // Zulu
        map.insert("UT",   0);
        map.insert("GMT",  0);
        map.insert("PST", -28800); // UTC-8
        map.insert("PDT", -25200); // UTC-7
        map.insert("MST", -25200); // UTC-7
        map.insert("MDT", -21600); // UTC-6
        map.insert("CST", -21600); // UTC-6
        map.insert("CDT", -18000); // UTC-5
        map.insert("EST", -18000); // UTC-5
        map.insert("EDT", -14400); // UTC-4
        map
    };
}

/// Parser for RFC822 style dates, as defined by Section 5.
///
/// Note that this also supports the additions as specified in
/// RFC5322 Section 3.3 while still being backward compatible.
#[unstable]
pub struct Rfc822DateParser<'s> {
    parser: Rfc5322Parser<'s>,
}

impl<'s> Rfc822DateParser<'s> {
    #[unstable]
    pub fn new(s: &'s str) -> Rfc822DateParser<'s> {
        Rfc822DateParser {
            parser: Rfc5322Parser::new(s),
        }
    }

    #[inline]
    fn consume_u32(&mut self) -> Option<u32> {
        match self.parser.consume_word(false) {
            Some(s) => match s.parse() {  // FIXME
                Ok(x) => Some(x),
                Err(_) => None
            },
            None => None,
        }
    }

    fn consume_time(&mut self) -> ParsingResult<(u32, u32, u32)> {
        let hour = match self.consume_u32() {
            Some(x) => x,
            None => return Err(ParsingError::new(
                    "Failed to parse time: Expected hour, a number.".to_string()))
        };

        try!(self.parser.assert_char(':'));
        self.parser.consume_char();

        let minute = match self.consume_u32() {
            Some(x) => x,
            None => return Err(ParsingError::new("Failed to parse time: Expected minute.".to_string()))
        };

        // Seconds are optional, only try to parse if we see the next seperator.
        let second = match self.parser.assert_char(':') {
            Ok(_) => {
                self.parser.consume_char();
                self.consume_u32()
            },
            Err(_) => None
        }.unwrap_or(0);

        Ok((hour, minute, second))
    }

    fn consume_timezone_offset(&mut self) -> ParsingResult<i32> {
        match self.parser.consume_word(false) {
            Some(s) => {
                // from_str doesn't like leading '+' to indicate positive,
                // so strip it off if it's there.
                let mut s_slice = s.as_slice();
                s_slice = if s_slice.starts_with("+") {
                    &s_slice[1..]
                } else {
                    s_slice
                };
                // Try to parse zone as an int
                match s_slice.parse::<i32>() {
                    Ok(i) => {
                        let offset_hours = i / 100;
                        let offset_mins = i % 100;
                        Ok(offset_hours * 3600 + offset_mins * 60)
                    },
                    Err(_) => {
                        // Isn't an int, so try to use the strings->TZ hash.
                        match TZ_DATA.get(s_slice) {
                            Some(offset) => Ok(offset.clone()),
                            None => Err(ParsingError::new(format!("Invalid timezone: {}", s_slice)))
                        }
                    }
                }
            },
            None => Err(ParsingError::new("Expected timezone offset.".to_string()))
        }
    }

    /// Consume a DateTime from the input.
    ///
    /// If successful, returns a DateTime with a fixed offset based on the
    /// timezone parsed. You may wish to deal with this in UTC, in which case
    /// you may want something like
    ///
    /// ```
    /// extern crate chrono;
    /// extern crate email;
    ///
    /// use email::rfc822::Rfc822DateParser;
    /// use chrono::UTC;
    ///
    /// fn main() {
    ///     let mut p = Rfc822DateParser::new("Thu, 18 Dec 2014 21:07:22 +0100");
    ///     let d = p.consume_datetime().unwrap();
    ///     let as_utc = d.with_offset(UTC);
    ///
    ///     assert_eq!(d, as_utc);
    /// }
    /// ```
    #[unstable]
    pub fn consume_datetime(&mut self) -> ParsingResult<DateTime<FixedOffset>> {
        // Handle the optional day ","
        self.parser.push_position();
        let day_of_week = self.parser.consume_word(false);
        if day_of_week.is_some() {
            let lower_dow = day_of_week.unwrap().into_ascii_lowercase();
            if DAYS_OF_WEEK.position_elem(&lower_dow.as_slice()).is_some() {
                // Lose the ","
                self.parser.consume_while(|c| { c == ',' || c.is_whitespace() });
            } else {
                // What we read doesn't look like a day, so ignore it,
                // go back to the start and continue on.
                self.parser.pop_position();
            }
        } else {
            // We don't have a leading day "," so go back to the start.
            self.parser.pop_position();
        }

        let day_of_month = match self.consume_u32() {
            Some(x) => x,
            None => return Err(ParsingError::new("Expected day of month, a number.".to_string()))
        };
        self.parser.consume_linear_whitespace();

        let month = match self.parser.consume_word(false) {
            // FIXME: Move into consume_month?
            Some(s) => {
                let lower_month = s.into_ascii_lowercase();
                // Add one because months are 1 indexed, array is 0 indexed.
                match MONTHS.position_elem(&lower_month.as_slice()).map(|i| { (i + 1) as u32 }) {
                    Some(x) => x,
                    None => return Err(ParsingError::new(format!("Invalid month: {}", lower_month)))
                }
            },
            None => return Err(ParsingError::new("Expected month.".to_string()))
        };
        self.parser.consume_linear_whitespace();

        let year = match self.consume_u32() {
            Some(i) => {
                // See RFC5322 4.3 for justification of obsolete year format handling.
                match i {
                    // 2 digit year between 0 and 49 is assumed to be in the 2000s
                    0...49 => { i + 2000 },
                    // 2 digit year greater than 50 and 3 digit years are added to 1900
                    50...999 => { i + 1900 },
                    _ => { i },
                }
            },
            None => return Err(ParsingError::new("Expected year.".to_string())),
        };
        self.parser.consume_linear_whitespace();

        let time = try!(self.consume_time());
        self.parser.consume_linear_whitespace();

        let tz_offset = try!(self.consume_timezone_offset());

        let (hour, minute, second) = time;

        Ok(FixedOffset::east(tz_offset).ymd(
            year as i32, month, day_of_month
        ).and_hms(
            hour, minute, second
        ))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::{
        DateTime,
        FixedOffset,
        Offset,
    };

    #[test]
    fn test_time_parse() {
        struct TimeParseTest<'s> {
            input: &'s str,
            result: Option<DateTime<FixedOffset>>,
        }

        let edt = FixedOffset::east(-14400); // UTC-0400
        let cet = FixedOffset::east(3600);  // UTC+0100
        let napal = FixedOffset::east(20700); // UTC+0545
        let utc = FixedOffset::east(0); // UTC+0000
        let tests = vec![
            TimeParseTest {
                input: "Mon, 20 Jun 1982 10:01:59 EDT",
                result: Some(edt.ymd(1982, 6, 20).and_hms(10, 1, 59))
            },
            TimeParseTest {
                // Check the 2 digit date parsing logic, >=50
                input: "Mon, 20 Jun 82 10:01:59 EDT",
                result: Some(edt.ymd(1982, 6, 20).and_hms(10, 1, 59))
            },
            TimeParseTest {
                // Check the 2 digit date parsing logic, <50
                input: "Mon, 20 Jun 02 10:01:59 EDT",
                result: Some(edt.ymd(2002, 6, 20).and_hms(10, 1, 59))
            },
            TimeParseTest {
                // Check the optional seconds
                input: "Mon, 20 Jun 1982 10:01 EDT",
                result: Some(edt.ymd(1982, 6, 20).and_hms(10, 1, 0))
            },
            TimeParseTest {
                // Check different TZ parsing
                input: "Mon, 20 Jun 1982 10:01:59 +0100",
                result: Some(cet.ymd(1982, 6, 20).and_hms(10, 1, 59))
            },
            TimeParseTest {
                input: "Mon, 20 Jun 1982 10:01:59 -0400",
                result: Some(edt.ymd(1982, 6, 20).and_hms(10, 1, 59))
            },
            TimeParseTest {
                // Test for wierd minute offsets in TZ
                input: "Mon, 20 Jun 1982 10:01:59 +0545",
                result: Some(napal.ymd(1982, 6, 20).and_hms(10, 1, 59))
            },
            TimeParseTest {
                // Test for being able to skip day of week
                input: "09 Jan 2012 21:20:00 +0000",
                result: Some(utc.ymd(2012, 1, 9).and_hms(21, 20, 00))
            },
        ];

        for test in tests.into_iter() {
            let mut parser = Rfc822DateParser::new(test.input);
            assert_eq!(parser.consume_datetime(), test.result);
        }
    }

}
<|endoftext|>


<commit_before>//! This module provides a scoped API, allowing for taking an arbitrary number of `&mut T` into `T` within one closure.
//! The references are all required to outlive the closure.
//!
//! # Example
//! ```
//! use take_mut::scoped;
//! struct Foo;
//! let mut foo = Foo; // Must outlive scope
//! scoped::scope(|scope| {
//!     let (t, hole) = scope.take(&mut foo);
//!     drop(t);
//!     hole.fill(Foo); // If not called before the closure ends, causes an abort.
//! });
//! ```
//!
//! # Invalid Example (does not compile)
//! ```ignore
//! use take_mut::scoped;
//! struct Foo;
//! scoped::scope(|scope| {
//!     let mut foo = Foo; // Invalid because foo must come from outside the scope.
//!     let (t, hole) = scope.take(&mut foo);
//!     drop(t);
//!     hole.fill(Foo);
//! });
//! ```
//!
//! `Scope` also offers `take_or_recover`, which takes a function to call in the event the hole isn't filled.

#![warn(missing_docs)]

use std;
use std::panic;
use std::cell::Cell;
use std::marker::PhantomData;

/// Represents a scope within which, it is possible to take a `T` from a `&mut T` as long as the `&mut T` outlives the scope.
pub struct Scope<'s> {
    active_holes: Cell<usize>,
    marker: PhantomData<Cell<&'s mut ()>>
}

impl<'s> Scope<'s> {

    /// Takes a `(T, Hole<'c, 'm, T, F>)` from an `&'m mut T`.
    ///
    /// If the `Hole` is dropped without being filled, either due to panic or forgetting to fill, will run the `recovery` function to obtain a `T` to fill itself with.
    pub fn take_or_recover<'c, 'm: 's, T: 'm, F: FnOnce() -> T>(&'c self, mut_ref: &'m mut T, recovery: F) -> (T, Hole<'c, 'm, T, F>) {
        use std::ptr;

        let t: T;
        let hole: Hole<'c, 'm, T, F>;
        let num_of_holes = self.active_holes.get();
        if num_of_holes == std::usize::MAX {
            panic!("Too many holes!");
        }
        self.active_holes.set(num_of_holes + 1);
        unsafe {
            t = ptr::read(mut_ref as *mut T);
            hole = Hole {
                active_holes: &self.active_holes,
                hole: mut_ref as *mut T,
                phantom: PhantomData,
                recovery: Some(recovery)
            };
        };
        (t, hole)
    }

    /// Takes a `(T, Hole<'c, 'm, T, F>)` from an `&'m mut T`.
    pub fn take<'c, 'm: 's, T: 'm>(&'c self, mut_ref: &'m mut T) -> (T, Hole<'c, 'm, T, fn() -> T>) {
        #[allow(missing_docs)]
        fn panic<T>() -> T {
            panic!("Failed to recover a Hole!")
        }
        self.take_or_recover(mut_ref, panic)
    }
}

/// Main function to create a `Scope`.
///
/// If the given closure ends without all Holes filled, will abort the program.
pub fn scope<'s, F, R>(f: F) -> R
    where F: FnOnce(&Scope<'s>) -> R {
    let this = Scope { active_holes: Cell::new(0), marker: PhantomData };
    let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
        f(&this)
    }));
    if this.active_holes.get() != 0 {
        std::process::abort();
    }
    match result {
        Ok(r) => r,
        Err(p) => panic::resume_unwind(p),
    }

}

/// A `Hole<'c, 'm, T, F>` represents an unfilled `&'m mut T` which must be filled before the end of the `Scope` with lifetime `'c` and recovery closure `F`.
///
/// An unfilled `Hole<'c, 'm, T, F> that is destructed will try to use `F` to fill the hole.
///
/// If the scope ends without the `Hole` being filled, the program will `std::process::abort()`.
#[must_use]
pub struct Hole<'c, 'm, T: 'm, F: FnOnce() -> T> {
    active_holes: &'c Cell<usize>,
    hole: *mut T,
    phantom: PhantomData<&'m mut T>,
    recovery: Option<F>,
}

impl<'c, 'm, T: 'm, F: FnOnce() -> T> Hole<'c, 'm, T, F> {
    /// Fills the Hole.
    pub fn fill(self, t: T) {
        use std::ptr;
        use std::mem;

        unsafe {
            ptr::write(self.hole, t);
        }
        let num_holes = self.active_holes.get();
        self.active_holes.set(num_holes - 1);
        mem::forget(self);
    }
}

impl<'c, 'm, T: 'm, F: FnOnce() -> T> Drop for Hole<'c, 'm, T, F> {
    fn drop(&mut self) {
        use std::ptr;

        let t = (self.recovery.take().expect("No recovery function in Hole!"))();
        unsafe {
            ptr::write(self.hole, t);
        }
        let num_holes = self.active_holes.get();
        self.active_holes.set(num_holes - 1);
    }
}

#[test]
fn scope_based_take() {
    #[derive(Debug)]
    struct Foo;

    #[derive(Debug)]
    struct Bar {
        a: Foo,
        b: Foo
    }
    let mut bar = Bar { a: Foo, b: Foo };
    scope(|scope| {
        let (a, a_hole) = scope.take(&mut bar.a);
        let (b, b_hole) = scope.take(&mut bar.b);
        // Imagine consuming a and b
        a_hole.fill(Foo);
        b_hole.fill(Foo);
    });
    println!("{:?}", &bar);
}<commit_msg>Add test.<commit_after>//! This module provides a scoped API, allowing for taking an arbitrary number of `&mut T` into `T` within one closure.
//! The references are all required to outlive the closure.
//!
//! # Example
//! ```
//! use take_mut::scoped;
//! struct Foo;
//! let mut foo = Foo; // Must outlive scope
//! scoped::scope(|scope| {
//!     let (t, hole) = scope.take(&mut foo);
//!     drop(t);
//!     hole.fill(Foo); // If not called before the closure ends, causes an abort.
//! });
//! ```
//!
//! # Invalid Example (does not compile)
//! ```ignore
//! use take_mut::scoped;
//! struct Foo;
//! scoped::scope(|scope| {
//!     let mut foo = Foo; // Invalid because foo must come from outside the scope.
//!     let (t, hole) = scope.take(&mut foo);
//!     drop(t);
//!     hole.fill(Foo);
//! });
//! ```
//!
//! `Scope` also offers `take_or_recover`, which takes a function to call in the event the hole isn't filled.

#![warn(missing_docs)]

use std;
use std::panic;
use std::cell::Cell;
use std::marker::PhantomData;

/// Represents a scope within which, it is possible to take a `T` from a `&mut T` as long as the `&mut T` outlives the scope.
pub struct Scope<'s> {
    active_holes: Cell<usize>,
    marker: PhantomData<Cell<&'s mut ()>>
}

impl<'s> Scope<'s> {

    /// Takes a `(T, Hole<'c, 'm, T, F>)` from an `&'m mut T`.
    ///
    /// If the `Hole` is dropped without being filled, either due to panic or forgetting to fill, will run the `recovery` function to obtain a `T` to fill itself with.
    pub fn take_or_recover<'c, 'm: 's, T: 'm, F: FnOnce() -> T>(&'c self, mut_ref: &'m mut T, recovery: F) -> (T, Hole<'c, 'm, T, F>) {
        use std::ptr;

        let t: T;
        let hole: Hole<'c, 'm, T, F>;
        let num_of_holes = self.active_holes.get();
        if num_of_holes == std::usize::MAX {
            panic!("Too many holes!");
        }
        self.active_holes.set(num_of_holes + 1);
        unsafe {
            t = ptr::read(mut_ref as *mut T);
            hole = Hole {
                active_holes: &self.active_holes,
                hole: mut_ref as *mut T,
                phantom: PhantomData,
                recovery: Some(recovery)
            };
        };
        (t, hole)
    }

    /// Takes a `(T, Hole<'c, 'm, T, F>)` from an `&'m mut T`.
    pub fn take<'c, 'm: 's, T: 'm>(&'c self, mut_ref: &'m mut T) -> (T, Hole<'c, 'm, T, fn() -> T>) {
        #[allow(missing_docs)]
        fn panic<T>() -> T {
            panic!("Failed to recover a Hole!")
        }
        self.take_or_recover(mut_ref, panic)
    }
}

/// Main function to create a `Scope`.
///
/// If the given closure ends without all Holes filled, will abort the program.
pub fn scope<'s, F, R>(f: F) -> R
    where F: FnOnce(&Scope<'s>) -> R {
    let this = Scope { active_holes: Cell::new(0), marker: PhantomData };
    let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
        f(&this)
    }));
    if this.active_holes.get() != 0 {
        std::process::abort();
    }
    match result {
        Ok(r) => r,
        Err(p) => panic::resume_unwind(p),
    }

}

/// A `Hole<'c, 'm, T, F>` represents an unfilled `&'m mut T` which must be filled before the end of the `Scope` with lifetime `'c` and recovery closure `F`.
///
/// An unfilled `Hole<'c, 'm, T, F> that is destructed will try to use `F` to fill the hole.
///
/// If the scope ends without the `Hole` being filled, the program will `std::process::abort()`.
#[must_use]
pub struct Hole<'c, 'm, T: 'm, F: FnOnce() -> T> {
    active_holes: &'c Cell<usize>,
    hole: *mut T,
    phantom: PhantomData<&'m mut T>,
    recovery: Option<F>,
}

impl<'c, 'm, T: 'm, F: FnOnce() -> T> Hole<'c, 'm, T, F> {
    /// Fills the Hole.
    pub fn fill(self, t: T) {
        use std::ptr;
        use std::mem;

        unsafe {
            ptr::write(self.hole, t);
        }
        let num_holes = self.active_holes.get();
        self.active_holes.set(num_holes - 1);
        mem::forget(self);
    }
}

impl<'c, 'm, T: 'm, F: FnOnce() -> T> Drop for Hole<'c, 'm, T, F> {
    fn drop(&mut self) {
        use std::ptr;

        let t = (self.recovery.take().expect("No recovery function in Hole!"))();
        unsafe {
            ptr::write(self.hole, t);
        }
        let num_holes = self.active_holes.get();
        self.active_holes.set(num_holes - 1);
    }
}

#[test]
fn scope_based_take() {
    #[derive(Debug)]
    struct Foo;

    #[derive(Debug)]
    struct Bar {
        a: Foo,
        b: Foo
    }
    let mut bar = Bar { a: Foo, b: Foo };
    scope(|scope| {
        let (a, a_hole) = scope.take(&mut bar.a);
        let (b, b_hole) = scope.take(&mut bar.b);
        // Imagine consuming a and b
        a_hole.fill(Foo);
        b_hole.fill(Foo);
    });
    println!("{:?}", &bar);
}

#[test]
fn panic_on_recovered_panic() {
    use std::panic;

    struct Foo;
    let mut foo = Foo;
    let result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
        scope(|scope| {
            let (t, hole) = scope.take_or_recover(&mut foo, || Foo);
            panic!("Oops!");
        });
    }));
    assert!(result.is_err());
}<|endoftext|>


<commit_before>#![allow(dead_code)]
#![allow(unused_assignments)]

#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum Script {
    Latin,
    Cyrillic,
    Arabic,
    Devanagari,
    Ethiopic,
    Hebrew,
    Cmn,
    Kat
}

macro_rules! check_scripts {
    ( $text:ident, $( $script:expr => $is_script:ident ),* ) => {
        {
            let mut max_score = 0;
            let mut result = None;
            let mut current_score = 0;
            let half = $text.len() / 2;

            $(
                current_score = 0;
                for ch in $text.chars() {
                    if $is_script(ch) { current_score += 1; }
                }
                if current_score > half {
                    return Some($script);
                }
                if current_score > max_score {
                    max_score = current_score;
                    result = Some($script);
                }
            )*
            result
        }
    };
}

pub fn detect_script(text : String) -> Option<Script> {
    check_scripts!(
        text,
        Script::Cyrillic   => is_cyrillic,
        Script::Latin      => is_latin,
        Script::Arabic     => is_arabic,
        Script::Devanagari => is_devanagari,
        Script::Hebrew     => is_hebrew,
        Script::Ethiopic   => is_ethiopic,
        Script::Kat        => is_kat,
        Script::Cmn        => is_cmn
    )
}

#[inline(always)]
fn is_cyrillic(ch: char) -> bool {
   match ch {
       '\u{0400}'...'\u{0484}' |
       '\u{0487}'...'\u{052F}' |
       '\u{2DE0}'...'\u{2DFF}' |
       '\u{A640}'...'\u{A69D}' |
       '\u{1D2B}' |
       '\u{1D78}' |
       '\u{A69F}' => true,
       _ => false
   }
}

// https://en.wikipedia.org/wiki/Latin_script_in_Unicode
#[inline(always)]
fn is_latin(ch : char) -> bool {
    match ch {
        'a'...'z' |
        'A'...'Z' |
        '\u{0080}'...'\u{00FF}' |
        '\u{0100}'...'\u{017F}' |
        '\u{0180}'...'\u{024F}' |
        '\u{0250}'...'\u{02AF}' |
        '\u{1D00}'...'\u{1D7F}' |
        '\u{1D80}'...'\u{1DBF}' |
        '\u{1E00}'...'\u{1EFF}' |
        '\u{2100}'...'\u{214F}' |
        '\u{2C60}'...'\u{2C7F}' |
        '\u{A720}'...'\u{A7FF}' |
        '\u{AB30}'...'\u{AB6F}' => true,
        _ => false
    }
}

// Based on https://en.wikipedia.org/wiki/Arabic_script_in_Unicode
#[inline(always)]
fn is_arabic(ch : char) -> bool {
    match ch {
        '\u{0600}'...'\u{06FF}' |
        '\u{0750}'...'\u{07FF}' |
        '\u{08A0}'...'\u{08FF}' |
        '\u{FB50}'...'\u{FDFF}' |
        '\u{FE70}'...'\u{FEFF}' |
        '\u{10E60}'...'\u{10E7F}' |
        '\u{1EE00}'...'\u{1EEFF}' => true,
        _ => false
    }
}

// Based on https://en.wikipedia.org/wiki/Devanagari#Unicode
#[inline(always)]
fn is_devanagari(ch : char) -> bool {
    match ch {
        '\u{0900}'...'\u{097F}' |
        '\u{A8E0}'...'\u{A8FF}' |
        '\u{1CD0}'...'\u{1CFF}' => true,
        _ => false
    }
}

// Based on https://www.key-shortcut.com/en/writing-systems/ethiopian-script/
#[inline(always)]
fn is_ethiopic(ch : char) -> bool {
    match ch {
        '\u{1200}'...'\u{139F}' |
        '\u{2D80}'...'\u{2DDF}' |
        '\u{AB00}'...'\u{AB2F}' => true,
        _ => false
    }
}

// Based on https://en.wikipedia.org/wiki/Hebrew_(Unicode_block)
#[inline(always)]
fn is_hebrew(ch : char) -> bool {
    match ch {
        '\u{0590}'...'\u{05FF}' => true,
        _ => false
    }
}

// Is Georgian char?
#[inline(always)]
fn is_kat(ch : char) -> bool {
   match ch {
       '\u{10A0}'...'\u{10FF}' => true,
       _ => false
   }
}

// TODO: likely not the full set of possible chars..
#[inline(always)]
fn is_cmn(ch : char) -> bool {
    match ch {
        '\u{2E80}'...'\u{2E99}' |
        '\u{2E9B}'...'\u{2EF3}' |
        '\u{2F00}'...'\u{2FD5}' |
        '\u{3005}' |
        '\u{3007}' |
        '\u{3021}'...'\u{3029}' |
        '\u{3038}'...'\u{303B}' |
        '\u{3400}'...'\u{4DB5}' |
        '\u{4E00}'...'\u{9FCC}' |
        '\u{F900}'...'\u{FA6D}' |
        '\u{FA70}'...'\u{FAD9}' => true,
        _ => false
    }
}

#[cfg(test)]
mod tests {
    use super::Script;
    use super::is_cyrillic;
    use super::is_latin;
    use super::is_kat;
    use super::is_ethiopic;
    use super::detect_script;

    #[test]
    fn test_detect_script() {
        assert_eq!(detect_script("1234567890-,;!".to_string()), None);

        // One script
        assert_eq!(detect_script("Hello!".to_string()), Some(Script::Latin));
        assert_eq!(detect_script("Привет всем!".to_string()), Some(Script::Cyrillic));
        assert_eq!(detect_script("ქართული ენა მსოფლიო ".to_string()), Some(Script::Kat));
        assert_eq!(detect_script("県見夜上温国阪題富販".to_string()), Some(Script::Cmn));
        assert_eq!(detect_script(" ككل حوالي 1.6، ومعظم الناس ".to_string()), Some(Script::Arabic));
        assert_eq!(detect_script("हिमालयी वन चिड़िया (जूथेरा सालिमअली) चिड़िया की एक प्रजाति है".to_string()), Some(Script::Devanagari));
        assert_eq!(detect_script("היסטוריה והתפתחות של האלפבית העברי".to_string()), Some(Script::Hebrew));
        assert_eq!(detect_script("የኢትዮጵያ ፌዴራላዊ ዴሞክራሲያዊሪፐብሊክ".to_string()), Some(Script::Ethiopic));

        // Mixed scripts
        assert_eq!(detect_script("Привет! Текст на русском with some English.".to_string()), Some(Script::Cyrillic));
        assert_eq!(detect_script("Russian word любовь means love.".to_string()), Some(Script::Latin));
    }

    #[test]
    fn test_is_latin() {
        assert_eq!(is_latin('z'), true);
        assert_eq!(is_latin('A'), true);
        assert_eq!(is_latin('č'), true);
        assert_eq!(is_latin('š'), true);
        assert_eq!(is_latin('Ĵ'), true);

        assert_eq!(is_latin('ж'), false);
    }

    #[test]
    fn test_is_cyrillic() {
        assert_eq!(is_cyrillic('а'), true);
        assert_eq!(is_cyrillic('Я'), true);
        assert_eq!(is_cyrillic('Ґ'), true);
        assert_eq!(is_cyrillic('ї'), true);
        assert_eq!(is_cyrillic('Ꙕ'), true);

        assert_eq!(is_cyrillic('L'), false);
    }

    #[test]
    fn test_is_ethiopic() {
        assert_eq!(is_ethiopic('ፚ'), true);
        assert_eq!(is_ethiopic('ᎀ'), true);

        assert_eq!(is_ethiopic('а'), false);
        assert_eq!(is_cyrillic('L'), false);
    }

    #[test]
    fn test_is_kat() {
        assert_eq!(is_kat('რ'), true);
        assert_eq!(is_kat('ж'), false);
    }
}
<commit_msg>Modify detect_script to receive reference<commit_after>#![allow(dead_code)]
#![allow(unused_assignments)]

#[derive(PartialEq, Eq, Debug, Clone, Copy)]
pub enum Script {
    Latin,
    Cyrillic,
    Arabic,
    Devanagari,
    Ethiopic,
    Hebrew,
    Cmn,
    Kat
}

macro_rules! check_scripts {
    ( $text:ident, $( $script:expr => $is_script:ident ),* ) => {
        {
            let mut max_score = 0;
            let mut result = None;
            let mut current_score = 0;
            let half = $text.len() / 2;

            $(
                current_score = 0;
                for ch in $text.chars() {
                    if $is_script(ch) { current_score += 1; }
                }
                if current_score > half {
                    return Some($script);
                }
                if current_score > max_score {
                    max_score = current_score;
                    result = Some($script);
                }
            )*
            result
        }
    };
}

pub fn detect_script(text: &String) -> Option<Script> {
    check_scripts!(
        text,
        Script::Cyrillic   => is_cyrillic,
        Script::Latin      => is_latin,
        Script::Arabic     => is_arabic,
        Script::Devanagari => is_devanagari,
        Script::Hebrew     => is_hebrew,
        Script::Ethiopic   => is_ethiopic,
        Script::Kat        => is_kat,
        Script::Cmn        => is_cmn
    )
}

#[inline(always)]
fn is_cyrillic(ch: char) -> bool {
   match ch {
       '\u{0400}'...'\u{0484}' |
       '\u{0487}'...'\u{052F}' |
       '\u{2DE0}'...'\u{2DFF}' |
       '\u{A640}'...'\u{A69D}' |
       '\u{1D2B}' |
       '\u{1D78}' |
       '\u{A69F}' => true,
       _ => false
   }
}

// https://en.wikipedia.org/wiki/Latin_script_in_Unicode
#[inline(always)]
fn is_latin(ch : char) -> bool {
    match ch {
        'a'...'z' |
        'A'...'Z' |
        '\u{0080}'...'\u{00FF}' |
        '\u{0100}'...'\u{017F}' |
        '\u{0180}'...'\u{024F}' |
        '\u{0250}'...'\u{02AF}' |
        '\u{1D00}'...'\u{1D7F}' |
        '\u{1D80}'...'\u{1DBF}' |
        '\u{1E00}'...'\u{1EFF}' |
        '\u{2100}'...'\u{214F}' |
        '\u{2C60}'...'\u{2C7F}' |
        '\u{A720}'...'\u{A7FF}' |
        '\u{AB30}'...'\u{AB6F}' => true,
        _ => false
    }
}

// Based on https://en.wikipedia.org/wiki/Arabic_script_in_Unicode
#[inline(always)]
fn is_arabic(ch : char) -> bool {
    match ch {
        '\u{0600}'...'\u{06FF}' |
        '\u{0750}'...'\u{07FF}' |
        '\u{08A0}'...'\u{08FF}' |
        '\u{FB50}'...'\u{FDFF}' |
        '\u{FE70}'...'\u{FEFF}' |
        '\u{10E60}'...'\u{10E7F}' |
        '\u{1EE00}'...'\u{1EEFF}' => true,
        _ => false
    }
}

// Based on https://en.wikipedia.org/wiki/Devanagari#Unicode
#[inline(always)]
fn is_devanagari(ch : char) -> bool {
    match ch {
        '\u{0900}'...'\u{097F}' |
        '\u{A8E0}'...'\u{A8FF}' |
        '\u{1CD0}'...'\u{1CFF}' => true,
        _ => false
    }
}

// Based on https://www.key-shortcut.com/en/writing-systems/ethiopian-script/
#[inline(always)]
fn is_ethiopic(ch : char) -> bool {
    match ch {
        '\u{1200}'...'\u{139F}' |
        '\u{2D80}'...'\u{2DDF}' |
        '\u{AB00}'...'\u{AB2F}' => true,
        _ => false
    }
}

// Based on https://en.wikipedia.org/wiki/Hebrew_(Unicode_block)
#[inline(always)]
fn is_hebrew(ch : char) -> bool {
    match ch {
        '\u{0590}'...'\u{05FF}' => true,
        _ => false
    }
}

// Is Georgian char?
#[inline(always)]
fn is_kat(ch : char) -> bool {
   match ch {
       '\u{10A0}'...'\u{10FF}' => true,
       _ => false
   }
}

// TODO: likely not the full set of possible chars..
#[inline(always)]
fn is_cmn(ch : char) -> bool {
    match ch {
        '\u{2E80}'...'\u{2E99}' |
        '\u{2E9B}'...'\u{2EF3}' |
        '\u{2F00}'...'\u{2FD5}' |
        '\u{3005}' |
        '\u{3007}' |
        '\u{3021}'...'\u{3029}' |
        '\u{3038}'...'\u{303B}' |
        '\u{3400}'...'\u{4DB5}' |
        '\u{4E00}'...'\u{9FCC}' |
        '\u{F900}'...'\u{FA6D}' |
        '\u{FA70}'...'\u{FAD9}' => true,
        _ => false
    }
}

#[cfg(test)]
mod tests {
    use super::Script;
    use super::is_cyrillic;
    use super::is_latin;
    use super::is_kat;
    use super::is_ethiopic;
    use super::detect_script;

    #[test]
    fn test_detect_script() {
        assert_eq!(detect_script(&"1234567890-,;!".to_string()), None);

        // One script
        assert_eq!(detect_script(&"Hello!".to_string()), Some(Script::Latin));
        assert_eq!(detect_script(&"Привет всем!".to_string()), Some(Script::Cyrillic));
        assert_eq!(detect_script(&"ქართული ენა მსოფლიო ".to_string()), Some(Script::Kat));
        assert_eq!(detect_script(&"県見夜上温国阪題富販".to_string()), Some(Script::Cmn));
        assert_eq!(detect_script(&" ككل حوالي 1.6، ومعظم الناس ".to_string()), Some(Script::Arabic));
        assert_eq!(detect_script(&"हिमालयी वन चिड़िया (जूथेरा सालिमअली) चिड़िया की एक प्रजाति है".to_string()), Some(Script::Devanagari));
        assert_eq!(detect_script(&"היסטוריה והתפתחות של האלפבית העברי".to_string()), Some(Script::Hebrew));
        assert_eq!(detect_script(&"የኢትዮጵያ ፌዴራላዊ ዴሞክራሲያዊሪፐብሊክ".to_string()), Some(Script::Ethiopic));

        // Mixed scripts
        assert_eq!(detect_script(&"Привет! Текст на русском with some English.".to_string()), Some(Script::Cyrillic));
        assert_eq!(detect_script(&"Russian word любовь means love.".to_string()), Some(Script::Latin));
    }

    #[test]
    fn test_is_latin() {
        assert_eq!(is_latin('z'), true);
        assert_eq!(is_latin('A'), true);
        assert_eq!(is_latin('č'), true);
        assert_eq!(is_latin('š'), true);
        assert_eq!(is_latin('Ĵ'), true);

        assert_eq!(is_latin('ж'), false);
    }

    #[test]
    fn test_is_cyrillic() {
        assert_eq!(is_cyrillic('а'), true);
        assert_eq!(is_cyrillic('Я'), true);
        assert_eq!(is_cyrillic('Ґ'), true);
        assert_eq!(is_cyrillic('ї'), true);
        assert_eq!(is_cyrillic('Ꙕ'), true);

        assert_eq!(is_cyrillic('L'), false);
    }

    #[test]
    fn test_is_ethiopic() {
        assert_eq!(is_ethiopic('ፚ'), true);
        assert_eq!(is_ethiopic('ᎀ'), true);

        assert_eq!(is_ethiopic('а'), false);
        assert_eq!(is_cyrillic('L'), false);
    }

    #[test]
    fn test_is_kat() {
        assert_eq!(is_kat('რ'), true);
        assert_eq!(is_kat('ж'), false);
    }
}
<|endoftext|>


<commit_before>// ---------------------------------------------------------------------------------------
// Ruthenium, an ack-like searcher, (c) 2015 Georg Brandl.
// Licensed under the MIT license.
// ---------------------------------------------------------------------------------------

use std::cmp::min;
use std::path::Path;

#[cfg(feature = "pcre")]
use pcre::Regex;
#[cfg(not(feature = "pcre"))]
use regex::bytes::Regex;

use options::{Casing, Opts};

/// Represents a line that matched the pattern (maybe multiple times).
#[derive(Debug)]
pub struct Match {
    /// Line number in the file
    pub lineno: usize,
    /// Line text
    pub line: Vec<u8>,
    /// Spans (start, end) of matching parts in the line
    pub spans: Vec<(usize, usize)>,
    /// Context lines before the matched line
    pub before: Vec<Vec<u8>>,
    /// Context lines after the matched line
    pub after: Vec<Vec<u8>>,
}

impl Match {
    fn new(lineno: usize, line: &[u8], spans: Vec<(usize, usize)>) -> Match {
        Match {
            lineno: lineno,
            line: line.into(),
            spans: spans,
            before: Vec::new(),
            after: Vec::new(),
        }
    }
}

/// Represents all matches from a single file.
#[derive(Debug)]
pub struct FileResult {
    /// File name, relative to initial argument
    pub fname: String,
    /// Is the file binary?  If yes, matches contains 0 or 1 element
    pub is_binary: bool,
    /// Do we provide (and print) context lines?
    pub has_context: bool,
    /// Matches relevant for printing
    pub matches: Vec<Match>,
}

impl FileResult {
    fn new(fname: String) -> FileResult {
        FileResult {
            fname: fname,
            is_binary: false,
            has_context: false,
            matches: Vec::new(),
        }
    }
}

/// Create a regular expression to search for matches from the given options.
///
/// The final regex is determined by several options, such as casing options
/// and options to take the search string literally.
pub fn create_rx(opts: &Opts) -> Regex {
    let mut pattern = opts.pattern.to_owned();
    if opts.literal {
        // escape regex meta-chars and create a normal pattern
        const ESCAPE: &'static str = ".?*+|^$(){}[]\\";
        pattern = pattern.chars().map(|c| {
            if ESCAPE.find(c).is_some() {
                format!("\\{}", c)
            } else {
                format!("{}", c)
            }
        }).collect();
    }
    if let Casing::Insensitive = opts.casing {
        pattern = format!("(?i){}", pattern);
    } else if let Casing::Smart = opts.casing {
        // smart casing: only case-insensitive when pattern contains no uppercase
        if !pattern.chars().any(|c| c.is_uppercase()) {
            pattern = format!("(?i){}", pattern);
        }
    }
    Regex::new(&pattern).unwrap()
}

/// Return normalized path: get rid of leading ./ and make leading // into /.
fn normalized_path(path: &Path) -> String {
    let s = path.to_string_lossy();
    if s.starts_with("./") {
        String::from(&s[2..])
    } else if s.starts_with("//") {
        String::from(&s[1..])
    } else {
        s.into_owned()
    }
}

/// Check file for binary-ness.
///
/// Currently only null-bytes are recognized to constitute binary file content.
/// However, this clashes with UTF-16 and UTF-32, so a more clever heuristic is
/// required at some point.
fn is_binary(buf: &[u8], len: usize) -> bool {
    if len == 0 {
        return false;
    }
    if len >= 3 && &buf[0..3] == b"\xEF\xBB\xBF" {
        // UTF-8 BOM
        return false;
    }
    let n = min(512, len);
    for b in buf[..n].iter() {
        if *b == b'\x00' {
            return true;  // null byte always means binary
        }
    }
    false
}

/// Iterator-like struct for collecting lines within a u8 buffer.
struct Lines<'a> {
    buf: &'a [u8],
    lines: Vec<&'a [u8]>,
    start: usize,
    lineno: usize,
}

impl<'a> Lines<'a> {
    pub fn new(buf: &[u8]) -> Lines {
        Lines { buf: buf, lines: Vec::new(), start: 0, lineno: 0 }
    }

    /// Get next line in the main iteration.
    pub fn next(&mut self) -> Option<(usize, &'a [u8])> {
        let lno = self.lineno;
        self.lineno += 1;
        if lno < self.lines.len() {
            Some((lno, self.lines[lno]))
        } else if self.advance(lno) {
            Some((lno, self.lines[lno]))
        } else {
            None
        }
    }

    /// Get an arbitrary line as a string.
    pub fn get_line(&mut self, lineno: usize) -> Option<Vec<u8>> {
        if self.advance(lineno) {
            Some(self.lines[lineno].to_vec())
        } else {
            None
        }
    }

    /// Advance the line detection until we have at least need_idx+1 lines.
    /// Return false if EOF was reached before given number of lines.
    fn advance(&mut self, need_idx: usize) -> bool {
        while self.lines.len() < need_idx + 1 {
            if self.start >= self.buf.len() {
                return false;
            }
            let end = self.buf[self.start..].iter()
                                            .position(|&x| x == b'\n')
                                            .unwrap_or(self.buf.len() - self.start);
            let line = &self.buf[self.start..self.start+end];
            self.lines.push(line);
            self.start += end + 1;
        }
        true
    }
}

/// Search a single file (represented as a u8 buffer) for matching lines.
pub fn search(regex: &Regex, opts: &Opts, path: &Path, buf: &[u8]) -> FileResult {
    let len = buf.len();
    let mut result = FileResult::new(normalized_path(path));
    result.has_context = opts.before > 0 || opts.after > 0;
    // binary file?
    if is_binary(buf, len) {
        result.is_binary = true;
        // if we care for binaries at all
        if opts.do_binaries {
            // XXX: obviously the from_utf8 will fail for binary files
            if regex.is_match(buf) {
                // found a match: create a dummy match object, and
                // leave it there (we never need more info than
                // "matched" or "didn't match")
                result.matches.push(Match::new(0, &[], Vec::new()));
            }
        }
    } else {
        let mut lines = Lines::new(buf);
        while let Some((lineno, line)) = lines.next() {
            let mut spans = Vec::new();
            if let Some(span) = regex.find(line) {
                let mut searchfrom = span.1;
                // create a match object for this line (lineno is 1-based)
                spans.push(span);
                // search for further matches in this line
                while let Some((i0, i1)) = regex.find(&line[searchfrom..]) {
                    spans.push((searchfrom + i0, searchfrom + i1));
                    searchfrom += i1;
                }
            }
            if opts.invert != spans.is_empty() {
                // no match
                continue;
            }
            let mut m = Match::new(lineno + 1, line, spans);

            // collect "before" context for this match
            if opts.before > 0 {
                for lno in lineno.saturating_sub(opts.before)..lineno {
                    m.before.push(lines.get_line(lno).unwrap());
                }
            }
            // collect "after" context for this match
            if opts.after > 0 {
                for lno in lineno+1..lineno+opts.after+1 {
                    if let Some(line) = lines.get_line(lno) {
                        m.after.push(line);
                    }
                }
            }
            result.matches.push(m);

            if opts.only_files.is_some() {
                // need only one match per file for this mode
                break;
            } else if result.matches.len() >= opts.max_count {
                break;
            }
        }
    }
    result
}
<commit_msg>Remove outdated comment.<commit_after>// ---------------------------------------------------------------------------------------
// Ruthenium, an ack-like searcher, (c) 2015 Georg Brandl.
// Licensed under the MIT license.
// ---------------------------------------------------------------------------------------

use std::cmp::min;
use std::path::Path;

#[cfg(feature = "pcre")]
use pcre::Regex;
#[cfg(not(feature = "pcre"))]
use regex::bytes::Regex;

use options::{Casing, Opts};

/// Represents a line that matched the pattern (maybe multiple times).
#[derive(Debug)]
pub struct Match {
    /// Line number in the file
    pub lineno: usize,
    /// Line text
    pub line: Vec<u8>,
    /// Spans (start, end) of matching parts in the line
    pub spans: Vec<(usize, usize)>,
    /// Context lines before the matched line
    pub before: Vec<Vec<u8>>,
    /// Context lines after the matched line
    pub after: Vec<Vec<u8>>,
}

impl Match {
    fn new(lineno: usize, line: &[u8], spans: Vec<(usize, usize)>) -> Match {
        Match {
            lineno: lineno,
            line: line.into(),
            spans: spans,
            before: Vec::new(),
            after: Vec::new(),
        }
    }
}

/// Represents all matches from a single file.
#[derive(Debug)]
pub struct FileResult {
    /// File name, relative to initial argument
    pub fname: String,
    /// Is the file binary?  If yes, matches contains 0 or 1 element
    pub is_binary: bool,
    /// Do we provide (and print) context lines?
    pub has_context: bool,
    /// Matches relevant for printing
    pub matches: Vec<Match>,
}

impl FileResult {
    fn new(fname: String) -> FileResult {
        FileResult {
            fname: fname,
            is_binary: false,
            has_context: false,
            matches: Vec::new(),
        }
    }
}

/// Create a regular expression to search for matches from the given options.
///
/// The final regex is determined by several options, such as casing options
/// and options to take the search string literally.
pub fn create_rx(opts: &Opts) -> Regex {
    let mut pattern = opts.pattern.to_owned();
    if opts.literal {
        // escape regex meta-chars and create a normal pattern
        const ESCAPE: &'static str = ".?*+|^$(){}[]\\";
        pattern = pattern.chars().map(|c| {
            if ESCAPE.find(c).is_some() {
                format!("\\{}", c)
            } else {
                format!("{}", c)
            }
        }).collect();
    }
    if let Casing::Insensitive = opts.casing {
        pattern = format!("(?i){}", pattern);
    } else if let Casing::Smart = opts.casing {
        // smart casing: only case-insensitive when pattern contains no uppercase
        if !pattern.chars().any(|c| c.is_uppercase()) {
            pattern = format!("(?i){}", pattern);
        }
    }
    Regex::new(&pattern).unwrap()
}

/// Return normalized path: get rid of leading ./ and make leading // into /.
fn normalized_path(path: &Path) -> String {
    let s = path.to_string_lossy();
    if s.starts_with("./") {
        String::from(&s[2..])
    } else if s.starts_with("//") {
        String::from(&s[1..])
    } else {
        s.into_owned()
    }
}

/// Check file for binary-ness.
///
/// Currently only null-bytes are recognized to constitute binary file content.
/// However, this clashes with UTF-16 and UTF-32, so a more clever heuristic is
/// required at some point.
fn is_binary(buf: &[u8], len: usize) -> bool {
    if len == 0 {
        return false;
    }
    if len >= 3 && &buf[0..3] == b"\xEF\xBB\xBF" {
        // UTF-8 BOM
        return false;
    }
    let n = min(512, len);
    for b in buf[..n].iter() {
        if *b == b'\x00' {
            return true;  // null byte always means binary
        }
    }
    false
}

/// Iterator-like struct for collecting lines within a u8 buffer.
struct Lines<'a> {
    buf: &'a [u8],
    lines: Vec<&'a [u8]>,
    start: usize,
    lineno: usize,
}

impl<'a> Lines<'a> {
    pub fn new(buf: &[u8]) -> Lines {
        Lines { buf: buf, lines: Vec::new(), start: 0, lineno: 0 }
    }

    /// Get next line in the main iteration.
    pub fn next(&mut self) -> Option<(usize, &'a [u8])> {
        let lno = self.lineno;
        self.lineno += 1;
        if lno < self.lines.len() {
            Some((lno, self.lines[lno]))
        } else if self.advance(lno) {
            Some((lno, self.lines[lno]))
        } else {
            None
        }
    }

    /// Get an arbitrary line as a string.
    pub fn get_line(&mut self, lineno: usize) -> Option<Vec<u8>> {
        if self.advance(lineno) {
            Some(self.lines[lineno].to_vec())
        } else {
            None
        }
    }

    /// Advance the line detection until we have at least need_idx+1 lines.
    /// Return false if EOF was reached before given number of lines.
    fn advance(&mut self, need_idx: usize) -> bool {
        while self.lines.len() < need_idx + 1 {
            if self.start >= self.buf.len() {
                return false;
            }
            let end = self.buf[self.start..].iter()
                                            .position(|&x| x == b'\n')
                                            .unwrap_or(self.buf.len() - self.start);
            let line = &self.buf[self.start..self.start+end];
            self.lines.push(line);
            self.start += end + 1;
        }
        true
    }
}

/// Search a single file (represented as a u8 buffer) for matching lines.
pub fn search(regex: &Regex, opts: &Opts, path: &Path, buf: &[u8]) -> FileResult {
    let len = buf.len();
    let mut result = FileResult::new(normalized_path(path));
    result.has_context = opts.before > 0 || opts.after > 0;
    // binary file?
    if is_binary(buf, len) {
        result.is_binary = true;
        // if we care for binaries at all
        if opts.do_binaries {
            if regex.is_match(buf) {
                // found a match: create a dummy match object, and
                // leave it there (we never need more info than
                // "matched" or "didn't match")
                result.matches.push(Match::new(0, &[], Vec::new()));
            }
        }
    } else {
        let mut lines = Lines::new(buf);
        while let Some((lineno, line)) = lines.next() {
            let mut spans = Vec::new();
            if let Some(span) = regex.find(line) {
                let mut searchfrom = span.1;
                // create a match object for this line (lineno is 1-based)
                spans.push(span);
                // search for further matches in this line
                while let Some((i0, i1)) = regex.find(&line[searchfrom..]) {
                    spans.push((searchfrom + i0, searchfrom + i1));
                    searchfrom += i1;
                }
            }
            if opts.invert != spans.is_empty() {
                // no match
                continue;
            }
            let mut m = Match::new(lineno + 1, line, spans);

            // collect "before" context for this match
            if opts.before > 0 {
                for lno in lineno.saturating_sub(opts.before)..lineno {
                    m.before.push(lines.get_line(lno).unwrap());
                }
            }
            // collect "after" context for this match
            if opts.after > 0 {
                for lno in lineno+1..lineno+opts.after+1 {
                    if let Some(line) = lines.get_line(lno) {
                        m.after.push(line);
                    }
                }
            }
            result.matches.push(m);

            if opts.only_files.is_some() {
                // need only one match per file for this mode
                break;
            } else if result.matches.len() >= opts.max_count {
                break;
            }
        }
    }
    result
}
<|endoftext|>


<commit_before>
use std::sync::mpsc::{channel, Sender, Receiver};
use std::thread;
use std::collections;
use std::error::Error;
use std::fmt;

use plugwise;

#[derive(Debug)]
pub enum SerialError {
    ConnectError(String)
}

impl Error for SerialError {
    fn description(&self) -> &str {
        match *self {
            SerialError::ConnectError(ref e) => &e[..]
        }
    }

    fn cause(&self) -> Option<&Error> {
        None
    }
}

impl fmt::Display for SerialError {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(f, "{}", self.description())
    }
}

enum Command {
    ConnectDevice(Option<Sender<ConnectResponse>>, String),
    ConnectStub,
    Hangup,
    RegisterCircle(String, u64),
    SwitchOn(String),
    SwitchOff(String),
}

enum ConnectResponse {
    Ok,
    ConnectionFailed(String),
}

pub struct Serial;

impl Serial {
    fn message_loop(rx: Receiver<Command>) {
        let mut plugwise = None;
        let mut circles = collections::HashMap::new();

        loop {
            let msg = rx.recv()
                        .expect("BUG: serial receive loop error");

            match msg {
                Command::ConnectDevice(tx, device) => {
                    let new_plugwise = plugwise::plugwise(plugwise::Device::Serial(device.clone()));

                    match new_plugwise {
                        Ok(new_plugwise) => {
                            plugwise = Some(new_plugwise);
                            if let Some(tx) = tx {
                                tx.send(ConnectResponse::Ok)
                                    .expect("unable to send response");
                            }
                        },
                        Err(err) => {
                            if let Some(tx) = tx {
                                tx.send(ConnectResponse::ConnectionFailed(err.description().into()))
                                    .expect("unable to send response");
                            }
                        }
                    }
                },
                Command::ConnectStub => {
                    let new_plugwise = plugwise::plugwise(plugwise::Device::Simulator).expect(
                                    "creating a simulation instance unexpectedly failed!");
                    plugwise = Some(new_plugwise);
                },
                Command::Hangup => break,
                Command::RegisterCircle(alias, mac) => {
                    if let Some(ref plugwise) = plugwise {
                        let circle = plugwise.create_circle(mac);
                        if let Ok(circle) = circle {
                            circles.insert(alias, circle);
                        }
                    }
                },
                Command::SwitchOn(circle) => {
                    if let Some(ref circle) = circles.get(&circle) {
                        circle.switch_on().expect("unable to switch on a circle");
                    }
                },
                Command::SwitchOff(circle) => {
                    if let Some(ref circle) = circles.get(&circle) {
                        circle.switch_off().expect("unable to switch off a circle");
                    }
                },
            }
        }
    }

    pub fn spawn() -> SerialClient {
        let (boot_tx, boot_rx) = channel();

        thread::spawn(move || {
            let (tx, rx) = channel();

            boot_tx.send(tx.clone())
                   .expect("BUG: bootstrap failed");

            Serial::message_loop(rx);
        });

        let response = boot_rx.recv()
                              .expect("BUG: bootstrap message expected");

        SerialClient {
            tx: response,
        }
    }
}

#[derive(Clone)]
pub struct SerialClient {
    tx: Sender<Command>,
}

impl SerialClient {
    pub fn connect_stub(&self) {
        self.tx.send(Command::ConnectStub)
               .expect("BUG: serial thread channel error");
    }

    pub fn connect_device(&self, device: &str) -> Result<(), SerialError> {
        let (tx, rx) = channel();

        self.tx.send(Command::ConnectDevice(Some(tx), device.into()))
               .expect("BUG: serial thread channel error");

        let response = rx.recv().expect("BUG: cannot receive answer from serial thread");

        match response {
            ConnectResponse::Ok => Ok(()),
            ConnectResponse::ConnectionFailed(err) => Err(SerialError::ConnectError(err))
        }
    }

    pub fn hangup(&self) {
        self.tx.send(Command::Hangup)
               .expect("BUG: cannot bring serial thread down");
    }

    pub fn register_circle(&self, alias: &str, mac: u64) {
        self.tx.send(Command::RegisterCircle(alias.into(), mac))
               .expect("BUG: cannot register circle");
    }

    pub fn switch_on(&self, alias: &str) {
        self.tx.send(Command::SwitchOn(alias.into()))
               .expect("BUG: unable to request to switch circle on");
    }

    pub fn switch_off(&self, alias: &str) {
        self.tx.send(Command::SwitchOff(alias.into()))
               .expect("BUG: unable to request to switch circle off");
    }
}
<commit_msg>Remove unnecessary panics during Circle communication<commit_after>
use std::sync::mpsc::{channel, Sender, Receiver};
use std::thread;
use std::collections;
use std::error::Error;
use std::fmt;

use plugwise;

#[derive(Debug)]
pub enum SerialError {
    ConnectError(String)
}

impl Error for SerialError {
    fn description(&self) -> &str {
        match *self {
            SerialError::ConnectError(ref e) => &e[..]
        }
    }

    fn cause(&self) -> Option<&Error> {
        None
    }
}

impl fmt::Display for SerialError {
    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
        write!(f, "{}", self.description())
    }
}

enum Command {
    ConnectDevice(Option<Sender<ConnectResponse>>, String),
    ConnectStub,
    Hangup,
    RegisterCircle(String, u64),
    SwitchOn(String),
    SwitchOff(String),
}

enum ConnectResponse {
    Ok,
    ConnectionFailed(String),
}

pub struct Serial;

impl Serial {
    fn message_loop(rx: Receiver<Command>) {
        let mut plugwise = None;
        let mut circles = collections::HashMap::new();

        loop {
            let msg = rx.recv()
                        .expect("BUG: serial receive loop error");

            match msg {
                Command::ConnectDevice(tx, device) => {
                    let new_plugwise = plugwise::plugwise(plugwise::Device::Serial(device.clone()));

                    match new_plugwise {
                        Ok(new_plugwise) => {
                            plugwise = Some(new_plugwise);
                            if let Some(tx) = tx {
                                tx.send(ConnectResponse::Ok)
                                    .expect("unable to send response");
                            }
                        },
                        Err(err) => {
                            if let Some(tx) = tx {
                                tx.send(ConnectResponse::ConnectionFailed(err.description().into()))
                                    .expect("unable to send response");
                            }
                        }
                    }
                },
                Command::ConnectStub => {
                    let new_plugwise = plugwise::plugwise(plugwise::Device::Simulator).expect(
                                    "creating a simulation instance unexpectedly failed!");
                    plugwise = Some(new_plugwise);
                },
                Command::Hangup => break,
                Command::RegisterCircle(alias, mac) => {
                    if let Some(ref plugwise) = plugwise {
                        let circle = plugwise.create_circle(mac);
                        if let Ok(circle) = circle {
                            circles.insert(alias, circle);
                        }
                    }
                },
                Command::SwitchOn(circle) => {
                    if let Some(ref circle_inst) = circles.get(&circle) {
                        if let Err(err) = circle_inst.switch_on() {
                            error!("unable to switch on a circle '{}' due to error {:?}",
                                   circle, err);
                        }
                    }
                },
                Command::SwitchOff(circle) => {
                    if let Some(ref circle_inst) = circles.get(&circle) {
                        if let Err(err) = circle_inst.switch_off() {
                            error!("unable to switch off a circle '{}' due to error {:?}",
                                   circle, err);
                        }
                    }
                },
            }
        }
    }

    pub fn spawn() -> SerialClient {
        let (boot_tx, boot_rx) = channel();

        thread::spawn(move || {
            let (tx, rx) = channel();

            boot_tx.send(tx.clone())
                   .expect("BUG: bootstrap failed");

            Serial::message_loop(rx);
        });

        let response = boot_rx.recv()
                              .expect("BUG: bootstrap message expected");

        SerialClient {
            tx: response,
        }
    }
}

#[derive(Clone)]
pub struct SerialClient {
    tx: Sender<Command>,
}

impl SerialClient {
    pub fn connect_stub(&self) {
        self.tx.send(Command::ConnectStub)
               .expect("BUG: serial thread channel error");
    }

    pub fn connect_device(&self, device: &str) -> Result<(), SerialError> {
        let (tx, rx) = channel();

        self.tx.send(Command::ConnectDevice(Some(tx), device.into()))
               .expect("BUG: serial thread channel error");

        let response = rx.recv().expect("BUG: cannot receive answer from serial thread");

        match response {
            ConnectResponse::Ok => Ok(()),
            ConnectResponse::ConnectionFailed(err) => Err(SerialError::ConnectError(err))
        }
    }

    pub fn hangup(&self) {
        self.tx.send(Command::Hangup)
               .expect("BUG: cannot bring serial thread down");
    }

    pub fn register_circle(&self, alias: &str, mac: u64) {
        self.tx.send(Command::RegisterCircle(alias.into(), mac))
               .expect("BUG: cannot register circle");
    }

    pub fn switch_on(&self, alias: &str) {
        self.tx.send(Command::SwitchOn(alias.into()))
               .expect("BUG: unable to request to switch circle on");
    }

    pub fn switch_off(&self, alias: &str) {
        self.tx.send(Command::SwitchOff(alias.into()))
               .expect("BUG: unable to request to switch circle off");
    }
}
<|endoftext|>