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

use crate::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 = hashset(&[
            "a",
            "b",
            "blockquote",
            "br",
            "code",
            "dd",
            "del",
            "dl",
            "dt",
            "em",
            "h1",
            "h2",
            "h3",
            "h4",
            "h5",
            "h6",
            "hr",
            "i",
            "img",
            "input",
            "kbd",
            "li",
            "ol",
            "p",
            "pre",
            "s",
            "strike",
            "strong",
            "sub",
            "sup",
            "table",
            "tbody",
            "td",
            "th",
            "thead",
            "tr",
            "ul",
            "hr",
            "span",
        ]);
        let tag_attributes = hashmap(&[
            ("a", hashset(&["href", "id", "target"])),
            ("img", hashset(&["width", "height", "src", "alt", "align"])),
            ("input", hashset(&["checked", "disabled", "type"])),
        ]);
        let allowed_classes = hashmap(&[(
            "code",
            hashset(&[
                "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",
            ]),
        )]);

        let sanitizer_base_url = base_url.map(ToString::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
        });

        fn is_media_url(url: &str) -> bool {
            Path::new(url)
                .extension()
                .and_then(std::ffi::OsStr::to_str)
                .map_or(false, |e| match e {
                    "png" | "svg" | "jpg" | "jpeg" | "gif" | "mp4" | "webm" | "ogg" => true,
                    _ => false,
                })
        }

        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('/');
            }
            if new_url.ends_with(".git/") {
                let offset = new_url.len() - 5;
                new_url.drain(offset..offset + 4);
            }
            // Assumes GitHub’s URL scheme. GitHub renders text and markdown
            // better in the "blob" view, but images need to be served raw.
            new_url += if is_media_url(url) {
                "raw/master"
            } else {
                "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 }
    }

    /// Renders the given markdown to HTML using the current settings.
    fn to_html(&self, text: &str) -> CargoResult<String> {
        let options = comrak::ComrakOptions {
            unsafe_: true, // The output will be sanitized with `ammonia`
            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"))
}

/// Helper function to build a new `HashSet` from the items slice.
fn hashset<T>(items: &[T]) -> std::collections::HashSet<T>
where
    T: Clone + Eq + std::hash::Hash,
{
    items.iter().cloned().collect()
}

/// Helper function to build a new `HashMap` from a slice of key-value pairs.
fn hashmap<K, V>(items: &[(K, V)]) -> std::collections::HashMap<K, V>
where
    K: Clone + Eq + std::hash::Hash,
    V: Clone,
{
    items.iter().cloned().collect()
}

#[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)";
        let image = "![alt](img.png)";

        for host in &["github.com", "gitlab.com", "bitbucket.org"] {
            for (&extra_slash, &dot_git) in [true, false].iter().zip(&[true, false]) {
                let url = format!(
                    "https://{}/rust-lang/test{}{}",
                    host,
                    if dot_git { ".git" } else { "" },
                    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(image, Some(&url)).unwrap();
                assert_eq!(
                    result,
                    format!(
                 "

\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>Refactor URL sanitization code for better readability.<commit_after>//! Render README files to HTML.

use ammonia::{Builder, UrlRelative, UrlRelativeEvaluate};
use htmlescape::encode_minimal;
use std::borrow::Cow;
use std::path::Path;
use url::Url;

use crate::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 = hashset(&[
            "a",
            "b",
            "blockquote",
            "br",
            "code",
            "dd",
            "del",
            "dl",
            "dt",
            "em",
            "h1",
            "h2",
            "h3",
            "h4",
            "h5",
            "h6",
            "hr",
            "i",
            "img",
            "input",
            "kbd",
            "li",
            "ol",
            "p",
            "pre",
            "s",
            "strike",
            "strong",
            "sub",
            "sup",
            "table",
            "tbody",
            "td",
            "th",
            "thead",
            "tr",
            "ul",
            "hr",
            "span",
        ]);
        let tag_attributes = hashmap(&[
            ("a", hashset(&["href", "id", "target"])),
            ("img", hashset(&["width", "height", "src", "alt", "align"])),
            ("input", hashset(&["checked", "disabled", "type"])),
        ]);
        let allowed_classes = hashmap(&[(
            "code",
            hashset(&[
                "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",
            ]),
        )]);
        let sanitize_url = UrlRelative::Custom(Box::new(SanitizeUrl::new(base_url)));

        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(sanitize_url)
            .id_prefix(Some("user-content-"));
        MarkdownRenderer { html_sanitizer }
    }

    /// Renders the given markdown to HTML using the current settings.
    fn to_html(&self, text: &str) -> CargoResult<String> {
        let options = comrak::ComrakOptions {
            unsafe_: true, // The output will be sanitized with `ammonia`
            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())
    }
}

/// Add trailing slash and remove `.git` suffix of base URL.
fn canon_base_url(mut base_url: String) -> String {
    if !base_url.ends_with('/') {
        base_url.push('/');
    }
    if base_url.ends_with(".git/") {
        let offset = base_url.len() - 5;
        base_url.drain(offset..offset + 4);
    }
    base_url
}

/// Sanitize relative URLs in README files.
struct SanitizeUrl {
    base_url: Option<String>,
}

impl SanitizeUrl {
    fn new(base_url: Option<&str>) -> Self {
        let base_url = base_url
            .and_then(|base_url| Url::parse(base_url).ok())
            .and_then(|url| match url.host_str() {
                Some("github.com") | Some("gitlab.com") | Some("bitbucket.org") => {
                    Some(canon_base_url(url.into_string()))
                }
                _ => None,
            });
        Self { base_url }
    }
}

/// Determine whether the given URL has a media file externsion.
fn is_media_url(url: &str) -> bool {
    Path::new(url)
        .extension()
        .and_then(std::ffi::OsStr::to_str)
        .map_or(false, |e| match e {
            "png" | "svg" | "jpg" | "jpeg" | "gif" | "mp4" | "webm" | "ogg" => true,
            _ => false,
        })
}

impl UrlRelativeEvaluate for SanitizeUrl {
    fn evaluate<'a>(&self, url: &'a str) -> Option<Cow<'a, str>> {
        if url.starts_with('#') {
            // Always allow fragment URLs.
            return Some(Cow::Borrowed(url));
        }
        self.base_url.as_ref().map(|base_url| {
            let mut new_url = base_url.clone();
            // Assumes GitHub’s URL scheme. GitHub renders text and markdown
            // better in the "blob" view, but images need to be served raw.
            new_url += if is_media_url(url) {
                "raw/master"
            } else {
                "blob/master"
            };
            if !url.starts_with('/') {
                new_url.push('/');
            }
            new_url += url;
            Cow::Owned(new_url)
        })
    }
}

/// 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"))
}

/// Helper function to build a new `HashSet` from the items slice.
fn hashset<T>(items: &[T]) -> std::collections::HashSet<T>
where
    T: Clone + Eq + std::hash::Hash,
{
    items.iter().cloned().collect()
}

/// Helper function to build a new `HashMap` from a slice of key-value pairs.
fn hashmap<K, V>(items: &[(K, V)]) -> std::collections::HashMap<K, V>
where
    K: Clone + Eq + std::hash::Hash,
    V: Clone,
{
    items.iter().cloned().collect()
}

#[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)";
        let image = "![alt](img.png)";

        for host in &["github.com", "gitlab.com", "bitbucket.org"] {
            for (&extra_slash, &dot_git) in [true, false].iter().zip(&[true, false]) {
                let url = format!(
                    "https://{}/rust-lang/test{}{}",
                    host,
                    if dot_git { ".git" } else { "" },
                    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(image, Some(&url)).unwrap();
                assert_eq!(
                    result,
                    format!(
                 "

\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>pub type c_char = i8;
pub type c_long = i64;
pub type c_ulong = u64;

pub type wchar_t = i16;

pub type off_t = usize;
pub type mode_t = u16;
pub type time_t = i64;
pub type pid_t = usize;
pub type gid_t = usize;
pub type uid_t = usize;

pub type in_addr_t = u32;
pub type in_port_t = u16;

pub type socklen_t = u32;
pub type sa_family_t = u16;

#[derive(Copy, Clone)]
#[repr(C)]
pub struct in_addr {
    pub s_addr: in_addr_t,
}

#[derive(Copy, Clone)]
#[repr(C)]
pub struct in6_addr {
    pub s6_addr: [u8; 16],
    __align: [u32; 0],
}

#[derive(Copy, Clone)]
#[repr(C)]
pub struct sockaddr {
    pub sa_family: sa_family_t,
    pub sa_data: [::c_char; 14],
}

#[derive(Copy, Clone)]
#[repr(C)]
pub struct sockaddr_in {
    pub sin_family: sa_family_t,
    pub sin_port: ::in_port_t,
    pub sin_addr: ::in_addr,
    pub sin_zero: [u8; 8],
}

#[derive(Copy, Clone)]
#[repr(C)]
pub struct sockaddr_in6 {
    pub sin6_family: sa_family_t,
    pub sin6_port: in_port_t,
    pub sin6_flowinfo: u32,
    pub sin6_addr: ::in6_addr,
    pub sin6_scope_id: u32,
}
<commit_msg>Place structs inside of s!<commit_after>pub type c_char = i8;
pub type c_long = i64;
pub type c_ulong = u64;

pub type wchar_t = i16;

pub type off_t = usize;
pub type mode_t = u16;
pub type time_t = i64;
pub type pid_t = usize;
pub type gid_t = usize;
pub type uid_t = usize;

pub type in_addr_t = u32;
pub type in_port_t = u16;

pub type socklen_t = u32;
pub type sa_family_t = u16;

s! {
    pub struct in_addr {
        pub s_addr: in_addr_t,
    }

    pub struct in6_addr {
        pub s6_addr: [u8; 16],
        __align: [u32; 0],
    }

    pub struct sockaddr {
        pub sa_family: sa_family_t,
        pub sa_data: [::c_char; 14],
    }

    pub struct sockaddr_in {
        pub sin_family: sa_family_t,
        pub sin_port: ::in_port_t,
        pub sin_addr: ::in_addr,
        pub sin_zero: [u8; 8],
    }

    pub struct sockaddr_in6 {
        pub sin6_family: sa_family_t,
        pub sin6_port: in_port_t,
        pub sin6_flowinfo: u32,
        pub sin6_addr: ::in6_addr,
        pub sin6_scope_id: u32,
    }
}
<|endoftext|>


<commit_before>use chrono::Local;
use database::Database;
use rutracker::forum::{Post, RutrackerForum, Topic, MESSAGE_LEN};

pub type Result<T> = ::std::result::Result<T, ::failure::Error>;

#[derive(Debug)]
pub struct List {
    forum: usize,
    size: f64,
    id: Vec<usize>,
}

impl List {
    pub fn new(db: &Database, forum: usize, id: Vec<usize>) -> Result<Self> {
        let size = db
            .get_tor_topic_data(&id)?
            .iter()
            .map(|(_, data)| data.size)
            .sum();
        Ok(Self { forum, id, size })
    }

    fn convert_size(size: f64) -> String {
        if size < 1f64 {
            format!("{:.2} B", size)
        } else {
            match size.log2() as usize {
                0...9 => format!("{:.2} B", size),
                10...19 => format!("{:.2} KB", size / 10f64.exp2()),
                20...29 => format!("{:.2} МB", size / 20f64.exp2()),
                _ => format!("{:.2} GB", size / 30f64.exp2()),
            }
        }
    }

    pub fn to_bbcode(&self, db: &Database, date: &str, max_len: usize) -> Result<Vec<String>> {
        let header_len = 200;
        let body_len = max_len - header_len;
        let mut message = String::with_capacity(max_len);
        message.push_str(
            format!(
                "Актуально на: [color=darkblue]{}[/color]\n\
                 Всего хранимых раздач в подразделе: {} шт. / {}\n",
                date,
                self.id.len(),
                List::convert_size(self.size)
            ).as_str(),
        );
        let mut item: Vec<(usize, String, f64)> = db
            .get_tor_topic_data(&self.id)?
            .into_iter()
            .map(|(id, data)| (id, data.topic_title, data.size))
            .collect();
        item.sort_unstable_by(|a, b| a.1.as_str().cmp(b.1.as_str()));
        let mut item: Vec<String> = item
            .into_iter()
            .map(|(id, topic_title, size)| {
                format!(
                    "[*][url=viewtopic.php?t={}]{}[/url] {}\n",
                    id,
                    topic_title,
                    List::convert_size(size)
                )
            })
            .collect();
        let mut num = Vec::new();
        let mut len = 0;
        for (n, s) in item.iter().enumerate() {
            if len + s.len() > body_len {
                len = 0;
                num.push(n);
            } else {
                len += s.len();
            }
        }
        debug!("List::to_bbcode::num {:?}", num);
        let mut vec = Vec::with_capacity(num.len() + 1);
        message.push_str(
            format!(
                "[spoiler=\"№№ 1 — {}\"][list=1]\n",
                num.get(0).unwrap_or(&self.id.len())
            ).as_str(),
        );
        item.iter()
            .take(*num.get(0).unwrap_or(&self.id.len()))
            .for_each(|i| message.push_str(i));
        message.push_str("[/list][/spoiler]\n");
        vec.push(message);
        for i in 0..num.len() {
            let mut message = String::with_capacity(max_len);
            message.push_str(
                format!(
                    "[spoiler=\"№№ {} — {}\"][list=1]\n",
                    num[i] + 1,
                    num.get(i + 1).unwrap_or(&self.id.len())
                ).as_str(),
            );
            item[num[i]].insert_str(2, format!("={}", num[i] + 1).as_str());
            item.iter()
                .skip(num[i])
                .take(*num.get(i + 1).unwrap_or(&(self.id.len() + 1)) - num[i])
                .for_each(|i| message.push_str(i));
            message.push_str("[/list][/spoiler]\n");
            vec.push(message);
        }
        Ok(vec)
    }
}

#[derive(Debug)]
pub struct Report<'a> {
    list: Vec<List>,
    date: String,
    db: &'a Database,
    forum: &'a RutrackerForum,
}

impl<'a> Report<'a> {
    pub fn new(db: &'a Database, forum: &'a RutrackerForum) -> Self {
        Self {
            list: Vec::new(),
            date: Local::now().format("%d.%m.%Y").to_string(),
            db,
            forum,
        }
    }

    pub fn add_list(&mut self, list: List) {
        self.list.push(list);
    }

    pub fn send_list_header(&self, forum_id: usize, topic: &Topic, post: &Post) -> Result<()> {
        info!("Формирование статиски подраздела...");
        let forum_size = self.db.get_forum_size(forum_id)?.unwrap_or((0, 0f64));
        let (keeper, torrent_id) = topic.get_stored_torrents()?;
        let mut torrent: Vec<usize> = torrent_id.iter().flat_map(|id| id).cloned().collect();
        torrent.sort();
        torrent.dedup();
        let torrent_info = self.db.get_tor_topic_data(torrent)?;
        let count = torrent_info.len();
        let size = torrent_info.values().map(|d| d.size).sum();
        let mut message = format!(
            "[url=viewforum.php?f={}][u][color=#006699]{}[/u][/color][/url] \
             | [url=tracker.php?f={0}&tm=-1&o=10&s=1&oop=1][color=indigo]\
             [u]Проверка сидов[/u][/color][/url]\n\n\
             Актуально на: [color=darkblue]{}[/color]\n\
             Всего раздач в подразделе: {} шт. / {}\n\
             Всего хранимых раздач в подразделе: \
             {} шт. / {}\n\
             Количество хранителей: {}\n[hr]\n",
            forum_id,
            topic.title.split(" » ").last().unwrap_or(""),
            self.date,
            forum_size.0,
            List::convert_size(forum_size.1),
            count,
            List::convert_size(size),
            keeper.len()
        );
        for (num, name) in keeper.iter().enumerate() {
            message.push_str(
                format!(
                    "Хранитель {}: \
                     [url=profile.php?mode=viewprofile&{}&name=1]\
                     [u][color=#006699]{}[/u][/color][/url] \
                     [color=gray]~>[/color] {} шт. [color=gray]~>[/color] {}\n",
                    num + 1,
                    RutrackerForum::encode(&[("u", name)]),
                    name,
                    torrent_id[num].len(),
                    List::convert_size(
                        torrent_id[num]
                            .iter()
                            .filter_map(|id| torrent_info.get(id))
                            .map(|d| d.size)
                            .sum()
                    )
                ).as_str(),
            );
        }
        debug!("{}", message);
        post.edit(message.as_str())?;
        Ok(())
    }

    pub fn send_list(&self, list: &List, topic: &Topic) -> Result<Option<usize>> {
        let messages = list.to_bbcode(self.db, &self.date, MESSAGE_LEN)?;
        let posts = topic.get_user_posts()?;
        let name = self.forum.user.name.as_str();
        let post_id = {
            let mut message = messages.iter();
            let mut post = posts.iter().skip(if topic.author == name { 1 } else { 0 });
            let post_id = match (post.next(), message.next()) {
                (Some(post), Some(message)) => {
                    post.edit(message)?;
                    Some(post.id)
                }
                (None, Some(message)) => topic.reply(message)?,
                _ => unreachable!(),
            };
            loop {
                match (post.next(), message.next()) {
                    (Some(post), Some(message)) => post.edit(message)?,
                    (Some(post), None) => post.edit("резерв")?,
                    (None, Some(message)) => topic.reply(message).map(|_| ())?,
                    (None, None) => break,
                }
            }
            post_id
        };
        if topic.author == name {
            self.send_list_header(list.forum, topic, &posts[0])?;
        }
        Ok(post_id)
    }

    pub fn send_all_list(&self) -> Result<Vec<(Option<usize>, String, usize, f64)>> {
        let keeper_working_forum = self.forum.get_keepers_working_forum();
        let mut topics = self.db.get_topic_with_subforum_list(
            &keeper_working_forum,
            &self.list.iter().map(|r| r.forum).collect::<Vec<usize>>(),
        )?;
        let mut vec = Vec::with_capacity(topics.len());
        for list in &self.list {
            if let Some((id, author, title)) = topics.remove(&list.forum) {
                let topic = keeper_working_forum.get_topic(id, author, title);
                vec.push((
                    self.send_list(list, &topic)?,
                    topic.title,
                    list.id.len(),
                    list.size,
                ));
            }
        }
        Ok(vec)
    }

    pub fn send_all(&self) -> Result<()> {
        let mut vec = self.send_all_list()?;
        let (count, size) = vec.iter().fold((0, 0f64), |(count, size), (_, _, c, s)| {
            (count + c, size + s)
        });
        let mut message = format!(
            "Актуально на: {}\n\
             Общее количество хранимых раздач: {} шт.\n\
             Общий вес хранимых раздач: {}\n[hr]",
            self.date,
            count,
            List::convert_size(size),
        );
        vec.sort_unstable_by(|a, b| a.1.as_str().cmp(b.1.as_str()));
        for (id, title, count, size) in vec {
            if let Some(id) = id {
                message.push_str(
                    format!(
                        "[url=viewtopic.php?p={}#{0}][u]{}[/u][/url] — {} шт. ({})",
                        id,
                        title,
                        count,
                        List::convert_size(size)
                    ).as_str(),
                );
            } else {
                message.push_str(
                    format!(
                        "[u]{}[/u] — {} шт. ({})",
                        title,
                        count,
                        List::convert_size(size)
                    ).as_str(),
                );
            }
        }
        debug!("{}", message);
        let keepers_forum = self.forum.get_keepers_forum();
        let summary_report = keepers_forum.get_topic(
            4275633,
            "Tokuchi_Toua",
            "Сводные отчеты работы в группе (публикация)",
        );
        let posts = summary_report.get_user_posts()?;
        if let Some(post) = posts.get(0) {
            post.edit(message.as_str())?;
            if posts.len() > 1 {
                warn!("В теме сводных отчётов должно быть не больше одного сообщения");
            }
        } else {
            summary_report.reply(message.as_str())?;
        }
        Ok(())
    }
}
<commit_msg>report: cut topic titile in summary report<commit_after>use chrono::Local;
use database::Database;
use rutracker::forum::{Post, RutrackerForum, Topic, MESSAGE_LEN};

pub type Result<T> = ::std::result::Result<T, ::failure::Error>;

#[derive(Debug)]
pub struct List {
    forum: usize,
    size: f64,
    id: Vec<usize>,
}

impl List {
    pub fn new(db: &Database, forum: usize, id: Vec<usize>) -> Result<Self> {
        let size = db
            .get_tor_topic_data(&id)?
            .iter()
            .map(|(_, data)| data.size)
            .sum();
        Ok(Self { forum, id, size })
    }

    fn convert_size(size: f64) -> String {
        if size < 1f64 {
            format!("{:.2} B", size)
        } else {
            match size.log2() as usize {
                0...9 => format!("{:.2} B", size),
                10...19 => format!("{:.2} KB", size / 10f64.exp2()),
                20...29 => format!("{:.2} МB", size / 20f64.exp2()),
                _ => format!("{:.2} GB", size / 30f64.exp2()),
            }
        }
    }

    pub fn to_bbcode(&self, db: &Database, date: &str, max_len: usize) -> Result<Vec<String>> {
        let header_len = 200;
        let body_len = max_len - header_len;
        let mut message = String::with_capacity(max_len);
        message.push_str(
            format!(
                "Актуально на: [color=darkblue]{}[/color]\n\
                 Всего хранимых раздач в подразделе: {} шт. / {}\n",
                date,
                self.id.len(),
                List::convert_size(self.size)
            ).as_str(),
        );
        let mut item: Vec<(usize, String, f64)> = db
            .get_tor_topic_data(&self.id)?
            .into_iter()
            .map(|(id, data)| (id, data.topic_title, data.size))
            .collect();
        item.sort_unstable_by(|a, b| a.1.as_str().cmp(b.1.as_str()));
        let mut item: Vec<String> = item
            .into_iter()
            .map(|(id, topic_title, size)| {
                format!(
                    "[*][url=viewtopic.php?t={}]{}[/url] {}\n",
                    id,
                    topic_title,
                    List::convert_size(size)
                )
            })
            .collect();
        let mut num = Vec::new();
        let mut len = 0;
        for (n, s) in item.iter().enumerate() {
            if len + s.len() > body_len {
                len = 0;
                num.push(n);
            } else {
                len += s.len();
            }
        }
        debug!("List::to_bbcode::num {:?}", num);
        let mut vec = Vec::with_capacity(num.len() + 1);
        message.push_str(
            format!(
                "[spoiler=\"№№ 1 — {}\"][list=1]\n",
                num.get(0).unwrap_or(&self.id.len())
            ).as_str(),
        );
        item.iter()
            .take(*num.get(0).unwrap_or(&self.id.len()))
            .for_each(|i| message.push_str(i));
        message.push_str("[/list][/spoiler]\n");
        vec.push(message);
        for i in 0..num.len() {
            let mut message = String::with_capacity(max_len);
            message.push_str(
                format!(
                    "[spoiler=\"№№ {} — {}\"][list=1]\n",
                    num[i] + 1,
                    num.get(i + 1).unwrap_or(&self.id.len())
                ).as_str(),
            );
            item[num[i]].insert_str(2, format!("={}", num[i] + 1).as_str());
            item.iter()
                .skip(num[i])
                .take(*num.get(i + 1).unwrap_or(&(self.id.len() + 1)) - num[i])
                .for_each(|i| message.push_str(i));
            message.push_str("[/list][/spoiler]\n");
            vec.push(message);
        }
        Ok(vec)
    }
}

#[derive(Debug)]
pub struct Report<'a> {
    list: Vec<List>,
    date: String,
    db: &'a Database,
    forum: &'a RutrackerForum,
}

impl<'a> Report<'a> {
    pub fn new(db: &'a Database, forum: &'a RutrackerForum) -> Self {
        Self {
            list: Vec::new(),
            date: Local::now().format("%d.%m.%Y").to_string(),
            db,
            forum,
        }
    }

    pub fn add_list(&mut self, list: List) {
        self.list.push(list);
    }

    pub fn send_list_header(&self, forum_id: usize, topic: &Topic, post: &Post) -> Result<()> {
        info!("Формирование статиски подраздела...");
        let forum_size = self.db.get_forum_size(forum_id)?.unwrap_or((0, 0f64));
        let (keeper, torrent_id) = topic.get_stored_torrents()?;
        let mut torrent: Vec<usize> = torrent_id.iter().flat_map(|id| id).cloned().collect();
        torrent.sort();
        torrent.dedup();
        let torrent_info = self.db.get_tor_topic_data(torrent)?;
        let count = torrent_info.len();
        let size = torrent_info.values().map(|d| d.size).sum();
        let mut message = format!(
            "[url=viewforum.php?f={}][u][color=#006699]{}[/u][/color][/url] \
             | [url=tracker.php?f={0}&tm=-1&o=10&s=1&oop=1][color=indigo]\
             [u]Проверка сидов[/u][/color][/url]\n\n\
             Актуально на: [color=darkblue]{}[/color]\n\
             Всего раздач в подразделе: {} шт. / {}\n\
             Всего хранимых раздач в подразделе: \
             {} шт. / {}\n\
             Количество хранителей: {}\n[hr]\n",
            forum_id,
            topic.title.split(" » ").last().unwrap_or(""),
            self.date,
            forum_size.0,
            List::convert_size(forum_size.1),
            count,
            List::convert_size(size),
            keeper.len()
        );
        for (num, name) in keeper.iter().enumerate() {
            message.push_str(
                format!(
                    "Хранитель {}: \
                     [url=profile.php?mode=viewprofile&{}&name=1]\
                     [u][color=#006699]{}[/u][/color][/url] \
                     [color=gray]~>[/color] {} шт. [color=gray]~>[/color] {}\n",
                    num + 1,
                    RutrackerForum::encode(&[("u", name)]),
                    name,
                    torrent_id[num].len(),
                    List::convert_size(
                        torrent_id[num]
                            .iter()
                            .filter_map(|id| torrent_info.get(id))
                            .map(|d| d.size)
                            .sum()
                    )
                ).as_str(),
            );
        }
        debug!("{}", message);
        post.edit(message.as_str())?;
        Ok(())
    }

    pub fn send_list(&self, list: &List, topic: &Topic) -> Result<Option<usize>> {
        let messages = list.to_bbcode(self.db, &self.date, MESSAGE_LEN)?;
        let posts = topic.get_user_posts()?;
        let name = self.forum.user.name.as_str();
        let post_id = {
            let mut message = messages.iter();
            let mut post = posts.iter().skip(if topic.author == name { 1 } else { 0 });
            let post_id = match (post.next(), message.next()) {
                (Some(post), Some(message)) => {
                    post.edit(message)?;
                    Some(post.id)
                }
                (None, Some(message)) => topic.reply(message)?,
                _ => unreachable!(),
            };
            loop {
                match (post.next(), message.next()) {
                    (Some(post), Some(message)) => post.edit(message)?,
                    (Some(post), None) => post.edit("резерв")?,
                    (None, Some(message)) => topic.reply(message).map(|_| ())?,
                    (None, None) => break,
                }
            }
            post_id
        };
        if topic.author == name {
            self.send_list_header(list.forum, topic, &posts[0])?;
        }
        Ok(post_id)
    }

    pub fn send_all_list(&self) -> Result<Vec<(Option<usize>, String, usize, f64)>> {
        let keeper_working_forum = self.forum.get_keepers_working_forum();
        let mut topics = self.db.get_topic_with_subforum_list(
            &keeper_working_forum,
            &self.list.iter().map(|r| r.forum).collect::<Vec<usize>>(),
        )?;
        let mut vec = Vec::with_capacity(topics.len());
        for list in &self.list {
            if let Some((id, author, title)) = topics.remove(&list.forum) {
                let topic = keeper_working_forum.get_topic(id, author, title);
                vec.push((
                    self.send_list(list, &topic)?,
                    topic.title,
                    list.id.len(),
                    list.size,
                ));
            }
        }
        Ok(vec)
    }

    pub fn send_all(&self) -> Result<()> {
        let mut vec = self.send_all_list()?;
        let (count, size) = vec.iter().fold((0, 0f64), |(count, size), (_, _, c, s)| {
            (count + c, size + s)
        });
        let mut message = format!(
            "Актуально на: {}\n\
             Общее количество хранимых раздач: {} шт.\n\
             Общий вес хранимых раздач: {}\n[hr]",
            self.date,
            count,
            List::convert_size(size),
        );
        vec.sort_unstable_by(|a, b| a.1.as_str().cmp(b.1.as_str()));
        for (id, title, count, size) in vec {
            if let Some(id) = id {
                message.push_str(
                    format!(
                        "[url=viewtopic.php?p={}#{0}][u]{}[/u][/url] — {} шт. ({})",
                        id,
                        title.trim_left_matches("[Список] "),
                        count,
                        List::convert_size(size)
                    ).as_str(),
                );
            } else {
                message.push_str(
                    format!(
                        "[u]{}[/u] — {} шт. ({})",
                        title.trim_left_matches("[Список] "),
                        count,
                        List::convert_size(size)
                    ).as_str(),
                );
            }
        }
        debug!("{}", message);
        let keepers_forum = self.forum.get_keepers_forum();
        let summary_report = keepers_forum.get_topic(
            4275633,
            "Tokuchi_Toua",
            "Сводные отчеты работы в группе (публикация)",
        );
        let posts = summary_report.get_user_posts()?;
        if let Some(post) = posts.get(0) {
            post.edit(message.as_str())?;
            if posts.len() > 1 {
                warn!("В теме сводных отчётов должно быть не больше одного сообщения");
            }
        } else {
            summary_report.reply(message.as_str())?;
        }
        Ok(())
    }
}
<|endoftext|>


<commit_before>use regex::{Captures, Regex};
use term::{self, color, Terminal};

/// Finds all matches for the given pattern on the given subject and prints out
/// the subject with matches highlighted.
pub fn print_matches(pattern: &str, subject: &str) {
    // Attempt to compile the given regex pattern.
    let regex = match Regex::new(&pattern) {
        Ok(result) => { result },
        Err(_) => {
            println!("Invalid regular expression pattern '{}'.", pattern);
            return;
        }
    };

    // Loop over each match in the subject and pretty-print the match as well as
    // any trailing, non-matching text preceding the match.
    let mut last_index = 0;
    for captures in regex.captures_iter(subject) {
        let positions = captures.pos(0).unwrap();

        print!("{}", &subject[last_index .. positions.0]);
        print_captures(captures);
        last_index = positions.1;
    }

    // Print trailing unmatched text if there is any.
    if last_index < subject.len() {
        print!("{}", &subject[last_index ..]);
    }
}

/// Prints out a capture group using color formatting.
fn print_captures(captures: Captures) {
    let mut terminal = term::stdout().unwrap();
    let color_cycle = [color::BLUE, color::GREEN, color::MAGENTA, color::YELLOW];
    let mut color_index = 1;

    // Get the string of the entire match and the relative offset in the subject.
    let string = captures.at(0).unwrap();
    let offsets = captures.pos(0).unwrap();

    // To highlight sub-matches, divide the match string into a series of
    // resizeable regions that store their range and their color. As we loop
    // over each capture, find the parent region the current capture group fits
    // into, and split the region in two, with a new region for the current
    // capture between it. Worst-case time is O(3n^2), where n is the number of
    // subgroups in the regular expression.
    let mut regions: Vec<(usize, usize, u16)> = Vec::new();
    regions.push((offsets.0, offsets.1, color::BLUE));

    for i in 1..captures.len() {
        let pos = captures.pos(i).unwrap();

        for j in 0..regions.len() {
            if pos.0 >= regions[j].0 && pos.1 <= regions[j].1 {
                // Define two new regions that overlap the old one.
                let middle = (pos.0, pos.1, color_cycle[color_index]);
                let right = (pos.1, regions[j].1, regions[j].2);
                // Shrink the old region to be the leftmost one.
                regions[j].1 = pos.0;

                // Insert the new regions.
                regions.insert(j + 1, middle);
                regions.insert(j + 2, right);

                // Choose next color next time.
                color_index = (color_index + 1) % 4;
                break;
            }
        }
    }

    // Now print out the prepared regions; this part is pretty easy, since the
    // region parser already did all the work.
    for i in 0..regions.len() {
        terminal.bg(regions[i].2).unwrap();
        print!("{}", &string[regions[i].0 - offsets.0 .. regions[i].1 - offsets.0]);
    }

    // Reset coloring to normal.
    terminal.reset().unwrap();
}
<commit_msg>Add newline after output<commit_after>use regex::{Captures, Regex};
use term::{self, color, Terminal};

/// Finds all matches for the given pattern on the given subject and prints out
/// the subject with matches highlighted.
pub fn print_matches(pattern: &str, subject: &str) {
    // Attempt to compile the given regex pattern.
    let regex = match Regex::new(&pattern) {
        Ok(result) => { result },
        Err(_) => {
            println!("Invalid regular expression pattern '{}'.", pattern);
            return;
        }
    };

    // Loop over each match in the subject and pretty-print the match as well as
    // any trailing, non-matching text preceding the match.
    let mut last_index = 0;
    for captures in regex.captures_iter(subject) {
        let positions = captures.pos(0).unwrap();

        print!("{}", &subject[last_index .. positions.0]);
        print_captures(captures);
        last_index = positions.1;
    }

    // Print trailing unmatched text if there is any.
    if last_index < subject.len() {
        print!("{}", &subject[last_index ..]);
    }
    println!("");
}

/// Prints out a capture group using color formatting.
fn print_captures(captures: Captures) {
    let mut terminal = term::stdout().unwrap();
    let color_cycle = [color::BLUE, color::GREEN, color::MAGENTA, color::YELLOW];
    let mut color_index = 1;

    // Get the string of the entire match and the relative offset in the subject.
    let string = captures.at(0).unwrap();
    let offsets = captures.pos(0).unwrap();

    // To highlight sub-matches, divide the match string into a series of
    // resizeable regions that store their range and their color. As we loop
    // over each capture, find the parent region the current capture group fits
    // into, and split the region in two, with a new region for the current
    // capture between it. Worst-case time is O(3n^2), where n is the number of
    // subgroups in the regular expression.
    let mut regions: Vec<(usize, usize, u16)> = Vec::new();
    regions.push((offsets.0, offsets.1, color::BLUE));

    for i in 1..captures.len() {
        let pos = captures.pos(i).unwrap();

        for j in 0..regions.len() {
            if pos.0 >= regions[j].0 && pos.1 <= regions[j].1 {
                // Define two new regions that overlap the old one.
                let middle = (pos.0, pos.1, color_cycle[color_index]);
                let right = (pos.1, regions[j].1, regions[j].2);
                // Shrink the old region to be the leftmost one.
                regions[j].1 = pos.0;

                // Insert the new regions.
                regions.insert(j + 1, middle);
                regions.insert(j + 2, right);

                // Choose next color next time.
                color_index = (color_index + 1) % 4;
                break;
            }
        }
    }

    // Now print out the prepared regions; this part is pretty easy, since the
    // region parser already did all the work.
    for i in 0..regions.len() {
        terminal.bg(regions[i].2).unwrap();
        print!("{}", &string[regions[i].0 - offsets.0 .. regions[i].1 - offsets.0]);
    }

    // Reset coloring to normal.
    terminal.reset().unwrap();
}
<|endoftext|>


<commit_before>//! Useful stuff for rotating points

use traits::has_values::HasValues;
use point::Point;

/// Rotate the point a specified number of times
///
/// Positive rotations are clockwise.
///
/// # Example
///
/// ```
/// use hex_math::{Point, rotate};
///
/// let point: Point = Point::new(1, 2, 5);
///
/// assert_eq!(rotate(&point, 1), Point::new(-2, 3, 5));
/// assert_eq!(rotate(&point, 2), Point::new(-3, 1, 5));
/// assert_eq!(rotate(&point, 3), Point::new(-1, -2, 5));
/// assert_eq!(rotate(&point, 4), Point::new(2, -3, 5));
/// assert_eq!(rotate(&point, 5), Point::new(3, -1, 5));
/// assert_eq!(rotate(&point, 6), point);
/// assert_eq!(rotate(&point, -1), Point::new(3, -1, 5));
/// assert_eq!(rotate(&point, -2), Point::new(2, -3, 5));
/// assert_eq!(rotate(&point, -3), Point::new(-1, -2, 5));
/// assert_eq!(rotate(&point, -4), Point::new(-3, 1, 5));
/// assert_eq!(rotate(&point, -5), Point::new(-2, 3, 5));
/// assert_eq!(rotate(&point, -6), point);
/// assert_eq!(rotate(&point, -12), point);
/// ```
pub fn rotate(point: &Point, mut times: i32) -> Point {

  let (q, r, s, t) = point.values_cube();

  times %= 6;

  if times < 0 {
    times += 6;
  }

  return match times {
    1 => Point::new(-r, -s, t),
    2 => Point::new(s, q, t),
    3 => Point::new(-q, -r, t),
    4 => Point::new(r, s, t),
    5 => Point::new(-s, -q, t),
    _ => point.clone(),
  }

}

<commit_msg>Closes #7: generic rotate<commit_after>//! Useful stuff for rotating points

use traits::has_values::HasValues;
use point::Point;

/// Rotate the point a specified number of times
///
/// Positive rotations are clockwise.
///
/// # Example
///
/// ```
/// use hex_math::{Point, rotate};
///
/// let point: Point = Point::new(1, 2, 5);
///
/// assert_eq!(rotate(&point, 1), Point::new(-2, 3, 5));
/// assert_eq!(rotate(&point, 2), Point::new(-3, 1, 5));
/// assert_eq!(rotate(&point, 3), Point::new(-1, -2, 5));
/// assert_eq!(rotate(&point, 4), Point::new(2, -3, 5));
/// assert_eq!(rotate(&point, 5), Point::new(3, -1, 5));
/// assert_eq!(rotate(&point, 6), point);
/// assert_eq!(rotate(&point, -1), Point::new(3, -1, 5));
/// assert_eq!(rotate(&point, -2), Point::new(2, -3, 5));
/// assert_eq!(rotate(&point, -3), Point::new(-1, -2, 5));
/// assert_eq!(rotate(&point, -4), Point::new(-3, 1, 5));
/// assert_eq!(rotate(&point, -5), Point::new(-2, 3, 5));
/// assert_eq!(rotate(&point, -6), point);
/// assert_eq!(rotate(&point, -12), point);
/// ```
pub fn rotate<T: HasValues>(point: &T, mut times: i32) -> Point {

  let (q, r, s, t) = point.values_cube();

  times %= 6;

  if times < 0 {
    times += 6;
  }

  return match times {
    1 => Point::new(-r, -s, t),
    2 => Point::new(s, q, t),
    3 => Point::new(-q, -r, t),
    4 => Point::new(r, s, t),
    5 => Point::new(-s, -q, t),
    _ => point.to_point(),
  }

}

<|endoftext|>


<commit_before>// Copyright 2014 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! A type-checked scaling factor between units.

use num::One;

use num_traits::NumCast;
#[cfg(feature = "serde")]
use serde;
use core::fmt;
use core::ops::{Add, Div, Mul, Neg, Sub};
use core::marker::PhantomData;
use {Point2D, Rect, Size2D, Vector2D};

/// A scaling factor between two different units of measurement.
///
/// This is effectively a type-safe float, intended to be used in combination with other types like
/// `length::Length` to enforce conversion between systems of measurement at compile time.
///
/// `Src` and `Dst` represent the units before and after multiplying a value by a `Scale`. They
/// may be types without values, such as empty enums.  For example:
///
/// ```rust
/// use euclid::Scale;
/// use euclid::Length;
/// enum Mm {};
/// enum Inch {};
///
/// let mm_per_inch: Scale<f32, Inch, Mm> = Scale::new(25.4);
///
/// let one_foot: Length<f32, Inch> = Length::new(12.0);
/// let one_foot_in_mm: Length<f32, Mm> = one_foot * mm_per_inch;
/// ```
#[repr(C)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(bound(serialize = "T: serde::Serialize", deserialize = "T: serde::Deserialize<'de>")))]
pub struct Scale<T, Src, Dst>(pub T, #[doc(hidden)] pub PhantomData<(Src, Dst)>);

impl<T, Src, Dst> Scale<T, Src, Dst> {
    #[inline]
    pub const fn new(x: T) -> Self {
        Scale(x, PhantomData)
    }

    /// Returns the given point transformed by this scale.
    ///
    /// # Example
    ///
    /// ```rust
    /// use euclid::{Scale, point2};
    /// enum Mm {};
    /// enum Cm {};
    ///
    /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
    ///
    /// assert_eq!(to_mm.transform_point(point2(42, -42)), point2(420, -420));
    /// ```
    #[inline]
    pub fn transform_point(&self, point: Point2D<T, Src>) -> Point2D<T::Output, Dst>
        where T: Clone + Mul
    {
        Point2D::new(point.x * self.get(), point.y * self.get())
    }

    /// Returns the given vector transformed by this scale.
    ///
    /// # Example
    ///
    /// ```rust
    /// use euclid::{Scale, vec2};
    /// enum Mm {};
    /// enum Cm {};
    ///
    /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
    ///
    /// assert_eq!(to_mm.transform_vector(vec2(42, -42)), vec2(420, -420));
    /// ```
    #[inline]
    pub fn transform_vector(&self, vec: Vector2D<T, Src>) -> Vector2D<T::Output, Dst>
        where T: Clone + Mul
    {
        Vector2D::new(vec.x * self.get(), vec.y * self.get())
    }

    /// Returns the given vector transformed by this scale.
    ///
    /// # Example
    ///
    /// ```rust
    /// use euclid::{Scale, size2};
    /// enum Mm {};
    /// enum Cm {};
    ///
    /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
    ///
    /// assert_eq!(to_mm.transform_size(size2(42, -42)), size2(420, -420));
    /// ```
    #[inline]
    pub fn transform_size(&self, size: Size2D<T, Src>) -> Size2D<T::Output, Dst>
        where T: Clone + Mul
    {
        Size2D::new(size.width * self.get(), size.height * self.get())
    }

    /// Returns the given rect transformed by this scale.
    ///
    /// # Example
    ///
    /// ```rust
    /// use euclid::{Scale, rect};
    /// enum Mm {};
    /// enum Cm {};
    ///
    /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
    ///
    /// assert_eq!(to_mm.transform_rect(&rect(1, 2, 42, -42)), rect(10, 20, 420, -420));
    /// ```
    #[inline]
    pub fn transform_rect(&self, rect: &Rect<T, Src>) -> Rect<T::Output, Dst>
        where T: Copy + Mul
    {
        Rect::new(
            self.transform_point(rect.origin),
            self.transform_size(rect.size),
        )
    }

    /// Returns the inverse of this scale.
    #[inline]
    pub fn inverse(&self) -> Scale<T::Output, Dst, Src>
        where T: Clone + Neg
    {
        Scale::new(-self.get())
    }

    /// Returns `true` if this scale has no effect.
    ///
    /// # Example
    ///
    /// ```rust
    /// use euclid::Scale;
    /// use euclid::num::One;
    /// enum Mm {};
    /// enum Cm {};
    ///
    /// let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
    /// let mm_per_mm: Scale<f32, Mm, Mm> = Scale::new(1.0);
    ///
    /// assert_eq!(cm_per_mm.is_identity(), false);
    /// assert_eq!(mm_per_mm.is_identity(), true);
    /// ```
    #[inline]
    pub fn is_identity(&self) -> bool
        where T: PartialEq + One
    {
        self.0 == T::one()
    }
}

impl<T: Clone, Src, Dst> Scale<T, Src, Dst> {
    #[inline]
    pub fn get(&self) -> T {
        self.0.clone()
    }

    /// The inverse Scale (1.0 / self).
    ///
    /// # Example
    ///
    /// ```rust
    /// use euclid::Scale;
    /// enum Mm {};
    /// enum Cm {};
    ///
    /// let cm_per_mm: Scale<f32, Cm, Mm> = Scale::new(0.1);
    ///
    /// assert_eq!(cm_per_mm.inv(), Scale::new(10.0));
    /// ```
    pub fn inv(&self) -> Scale<T::Output, Dst, Src>
    where
        T: One + Div
    {
        let one: T = One::one();
        Scale::new(one / self.0.clone())
    }
}

impl<T: NumCast + Clone, Src, Dst> Scale<T, Src, Dst> {
    /// Cast from one numeric representation to another, preserving the units.
    ///
    /// # Panics
    ///
    /// If the source value cannot be represented by the target type `NewT`, then
    /// method panics. Use `try_cast` if that must be case.
    ///
    /// # Example
    ///
    /// ```rust
    /// use euclid::Scale;
    /// enum Mm {};
    /// enum Cm {};
    ///
    /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
    ///
    /// assert_eq!(to_mm.cast::<f32>(), Scale::new(10.0));
    /// ```
    /// That conversion will panic, because `i32` not enough to store such big numbers:
    /// ```rust,should_panic
    /// use euclid::Scale;
    /// enum Mm {};// millimeter = 10^-2 meters
    /// enum Em {};// exameter   = 10^18 meters
    ///
    /// // Panics
    /// let to_em: Scale<i32, Mm, Em> = Scale::new(10e20).cast();
    /// ```
    #[inline]
    pub fn cast<NewT: NumCast>(&self) -> Scale<NewT, Src, Dst> {
        self.try_cast().unwrap()
    }

    /// Fallible cast from one numeric representation to another, preserving the units.
    /// If the source value cannot be represented by the target type `NewT`, then `None`
    /// is returned.
    ///
    /// # Example
    ///
    /// ```rust
    /// use euclid::Scale;
    /// enum Mm {};
    /// enum Cm {};
    /// enum Em {};// Exameter = 10^18 meters
    ///
    /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
    /// let to_em: Scale<f32, Mm, Em> = Scale::new(10e20);
    ///
    /// assert_eq!(to_mm.try_cast::<f32>(), Some(Scale::new(10.0)));
    /// // Integer to small to store that number
    /// assert_eq!(to_em.try_cast::<i32>(), None);
    /// ```
    pub fn try_cast<NewT: NumCast>(&self) -> Option<Scale<NewT, Src, Dst>> {
        NumCast::from(self.get()).map(Scale::new)
    }
}

impl<Src, Dst> Scale<f32, Src, Dst> {
    /// Identity scaling, could be used to safely transit from one space to another.
    pub const ONE: Self = Scale(1.0, PhantomData);
}

// scale0 * scale1
impl<T: Mul<T, Output = T>, A, B, C> Mul<Scale<T, B, C>> for Scale<T, A, B> {
    type Output = Scale<T, A, C>;
    #[inline]
    fn mul(self, other: Scale<T, B, C>) -> Scale<T, A, C> {
        Scale::new(self.0 * other.0)
    }
}

// scale0 + scale1
impl<T: Add<T, Output = T>, Src, Dst> Add for Scale<T, Src, Dst> {
    type Output = Scale<T, Src, Dst>;
    #[inline]
    fn add(self, other: Scale<T, Src, Dst>) -> Scale<T, Src, Dst> {
        Scale::new(self.0 + other.0)
    }
}

// scale0 - scale1
impl<T: Sub<T, Output = T>, Src, Dst> Sub for Scale<T, Src, Dst> {
    type Output = Scale<T, Src, Dst>;
    #[inline]
    fn sub(self, other: Scale<T, Src, Dst>) -> Scale<T, Src, Dst> {
        Scale::new(self.0 - other.0)
    }
}

// FIXME: Switch to `derive(PartialEq, Clone)` after this Rust issue is fixed:
// https://github.com/rust-lang/rust/issues/26925

impl<T: PartialEq, Src, Dst> PartialEq for Scale<T, Src, Dst> {
    fn eq(&self, other: &Scale<T, Src, Dst>) -> bool {
        self.0 == other.0
    }
}

impl<T: Clone, Src, Dst> Clone for Scale<T, Src, Dst> {
    fn clone(&self) -> Scale<T, Src, Dst> {
        Scale::new(self.get())
    }
}

impl<T: Copy, Src, Dst> Copy for Scale<T, Src, Dst> {}

impl<T: fmt::Debug, Src, Dst> fmt::Debug for Scale<T, Src, Dst> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl<T: fmt::Display, Src, Dst> fmt::Display for Scale<T, Src, Dst> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}

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

    enum Inch {}
    enum Cm {}
    enum Mm {}

    #[test]
    fn test_scale() {
        let mm_per_inch: Scale<f32, Inch, Mm> = Scale::new(25.4);
        let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);

        let mm_per_cm: Scale<f32, Cm, Mm> = cm_per_mm.inv();
        assert_eq!(mm_per_cm.get(), 10.0);

        let cm_per_inch: Scale<f32, Inch, Cm> = mm_per_inch * cm_per_mm;
        assert_eq!(cm_per_inch, Scale::new(2.54));

        let a: Scale<isize, Inch, Inch> = Scale::new(2);
        let b: Scale<isize, Inch, Inch> = Scale::new(3);
        assert!(a != b);
        assert_eq!(a, a.clone());
        assert_eq!(a.clone() + b.clone(), Scale::new(5));
        assert_eq!(a - b, Scale::new(-1));
    }
}
<commit_msg>Scale: do not restrict operators only to the same type, as wrapped<commit_after>// Copyright 2014 The Servo Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! A type-checked scaling factor between units.

use num::One;

use num_traits::NumCast;
#[cfg(feature = "serde")]
use serde;
use core::fmt;
use core::ops::{Add, Div, Mul, Neg, Sub};
use core::marker::PhantomData;
use {Point2D, Rect, Size2D, Vector2D};

/// A scaling factor between two different units of measurement.
///
/// This is effectively a type-safe float, intended to be used in combination with other types like
/// `length::Length` to enforce conversion between systems of measurement at compile time.
///
/// `Src` and `Dst` represent the units before and after multiplying a value by a `Scale`. They
/// may be types without values, such as empty enums.  For example:
///
/// ```rust
/// use euclid::Scale;
/// use euclid::Length;
/// enum Mm {};
/// enum Inch {};
///
/// let mm_per_inch: Scale<f32, Inch, Mm> = Scale::new(25.4);
///
/// let one_foot: Length<f32, Inch> = Length::new(12.0);
/// let one_foot_in_mm: Length<f32, Mm> = one_foot * mm_per_inch;
/// ```
#[repr(C)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(bound(serialize = "T: serde::Serialize", deserialize = "T: serde::Deserialize<'de>")))]
pub struct Scale<T, Src, Dst>(pub T, #[doc(hidden)] pub PhantomData<(Src, Dst)>);

impl<T, Src, Dst> Scale<T, Src, Dst> {
    #[inline]
    pub const fn new(x: T) -> Self {
        Scale(x, PhantomData)
    }

    /// Returns the given point transformed by this scale.
    ///
    /// # Example
    ///
    /// ```rust
    /// use euclid::{Scale, point2};
    /// enum Mm {};
    /// enum Cm {};
    ///
    /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
    ///
    /// assert_eq!(to_mm.transform_point(point2(42, -42)), point2(420, -420));
    /// ```
    #[inline]
    pub fn transform_point(&self, point: Point2D<T, Src>) -> Point2D<T::Output, Dst>
        where T: Clone + Mul
    {
        Point2D::new(point.x * self.get(), point.y * self.get())
    }

    /// Returns the given vector transformed by this scale.
    ///
    /// # Example
    ///
    /// ```rust
    /// use euclid::{Scale, vec2};
    /// enum Mm {};
    /// enum Cm {};
    ///
    /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
    ///
    /// assert_eq!(to_mm.transform_vector(vec2(42, -42)), vec2(420, -420));
    /// ```
    #[inline]
    pub fn transform_vector(&self, vec: Vector2D<T, Src>) -> Vector2D<T::Output, Dst>
        where T: Clone + Mul
    {
        Vector2D::new(vec.x * self.get(), vec.y * self.get())
    }

    /// Returns the given vector transformed by this scale.
    ///
    /// # Example
    ///
    /// ```rust
    /// use euclid::{Scale, size2};
    /// enum Mm {};
    /// enum Cm {};
    ///
    /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
    ///
    /// assert_eq!(to_mm.transform_size(size2(42, -42)), size2(420, -420));
    /// ```
    #[inline]
    pub fn transform_size(&self, size: Size2D<T, Src>) -> Size2D<T::Output, Dst>
        where T: Clone + Mul
    {
        Size2D::new(size.width * self.get(), size.height * self.get())
    }

    /// Returns the given rect transformed by this scale.
    ///
    /// # Example
    ///
    /// ```rust
    /// use euclid::{Scale, rect};
    /// enum Mm {};
    /// enum Cm {};
    ///
    /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
    ///
    /// assert_eq!(to_mm.transform_rect(&rect(1, 2, 42, -42)), rect(10, 20, 420, -420));
    /// ```
    #[inline]
    pub fn transform_rect(&self, rect: &Rect<T, Src>) -> Rect<T::Output, Dst>
        where T: Copy + Mul
    {
        Rect::new(
            self.transform_point(rect.origin),
            self.transform_size(rect.size),
        )
    }

    /// Returns the inverse of this scale.
    #[inline]
    pub fn inverse(&self) -> Scale<T::Output, Dst, Src>
        where T: Clone + Neg
    {
        Scale::new(-self.get())
    }

    /// Returns `true` if this scale has no effect.
    ///
    /// # Example
    ///
    /// ```rust
    /// use euclid::Scale;
    /// use euclid::num::One;
    /// enum Mm {};
    /// enum Cm {};
    ///
    /// let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);
    /// let mm_per_mm: Scale<f32, Mm, Mm> = Scale::new(1.0);
    ///
    /// assert_eq!(cm_per_mm.is_identity(), false);
    /// assert_eq!(mm_per_mm.is_identity(), true);
    /// ```
    #[inline]
    pub fn is_identity(&self) -> bool
        where T: PartialEq + One
    {
        self.0 == T::one()
    }
}

impl<T: Clone, Src, Dst> Scale<T, Src, Dst> {
    #[inline]
    pub fn get(&self) -> T {
        self.0.clone()
    }

    /// The inverse Scale (1.0 / self).
    ///
    /// # Example
    ///
    /// ```rust
    /// use euclid::Scale;
    /// enum Mm {};
    /// enum Cm {};
    ///
    /// let cm_per_mm: Scale<f32, Cm, Mm> = Scale::new(0.1);
    ///
    /// assert_eq!(cm_per_mm.inv(), Scale::new(10.0));
    /// ```
    pub fn inv(&self) -> Scale<T::Output, Dst, Src>
    where
        T: One + Div
    {
        let one: T = One::one();
        Scale::new(one / self.0.clone())
    }
}

impl<T: NumCast + Clone, Src, Dst> Scale<T, Src, Dst> {
    /// Cast from one numeric representation to another, preserving the units.
    ///
    /// # Panics
    ///
    /// If the source value cannot be represented by the target type `NewT`, then
    /// method panics. Use `try_cast` if that must be case.
    ///
    /// # Example
    ///
    /// ```rust
    /// use euclid::Scale;
    /// enum Mm {};
    /// enum Cm {};
    ///
    /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
    ///
    /// assert_eq!(to_mm.cast::<f32>(), Scale::new(10.0));
    /// ```
    /// That conversion will panic, because `i32` not enough to store such big numbers:
    /// ```rust,should_panic
    /// use euclid::Scale;
    /// enum Mm {};// millimeter = 10^-2 meters
    /// enum Em {};// exameter   = 10^18 meters
    ///
    /// // Panics
    /// let to_em: Scale<i32, Mm, Em> = Scale::new(10e20).cast();
    /// ```
    #[inline]
    pub fn cast<NewT: NumCast>(&self) -> Scale<NewT, Src, Dst> {
        self.try_cast().unwrap()
    }

    /// Fallible cast from one numeric representation to another, preserving the units.
    /// If the source value cannot be represented by the target type `NewT`, then `None`
    /// is returned.
    ///
    /// # Example
    ///
    /// ```rust
    /// use euclid::Scale;
    /// enum Mm {};
    /// enum Cm {};
    /// enum Em {};// Exameter = 10^18 meters
    ///
    /// let to_mm: Scale<i32, Cm, Mm> = Scale::new(10);
    /// let to_em: Scale<f32, Mm, Em> = Scale::new(10e20);
    ///
    /// assert_eq!(to_mm.try_cast::<f32>(), Some(Scale::new(10.0)));
    /// // Integer to small to store that number
    /// assert_eq!(to_em.try_cast::<i32>(), None);
    /// ```
    pub fn try_cast<NewT: NumCast>(&self) -> Option<Scale<NewT, Src, Dst>> {
        NumCast::from(self.get()).map(Scale::new)
    }
}

impl<Src, Dst> Scale<f32, Src, Dst> {
    /// Identity scaling, could be used to safely transit from one space to another.
    pub const ONE: Self = Scale(1.0, PhantomData);
}

// scale0 * scale1
impl<T: Mul, A, B, C> Mul<Scale<T, B, C>> for Scale<T, A, B> {
    type Output = Scale<T::Output, A, C>;

    #[inline]
    fn mul(self, other: Scale<T, B, C>) -> Self::Output {
        Scale::new(self.0 * other.0)
    }
}

// scale0 + scale1
impl<T: Add, Src, Dst> Add for Scale<T, Src, Dst> {
    type Output = Scale<T::Output, Src, Dst>;

    #[inline]
    fn add(self, other: Scale<T, Src, Dst>) -> Self::Output {
        Scale::new(self.0 + other.0)
    }
}

// scale0 - scale1
impl<T: Sub, Src, Dst> Sub for Scale<T, Src, Dst> {
    type Output = Scale<T::Output, Src, Dst>;

    #[inline]
    fn sub(self, other: Scale<T, Src, Dst>) -> Self::Output {
        Scale::new(self.0 - other.0)
    }
}

// FIXME: Switch to `derive(PartialEq, Clone)` after this Rust issue is fixed:
// https://github.com/rust-lang/rust/issues/26925

impl<T: PartialEq, Src, Dst> PartialEq for Scale<T, Src, Dst> {
    fn eq(&self, other: &Scale<T, Src, Dst>) -> bool {
        self.0 == other.0
    }
}

impl<T: Clone, Src, Dst> Clone for Scale<T, Src, Dst> {
    fn clone(&self) -> Scale<T, Src, Dst> {
        Scale::new(self.get())
    }
}

impl<T: Copy, Src, Dst> Copy for Scale<T, Src, Dst> {}

impl<T: fmt::Debug, Src, Dst> fmt::Debug for Scale<T, Src, Dst> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl<T: fmt::Display, Src, Dst> fmt::Display for Scale<T, Src, Dst> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0.fmt(f)
    }
}

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

    enum Inch {}
    enum Cm {}
    enum Mm {}

    #[test]
    fn test_scale() {
        let mm_per_inch: Scale<f32, Inch, Mm> = Scale::new(25.4);
        let cm_per_mm: Scale<f32, Mm, Cm> = Scale::new(0.1);

        let mm_per_cm: Scale<f32, Cm, Mm> = cm_per_mm.inv();
        assert_eq!(mm_per_cm.get(), 10.0);

        let cm_per_inch: Scale<f32, Inch, Cm> = mm_per_inch * cm_per_mm;
        assert_eq!(cm_per_inch, Scale::new(2.54));

        let a: Scale<isize, Inch, Inch> = Scale::new(2);
        let b: Scale<isize, Inch, Inch> = Scale::new(3);
        assert_ne!(a, b);
        assert_eq!(a, a.clone());
        assert_eq!(a.clone() + b.clone(), Scale::new(5));
        assert_eq!(a - b, Scale::new(-1));
    }
}
<|endoftext|>


<commit_before>
use libc;
use std::time::duration::Duration;
use std::fmt::{Show, Error, Formatter};
use time::Timespec;

use common::{FromConn, MpdResult};
use connection::{mpd_connection, MpdConnection};
use tags::MpdTagType;

#[repr(C)] pub struct mpd_song;

#[link(name = "mpdclient")]
extern "C" {
    fn mpd_song_dup(song: *const mpd_song) -> *mut mpd_song;
    fn mpd_song_free(song: *mut mpd_song);
    fn mpd_song_get_uri(song: *const mpd_song) -> *const u8;
    fn mpd_song_get_tag(song: *const mpd_song, typ: MpdTagType, idx: libc::c_uint) -> *const u8;
    fn mpd_song_get_duration(song: *const mpd_song) -> libc::c_uint;
    fn mpd_song_get_start(song: *const mpd_song) -> libc::c_uint;
    fn mpd_song_get_end(song: *const mpd_song) -> libc::c_uint;
    fn mpd_song_get_last_modified(song: *const mpd_song) -> libc::time_t;
    fn mpd_song_get_id(song: *const mpd_song) -> libc::c_uint;
    fn mpd_song_get_pos(song: *const mpd_song) -> libc::c_uint;
    fn mpd_song_set_pos(song: *mut mpd_song, pos: libc::c_uint);
    fn mpd_song_get_prio(song: *const mpd_song) -> libc::c_uint;
    fn mpd_recv_song(connection: *mut mpd_connection) -> *mut mpd_song;
}

pub struct MpdSongs<'a> {
    pub conn: &'a MpdConnection
}

impl<'a> Iterator<MpdResult<MpdSong>> for MpdSongs<'a> {
    fn next(&mut self) -> Option<MpdResult<MpdSong>> {
        match FromConn::from_conn(self.conn.conn) {
            Some(song) => Some(Ok(song)),
            None => match FromConn::from_conn(self.conn.conn) {
                None => None,
                Some(e) => Some(Err(e))
            }
        }
    }
}

pub struct MpdSong {
    pub song: *mut mpd_song
}

impl Show for MpdSong {
    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
        try!(f.write(b"MpdSong { "));
        try!(f.write(b"uri: "));
        try!(self.uri().fmt(f));
        try!(f.write(b" }"));
        Ok(())
    }
}

impl MpdSong {
    pub fn uri(&self) -> String { unsafe { String::from_raw_buf(mpd_song_get_uri(self.song as *const _)) } }
    pub fn tags(&self, kind: MpdTagType, index: u32) -> Option<String> {
        let tag = unsafe { mpd_song_get_tag(self.song as *const _, kind, index) };
        if tag.is_null() {
            None
        } else {
            Some(unsafe { String::from_raw_buf(tag) })
        }
    }
    pub fn duration(&self) -> Duration { Duration::seconds(unsafe { mpd_song_get_duration(self.song as *const _) } as i64) }
    pub fn id(&self) -> u32 { unsafe { mpd_song_get_id(self.song as *const _) } }
    pub fn prio(&self) -> u32 { unsafe { mpd_song_get_prio(self.song as *const _) } }
    pub fn start(&self) -> Duration { Duration::seconds(unsafe { mpd_song_get_start(self.song as *const _) } as i64) }
    pub fn end(&self) -> Option<Duration> {
        match unsafe { mpd_song_get_end(self.song as *const _) } {
            0 => None,
            s @ _ => Some(Duration::seconds(s as i64))
        }
    }
    pub fn slice(&self) -> (Duration, Option<Duration>) { (self.start(), self.end()) }
    pub fn last_mod(&self) -> Timespec { Timespec::new(unsafe { mpd_song_get_last_modified(self.song as *const _) }, 0) }
    pub fn get_pos(&self) -> u32 { unsafe { mpd_song_get_pos(self.song as *const _) } }
    pub fn set_pos(&mut self, pos: u32) { unsafe { mpd_song_set_pos(self.song, pos) } }
}

impl FromConn for MpdSong {
    fn from_conn(connection: *mut mpd_connection) -> Option<MpdSong> {
        let song = unsafe { mpd_recv_song(connection) };
        if song.is_null() {
            None
        } else {
            Some(MpdSong { song: song })
        }
    }
}

impl Drop for MpdSong {
    fn drop(&mut self) {
        unsafe { mpd_song_free(self.song); }
    }
}

impl Clone for MpdSong {
    fn clone(&self) -> MpdSong {
        let song = unsafe { mpd_song_dup(self.song as *const _) };
        if song.is_null() {
            panic!("Out of memory!")
        }

        MpdSong { song: song }
    }
}
<commit_msg>add seek song<commit_after>
use libc;
use std::time::duration::Duration;
use std::fmt::{Show, Error, Formatter};
use time::Timespec;

use common::{FromConn, MpdResult};
use connection::{mpd_connection, MpdConnection};
use tags::MpdTagType;

#[repr(C)] pub struct mpd_song;

#[link(name = "mpdclient")]
extern "C" {
    fn mpd_song_dup(song: *const mpd_song) -> *mut mpd_song;
    fn mpd_song_free(song: *mut mpd_song);
    fn mpd_song_get_uri(song: *const mpd_song) -> *const u8;
    fn mpd_song_get_tag(song: *const mpd_song, typ: MpdTagType, idx: libc::c_uint) -> *const u8;
    fn mpd_song_get_duration(song: *const mpd_song) -> libc::c_uint;
    fn mpd_song_get_start(song: *const mpd_song) -> libc::c_uint;
    fn mpd_song_get_end(song: *const mpd_song) -> libc::c_uint;
    fn mpd_song_get_last_modified(song: *const mpd_song) -> libc::time_t;
    fn mpd_song_get_id(song: *const mpd_song) -> libc::c_uint;
    fn mpd_song_get_pos(song: *const mpd_song) -> libc::c_uint;
    fn mpd_song_set_pos(song: *mut mpd_song, pos: libc::c_uint);
    fn mpd_song_get_prio(song: *const mpd_song) -> libc::c_uint;
    fn mpd_recv_song(connection: *mut mpd_connection) -> *mut mpd_song;

    fn mpd_run_seek_id(connection: *mut mpd_connection, song_id: libc::c_uint, t: libc::c_uint) -> bool;
}

pub struct MpdSongs<'a> {
    pub conn: &'a MpdConnection
}

impl<'a> Iterator<MpdResult<MpdSong>> for MpdSongs<'a> {
    fn next(&mut self) -> Option<MpdResult<MpdSong>> {
        match FromConn::from_conn(self.conn.conn) {
            Some(song) => Some(Ok(song)),
            None => match FromConn::from_conn(self.conn.conn) {
                None => None,
                Some(e) => Some(Err(e))
            }
        }
    }
}

pub struct MpdSong {
    pub song: *mut mpd_song
}

impl Show for MpdSong {
    fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
        try!(f.write(b"MpdSong { "));
        try!(f.write(b"uri: "));
        try!(self.uri().fmt(f));
        try!(f.write(b" }"));
        Ok(())
    }
}

impl MpdSong {
    pub fn uri(&self) -> String { unsafe { String::from_raw_buf(mpd_song_get_uri(self.song as *const _)) } }
    pub fn tags(&self, kind: MpdTagType, index: u32) -> Option<String> {
        let tag = unsafe { mpd_song_get_tag(self.song as *const _, kind, index) };
        if tag.is_null() {
            None
        } else {
            Some(unsafe { String::from_raw_buf(tag) })
        }
    }
    pub fn duration(&self) -> Duration { Duration::seconds(unsafe { mpd_song_get_duration(self.song as *const _) } as i64) }
    pub fn id(&self) -> u32 { unsafe { mpd_song_get_id(self.song as *const _) } }
    pub fn prio(&self) -> u32 { unsafe { mpd_song_get_prio(self.song as *const _) } }
    pub fn start(&self) -> Duration { Duration::seconds(unsafe { mpd_song_get_start(self.song as *const _) } as i64) }
    pub fn end(&self) -> Option<Duration> {
        match unsafe { mpd_song_get_end(self.song as *const _) } {
            0 => None,
            s @ _ => Some(Duration::seconds(s as i64))
        }
    }
    pub fn slice(&self) -> (Duration, Option<Duration>) { (self.start(), self.end()) }
    pub fn last_mod(&self) -> Timespec { Timespec::new(unsafe { mpd_song_get_last_modified(self.song as *const _) }, 0) }
    pub fn get_pos(&self) -> u32 { unsafe { mpd_song_get_pos(self.song as *const _) } }
    pub fn set_pos(&mut self, pos: u32) { unsafe { mpd_song_set_pos(self.song, pos) } }

    pub fn seek(&mut self, conn: &mut MpdConnection, pos: Duration) -> MpdResult<()> {
        if unsafe { mpd_run_seek_id(conn.conn, self.id(), pos.num_seconds() as libc::c_uint) } {
            Ok(())
        } else {
            Err(FromConn::from_conn(conn.conn).unwrap())
        }
    }
}

impl FromConn for MpdSong {
    fn from_conn(connection: *mut mpd_connection) -> Option<MpdSong> {
        let song = unsafe { mpd_recv_song(connection) };
        if song.is_null() {
            None
        } else {
            Some(MpdSong { song: song })
        }
    }
}

impl Drop for MpdSong {
    fn drop(&mut self) {
        unsafe { mpd_song_free(self.song); }
    }
}

impl Clone for MpdSong {
    fn clone(&self) -> MpdSong {
        let song = unsafe { mpd_song_dup(self.song as *const _) };
        if song.is_null() {
            panic!("Out of memory!")
        }

        MpdSong { song: song }
    }
}
<|endoftext|>


<commit_before>use std::ops::{Not, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign};
use wrapping::{WrappingAdd, WrappingSub};

/// A type designating data that will only be used in a constant time manner
pub struct Secret<T: Copy> {
    inner: T
}

impl<T: Copy> Copy for Secret<T> { }
impl<T: Copy> Clone for Secret<T> {
    fn clone(&self) -> Secret<T> {
        *self
    }
}

impl<T: Copy> Secret<T> {
    pub fn new(val: T) -> Secret<T> {
        Secret { inner: val }
    }

    /// Expose the data inside this wrapper. TODO: warning about use and greppability
    pub fn expose(self) -> T {
        self.inner
    }
}

macro_rules! pod_impl {
    { $tr:ident, $method:ident, $t:ty } => {
        impl $tr<$t> for Secret<$t> {
            type Output = Secret<<$t as $tr<$t>>::Output>;
            fn $method(self, rhs: $t) -> Self::Output {
                Secret::new($tr::$method(self.expose(), rhs))
            }
        }

        impl $tr<Secret<$t>> for $t {
            type Output = Secret<<$t as $tr<$t>>::Output>;
            fn $method(self, rhs: Secret<$t>) -> Self::Output {
                Secret::new($tr::$method(self, rhs.expose()))
            }
        }

        impl $tr<Secret<$t>> for Secret<$t> {
            type Output = Secret<<$t as $tr<$t>>::Output>;
            fn $method(self, rhs: Secret<$t>) -> Self::Output {
                Secret::new($tr::$method(self.expose(), rhs.expose()))
            }
        }
    };

    { $tr:ident, $method:ident, $trassign:ident, $methodassign: ident, $t:ty } => {
        pod_impl! { $tr, $method, $t }

        impl $trassign<Secret<$t>> for Secret<$t> {
            fn $methodassign(&mut self, rhs: Secret<$t>) {
                $trassign::$methodassign(&mut self.inner, rhs.expose());
            }
        }

        impl $trassign<$t> for Secret<$t> {
            fn $methodassign(&mut self, rhs: $t) {
                $trassign::$methodassign(&mut self.inner, rhs);
            }
        }
    };
}

macro_rules! pod_impls {
    { $t:ty } => {
        impl Not for Secret<$t> {
            type Output = Secret<<$t as Not>::Output>;
            fn not(self) -> Self::Output {
                Secret::new(!self.expose())
            }
        }

        pod_impl! { BitAnd, bitand, BitAndAssign, bitand_assign, $t }
        pod_impl! { BitOr, bitor, BitOrAssign, bitor_assign, $t }
        pod_impl! { BitXor, bitxor, BitXorAssign, bitxor_assign, $t }
        pod_impl! { WrappingAdd, wrapping_add, $t }
        pod_impl! { WrappingSub, wrapping_sub, $t }
    };
}

pod_impls! { u8 }
pod_impls! { u16 }
pod_impls! { u32 }
pod_impls! { u64 }
pod_impls! { usize }
pod_impls! { i8 }
pod_impls! { i16 }
pod_impls! { i32 }
pod_impls! { i64 }
pod_impls! { isize }
<commit_msg>Add Shl/Shr impls for Secret<commit_after>use std::ops::{Not, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Shl, ShlAssign, Shr, ShrAssign};
use wrapping::{WrappingAdd, WrappingSub};

/// A type designating data that will only be used in a constant time manner
pub struct Secret<T: Copy> {
    inner: T
}

impl<T: Copy> Copy for Secret<T> { }
impl<T: Copy> Clone for Secret<T> {
    fn clone(&self) -> Secret<T> {
        *self
    }
}

impl<T: Copy> Secret<T> {
    pub fn new(val: T) -> Secret<T> {
        Secret { inner: val }
    }

    /// Expose the data inside this wrapper. TODO: warning about use and greppability
    pub fn expose(self) -> T {
        self.inner
    }
}

macro_rules! pod_impl {
    { $tr:ident, $method:ident, $t:ty } => {
        impl $tr<$t> for Secret<$t> {
            type Output = Secret<<$t as $tr<$t>>::Output>;
            fn $method(self, rhs: $t) -> Self::Output {
                Secret::new($tr::$method(self.expose(), rhs))
            }
        }

        impl $tr<Secret<$t>> for $t {
            type Output = Secret<<$t as $tr<$t>>::Output>;
            fn $method(self, rhs: Secret<$t>) -> Self::Output {
                Secret::new($tr::$method(self, rhs.expose()))
            }
        }

        impl $tr<Secret<$t>> for Secret<$t> {
            type Output = Secret<<$t as $tr<$t>>::Output>;
            fn $method(self, rhs: Secret<$t>) -> Self::Output {
                Secret::new($tr::$method(self.expose(), rhs.expose()))
            }
        }
    };

    { $tr:ident, $method:ident, $trassign:ident, $methodassign: ident, $t:ty } => {
        pod_impl! { $tr, $method, $t }

        impl $trassign<Secret<$t>> for Secret<$t> {
            fn $methodassign(&mut self, rhs: Secret<$t>) {
                $trassign::$methodassign(&mut self.inner, rhs.expose());
            }
        }

        impl $trassign<$t> for Secret<$t> {
            fn $methodassign(&mut self, rhs: $t) {
                $trassign::$methodassign(&mut self.inner, rhs);
            }
        }
    };

    { shift, $tr:ident, $method:ident, $trassign:ident, $methodassign: ident, $t:ty } => {
        impl $tr<usize> for Secret<$t> {
            type Output = Secret<<$t as $tr<$t>>::Output>;
            fn $method(self, rhs: usize) -> Self::Output {
                Secret::new($tr::$method(self.expose(), rhs))
            }
        }

        impl $trassign<usize> for Secret<$t> {
            fn $methodassign(&mut self, rhs: usize) {
                $trassign::$methodassign(&mut self.inner, rhs);
            }
        }
    }
}

macro_rules! pod_impls {
    { $t:ty } => {
        impl Not for Secret<$t> {
            type Output = Secret<<$t as Not>::Output>;
            fn not(self) -> Self::Output {
                Secret::new(!self.expose())
            }
        }

        pod_impl! { BitAnd, bitand, BitAndAssign, bitand_assign, $t }
        pod_impl! { BitOr, bitor, BitOrAssign, bitor_assign, $t }
        pod_impl! { BitXor, bitxor, BitXorAssign, bitxor_assign, $t }
        pod_impl! { shift, Shl, shl, ShlAssign, shl_assign, $t }
        pod_impl! { shift, Shr, shr, ShrAssign, shr_assign, $t }
        pod_impl! { WrappingAdd, wrapping_add, $t }
        pod_impl! { WrappingSub, wrapping_sub, $t }
    };
}

pod_impls! { u8 }
pod_impls! { u16 }
pod_impls! { u32 }
pod_impls! { u64 }
pod_impls! { usize }
pod_impls! { i8 }
pod_impls! { i16 }
pod_impls! { i32 }
pod_impls! { i64 }
pod_impls! { isize }
<|endoftext|>