<commit_before>use std::str;
use nom;
use nom::IResult;

/// Try to parse a version
///
/// If there's an error, then you just get (). for now.
pub fn try_parse(i: &[u8]) -> Result<super::Version, ()> {
    match version(i) {
        IResult::Done(_, d) => Ok(d),
        _ => Err(()),
    }
}

/// parse a u32
fn number(i: &[u8]) -> IResult<&[u8], u32> {
    map_res!(i,
             nom::digit,
             |d| str::FromStr::from_str(str::from_utf8(d).unwrap()))
}

/// parse a word
fn word(i: &[u8]) -> IResult<&[u8], String> {
    map_res!(i,
             nom::alphanumeric,
             |d: &[u8]| String::from_utf8(d.to_vec()))
}

/// parse a . and then a u32
named!(dot_number<&[u8], u32>, chain!(
        tag!(".") ~
        i: number, || { i }
));

named!(pre<&[u8], Option<String> >, opt!(complete!(chain!(
        tag!("-") ~
        w: word, || { w }
))));

/// parse a version
///
/// A version is currently:
///
/// - a major version number
/// - optionally followed by a dot and a minor version number
/// - optionally followed by a dot and a patch version number
///
/// If some of the versions aren't present, gives a zero.
named!(version<&[u8], super::Version>, chain!(
        major: number ~
        rest: opt!(complete!(chain!(
                minor: dot_number ~
                patch_pre: opt!(complete!(chain!(
                    patch: dot_number ~
                    pre: pre,
                    || { (patch, pre) }
                ))),
                || {
                    let (patch, pre) = match patch_pre {
                        Some((patch, ref pre)) => (patch, pre.clone()),
                        None => (0, None),
                    };

                    (minor, patch, pre)
                }
        ))),
        || {
            let (minor, patch, pre) = match rest {
                Some((minor, patch, ref pre)) => (minor, patch, pre.clone()),
                None => (0, 0, None),
            };
            super::Version { major: major, minor: minor, patch: patch, pre: pre }
        }
));

#[cfg(test)]
mod tests {
    use super::number;
    use super::dot_number;
    use super::version;
    use Version;

    fn done<T>(x: T) -> ::nom::IResult<&'static [u8], T> {
        ::nom::IResult::Done(&[][..], x)
    }

    #[test]
    fn parse_number() {
        let v = "10".as_bytes();

        assert_eq!(number(v), done(10));
    }

    #[test]
    fn parse_dot_number() {
        let v = ".10".as_bytes();

        assert_eq!(dot_number(v), done(10));
    }

    #[test]
    fn parse_major() {
        let v1 = "10".as_bytes();
        let v2 = Version {
            major: 10,
            minor: 0,
            patch: 0,
            pre: None,
        };

        assert_eq!(version(v1), done(v2));
    }

    #[test]
    fn parse_minor() {
        let v1 = "10.11".as_bytes();
        let v2 = Version {
            major: 10,
            minor: 11,
            patch: 0,
            pre: None,
        };

        assert_eq!(version(v1), done(v2));
    }

    #[test]
    fn parse_version() {
        let v1 = "10.11.12".as_bytes();
        let v2 = Version {
            major: 10,
            minor: 11,
            patch: 12,
            pre: None,
        };

        assert_eq!(version(v1), done(v2));
    }

    #[test]
    fn parse_pre() {
        let v1 = "1.0.0-alpha".as_bytes();
        let v2 = Version {
            major: 1,
            minor: 0,
            patch: 0,
            pre: Some(String::from("alpha")),
        };

        assert_eq!(version(v1), done(v2));
    }
}
<commit_msg>make the parsers a bit simpler<commit_after>use std::str;
use nom;
use nom::IResult;

/// Try to parse a version
///
/// If there's an error, then you just get (). for now.
pub fn try_parse(i: &[u8]) -> Result<super::Version, ()> {
    match version(i) {
        IResult::Done(_, d) => Ok(d),
        _ => Err(()),
    }
}

/// parse a u32
fn number(i: &[u8]) -> IResult<&[u8], u32> {
    map_res!(i,
             nom::digit,
             |d| str::FromStr::from_str(str::from_utf8(d).unwrap()))
}

/// parse a word
fn word(i: &[u8]) -> IResult<&[u8], String> {
    map_res!(i,
             nom::alphanumeric,
             |d: &[u8]| String::from_utf8(d.to_vec()))
}

/// parse a . and then a u32
named!(dot_number<&[u8], u32>, preceded!(char!('.'), number));

named!(pre<&[u8], Option<String> >, opt!(complete!(preceded!(char!('-'), word))));

/// parse a version
///
/// A version is currently:
///
/// - a major version number
/// - optionally followed by a dot and a minor version number
/// - optionally followed by a dot and a patch version number
///
/// If some of the versions aren't present, gives a zero.
named!(version<&[u8], super::Version>, chain!(
        major: number ~
        rest: opt!(complete!(chain!(
                minor: dot_number ~
                patch_pre: opt!(complete!(chain!(
                    patch: dot_number ~
                    pre: pre,
                    || { (patch, pre) }
                ))),
                || {
                    let (patch, pre) = match patch_pre {
                        Some((patch, ref pre)) => (patch, pre.clone()),
                        None => (0, None),
                    };

                    (minor, patch, pre)
                }
        ))),
        || {
            let (minor, patch, pre) = match rest {
                Some((minor, patch, ref pre)) => (minor, patch, pre.clone()),
                None => (0, 0, None),
            };
            super::Version { major: major, minor: minor, patch: patch, pre: pre }
        }
));

#[cfg(test)]
mod tests {
    use super::number;
    use super::dot_number;
    use super::version;
    use Version;

    fn done<T>(x: T) -> ::nom::IResult<&'static [u8], T> {
        ::nom::IResult::Done(&[][..], x)
    }

    #[test]
    fn parse_number() {
        let v = "10".as_bytes();

        assert_eq!(number(v), done(10));
    }

    #[test]
    fn parse_dot_number() {
        let v = ".10".as_bytes();

        assert_eq!(dot_number(v), done(10));
    }

    #[test]
    fn parse_major() {
        let v1 = "10".as_bytes();
        let v2 = Version {
            major: 10,
            minor: 0,
            patch: 0,
            pre: None,
        };

        assert_eq!(version(v1), done(v2));
    }

    #[test]
    fn parse_minor() {
        let v1 = "10.11".as_bytes();
        let v2 = Version {
            major: 10,
            minor: 11,
            patch: 0,
            pre: None,
        };

        assert_eq!(version(v1), done(v2));
    }

    #[test]
    fn parse_version() {
        let v1 = "10.11.12".as_bytes();
        let v2 = Version {
            major: 10,
            minor: 11,
            patch: 12,
            pre: None,
        };

        assert_eq!(version(v1), done(v2));
    }

    #[test]
    fn parse_pre() {
        let v1 = "1.0.0-alpha".as_bytes();
        let v2 = Version {
            major: 1,
            minor: 0,
            patch: 0,
            pre: Some(String::from("alpha")),
        };

        assert_eq!(version(v1), done(v2));
    }
}
<|endoftext|>


<commit_before>use std::borrow::Cow;
use std::io;
use std::str;

/// re-export of [`crate::core::PositionStore`](crate::core::PositionStore).
/// Keep in mind that the methods of `PositionStore` are only used internally.
pub use crate::core::PositionStore;

pub trait BaseRecord {
    /// Return the header line of the record as byte slice
    fn head(&self) -> &[u8];

    /// Return the record sequence as byte slice. With FASTA and multi-line
    /// FASTQ, this slice may contain line terminators if called on `RefRecord`.
    /// Use [`full_seq`](#tymethod.full_seq) or
    /// [`full_seq_given`](#tymethod.full_seq_given) instead in
    /// such cases, or iterate over lines with `fasta/q/x::RefRecord::seq_lines()`
    fn seq(&self) -> &[u8];

    /// Returns the full sequence as `Cow<[u8]>`.
    /// If the sequence consists of a single line, then the sequence will be
    /// borrowed from the underlying buffer (equivalent to calling `seq()`).
    /// If there are multiple lines, an owned copy will be created.
    fn full_seq(&self) -> Cow<[u8]>;

    /// Like `full_seq` returns the full sequence as `Cow<[u8]>`, with the
    /// difference that the vector that is used to store the contiguous
    /// sequence (in the case of multiple sequence lines) is provided in
    /// a closure, which is only executed if needed.
    /// This allows e.g. storing the necessary vectors in an arena allocator.
    fn full_seq_given<'s, F>(&'s self, owned_fn: F) -> Cow<'s, [u8]>
    where
        F: FnOnce() -> &'s mut Vec<u8>;

    /// Returns the number of sequence lines.
    /// Calling `record.seq_lines()` gives the same result as
    /// `record.seq_lines().len()`, but the latter may be slow depending on the
    /// type [`PositionStore`](crate::core::PositionStore) used since the line
    /// positions may have to be searched first.
    fn num_seq_lines(&self) -> usize;

    /// Returns a booleany specifying whether there is quality information in
    /// this record or not.
    fn has_quality(&self) -> bool;

    /// Returns the quality information or `None`.
    /// With multi-line FASTQ, this slice may contain line terminators if
    /// called on `RefRecord`.
    /// Use [`opt_full_qual`](#tymethod.opt_full_qual) or
    /// [`opt_full_qual_given`](#tymethod.opt_full_qual_given) instead in
    /// such cases, or iterate over lines with `fasta/q/x::RefRecord::[opt_]qual_lines()`
    fn opt_qual(&self) -> Option<&[u8]>;

    /// Returns the quality scores as contiguous `Cow<[u8]>`, if present. If not
    /// dealing with multi-line FASTQ, this does not involve any copying
    /// (same as if calling `opt_qual()`).
    /// If there are multiple lines, an owned copy will be created.
    fn opt_full_qual(&self) -> Option<Cow<[u8]>>;

    /// Like `opt_full_qual` returns the full quality scores as `Cow<[u8]>`,
    /// with the difference that the vector that is used to store the contiguous
    /// data (in the case of multiple quality lines) is provided in
    /// a closure, which is only executed if needed.
    fn opt_full_qual_given<'s, F>(&'s self, owned_fn: F) -> Option<Cow<'s, [u8]>>
    where
        F: FnOnce() -> &'s mut Vec<u8>;

    /// Returns the number of quality lines. Always > 0 with the current parsers.
    fn num_qual_lines(&self) -> usize;

    /// Writes the record to an output.
    ///
    /// The default for FASTA is to write one sequence line. Otherwise refer to
    /// the method [`fasta::Record::write_wrap`](crate::fasta::Record::write_wrap).
    fn write<W>(&self, writer: W) -> io::Result<()>
    where
        W: io::Write;

    /// Returns the record ID (everything before an optional space) as byte slice.
    #[inline]
    fn id_bytes(&self) -> &[u8] {
        self.head().split(|b| *b == b' ').next().unwrap()
    }

    /// Returns the record ID (everything before an optional space) as `&str`.
    #[inline]
    fn id(&self) -> Result<&str, std::str::Utf8Error> {
        str::from_utf8(self.id_bytes())
    }

    /// Returns the record description (separated from the ID by a space)
    /// as byte slice, if present.
    #[inline]
    fn desc_bytes(&self) -> Option<&[u8]> {
        self.head().splitn(2, |b| *b == b' ').nth(1)
    }

    /// Returns the record description (separated from the ID by a space)
    /// as `&str` slice, if present.
    #[inline]
    fn desc(&self) -> Option<Result<&str, std::str::Utf8Error>> {
        self.desc_bytes().map(str::from_utf8)
    }

    /// Return both the ID and the description of the record (if present)
    /// This should be faster than calling `id()` and `desc()` separately.
    #[inline]
    fn id_desc_bytes(&self) -> (&[u8], Option<&[u8]>) {
        let mut h = self.head().splitn(2, |c| *c == b' ');
        (h.next().unwrap(), h.next())
    }

    /// Returns both the ID and the description of the record (if present)
    /// as `&str` slices.
    /// This should be faster than calling `id()` and `desc()` separately.
    #[inline]
    fn id_desc(&self) -> Result<(&str, Option<&str>), std::str::Utf8Error> {
        let mut h = str::from_utf8(self.head())?.splitn(2, ' ');
        Ok((h.next().unwrap(), h.next()))
    }
}

impl<'a, R> BaseRecord for &'a R
where
    R: BaseRecord,
{
    fn head(&self) -> &[u8] {
        (**self).head()
    }

    fn seq(&self) -> &[u8] {
        (**self).seq()
    }

    fn full_seq(&self) -> Cow<[u8]> {
        (**self).full_seq()
    }

    fn full_seq_given<'s, F>(&'s self, owned_fn: F) -> Cow<'s, [u8]>
    where
        F: FnOnce() -> &'s mut Vec<u8>,
    {
        (**self).full_seq_given(owned_fn)
    }

    fn num_seq_lines(&self) -> usize {
        (**self).num_seq_lines()
    }

    fn id_bytes(&self) -> &[u8] {
        (**self).id_bytes()
    }

    fn id(&self) -> Result<&str, std::str::Utf8Error> {
        (**self).id()
    }

    fn desc_bytes(&self) -> Option<&[u8]> {
        (**self).desc_bytes()
    }

    fn desc(&self) -> Option<Result<&str, std::str::Utf8Error>> {
        (**self).desc()
    }

    fn id_desc_bytes(&self) -> (&[u8], Option<&[u8]>) {
        (**self).id_desc_bytes()
    }

    fn id_desc(&self) -> Result<(&str, Option<&str>), std::str::Utf8Error> {
        (**self).id_desc()
    }

    fn has_quality(&self) -> bool {
        (**self).has_quality()
    }

    fn opt_qual(&self) -> Option<&[u8]> {
        (**self).opt_qual()
    }

    fn opt_full_qual(&self) -> Option<Cow<[u8]>> {
        (**self).opt_full_qual()
    }

    fn opt_full_qual_given<'s, F: FnOnce() -> &'s mut Vec<u8>>(
        &'s self,
        owned_fn: F,
    ) -> Option<Cow<'s, [u8]>> {
        (**self).opt_full_qual_given(owned_fn)
    }

    fn num_qual_lines(&self) -> usize {
        (**self).num_qual_lines()
    }

    fn write<W: io::Write>(&self, writer: W) -> io::Result<()> {
        (**self).write(writer)
    }
}
<commit_msg>Speed up access to ID and description<commit_after>use std::borrow::Cow;
use std::io;
use std::str;
use memchr::memchr;

/// re-export of [`crate::core::PositionStore`](crate::core::PositionStore).
/// Keep in mind that the methods of `PositionStore` are only used internally.
pub use crate::core::PositionStore;

pub trait BaseRecord {
    /// Return the header line of the record as byte slice
    fn head(&self) -> &[u8];

    /// Return the record sequence as byte slice. With FASTA and multi-line
    /// FASTQ, this slice may contain line terminators if called on `RefRecord`.
    /// Use [`full_seq`](#tymethod.full_seq) or
    /// [`full_seq_given`](#tymethod.full_seq_given) instead in
    /// such cases, or iterate over lines with `fasta/q/x::RefRecord::seq_lines()`
    fn seq(&self) -> &[u8];

    /// Returns the full sequence as `Cow<[u8]>`.
    /// If the sequence consists of a single line, then the sequence will be
    /// borrowed from the underlying buffer (equivalent to calling `seq()`).
    /// If there are multiple lines, an owned copy will be created.
    fn full_seq(&self) -> Cow<[u8]>;

    /// Like `full_seq` returns the full sequence as `Cow<[u8]>`, with the
    /// difference that the vector that is used to store the contiguous
    /// sequence (in the case of multiple sequence lines) is provided in
    /// a closure, which is only executed if needed.
    /// This allows e.g. storing the necessary vectors in an arena allocator.
    fn full_seq_given<'s, F>(&'s self, owned_fn: F) -> Cow<'s, [u8]>
    where
        F: FnOnce() -> &'s mut Vec<u8>;

    /// Returns the number of sequence lines.
    /// Calling `record.seq_lines()` gives the same result as
    /// `record.seq_lines().len()`, but the latter may be slow depending on the
    /// type [`PositionStore`](crate::core::PositionStore) used since the line
    /// positions may have to be searched first.
    fn num_seq_lines(&self) -> usize;

    /// Returns a booleany specifying whether there is quality information in
    /// this record or not.
    fn has_quality(&self) -> bool;

    /// Returns the quality information or `None`.
    /// With multi-line FASTQ, this slice may contain line terminators if
    /// called on `RefRecord`.
    /// Use [`opt_full_qual`](#tymethod.opt_full_qual) or
    /// [`opt_full_qual_given`](#tymethod.opt_full_qual_given) instead in
    /// such cases, or iterate over lines with `fasta/q/x::RefRecord::[opt_]qual_lines()`
    fn opt_qual(&self) -> Option<&[u8]>;

    /// Returns the quality scores as contiguous `Cow<[u8]>`, if present. If not
    /// dealing with multi-line FASTQ, this does not involve any copying
    /// (same as if calling `opt_qual()`).
    /// If there are multiple lines, an owned copy will be created.
    fn opt_full_qual(&self) -> Option<Cow<[u8]>>;

    /// Like `opt_full_qual` returns the full quality scores as `Cow<[u8]>`,
    /// with the difference that the vector that is used to store the contiguous
    /// data (in the case of multiple quality lines) is provided in
    /// a closure, which is only executed if needed.
    fn opt_full_qual_given<'s, F>(&'s self, owned_fn: F) -> Option<Cow<'s, [u8]>>
    where
        F: FnOnce() -> &'s mut Vec<u8>;

    /// Returns the number of quality lines. Always > 0 with the current parsers.
    fn num_qual_lines(&self) -> usize;

    /// Writes the record to an output.
    ///
    /// The default for FASTA is to write one sequence line. Otherwise refer to
    /// the method [`fasta::Record::write_wrap`](crate::fasta::Record::write_wrap).
    fn write<W>(&self, writer: W) -> io::Result<()>
    where
        W: io::Write;

    /// Returns the record ID (everything before an optional space) as byte slice.
    #[inline]
    fn id_bytes(&self) -> &[u8] {
        let head = self.head();
        if let Some(pos) = memchr(b' ', head) {
            return &head[..pos];
        }
        head
    }

    /// Returns the record ID (everything before an optional space) as `&str`.
    #[inline]
    fn id(&self) -> Result<&str, str::Utf8Error> {
        str::from_utf8(self.id_bytes())
    }

    /// Returns the record description (separated from the ID by a space)
    /// as byte slice, if present.
    #[inline]
    fn desc_bytes(&self) -> Option<&[u8]> {
        let head = self.head();
        if let Some(pos) = memchr(b' ', head) {
            return Some(&head[pos + 1..])
        }
        None

    }

    /// Returns the record description (separated from the ID by a space)
    /// as `&str` slice, if present.
    #[inline]
    fn desc(&self) -> Option<Result<&str, str::Utf8Error>> {
        self.desc_bytes().map(str::from_utf8)
    }

    /// Return both the ID and the description of the record (if present)
    /// This should be faster than calling `id()` and `desc()` separately.
    #[inline]
    fn id_desc_bytes(&self) -> (&[u8], Option<&[u8]>) {
        let head = self.head();
        if let Some(pos) = memchr(b' ', head) {
            return (&head[..pos], Some(&head[pos + 1..]))
        }
        (head, None)
    }

    /// Returns both the ID and the description of the record (if present)
    /// as `&str` slices.
    /// This should be faster than calling `id()` and `desc()` separately.
    #[inline]
    fn id_desc(&self) -> Result<(&str, Option<&str>), str::Utf8Error> {
        let (id, desc) = self.id_desc_bytes();
        Ok((str::from_utf8(id)?, desc.map(str::from_utf8).transpose()?))
    }
}

impl<'a, R> BaseRecord for &'a R
where
    R: BaseRecord,
{
    fn head(&self) -> &[u8] {
        (**self).head()
    }

    fn seq(&self) -> &[u8] {
        (**self).seq()
    }

    fn full_seq(&self) -> Cow<[u8]> {
        (**self).full_seq()
    }

    fn full_seq_given<'s, F>(&'s self, owned_fn: F) -> Cow<'s, [u8]>
    where
        F: FnOnce() -> &'s mut Vec<u8>,
    {
        (**self).full_seq_given(owned_fn)
    }

    fn num_seq_lines(&self) -> usize {
        (**self).num_seq_lines()
    }

    fn id_bytes(&self) -> &[u8] {
        (**self).id_bytes()
    }

    fn id(&self) -> Result<&str, std::str::Utf8Error> {
        (**self).id()
    }

    fn desc_bytes(&self) -> Option<&[u8]> {
        (**self).desc_bytes()
    }

    fn desc(&self) -> Option<Result<&str, std::str::Utf8Error>> {
        (**self).desc()
    }

    fn id_desc_bytes(&self) -> (&[u8], Option<&[u8]>) {
        (**self).id_desc_bytes()
    }

    fn id_desc(&self) -> Result<(&str, Option<&str>), std::str::Utf8Error> {
        (**self).id_desc()
    }

    fn has_quality(&self) -> bool {
        (**self).has_quality()
    }

    fn opt_qual(&self) -> Option<&[u8]> {
        (**self).opt_qual()
    }

    fn opt_full_qual(&self) -> Option<Cow<[u8]>> {
        (**self).opt_full_qual()
    }

    fn opt_full_qual_given<'s, F: FnOnce() -> &'s mut Vec<u8>>(
        &'s self,
        owned_fn: F,
    ) -> Option<Cow<'s, [u8]>> {
        (**self).opt_full_qual_given(owned_fn)
    }

    fn num_qual_lines(&self) -> usize {
        (**self).num_qual_lines()
    }

    fn write<W: io::Write>(&self, writer: W) -> io::Result<()> {
        (**self).write(writer)
    }
}
<|endoftext|>


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

use util::CargoResult;

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

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

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

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

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

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

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

        MarkdownRenderer {
            html_sanitizer: html_sanitizer,
        }
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        let result = markdown_to_html(absolute, Some("https://google.com/")).unwrap();
        assert_eq!(
            result,
            "
hi
\n"
        );
    }
}
<commit_msg>Add a failing test-- too much resolving now :)<commit_after>use ammonia::{Builder, UrlRelative};
use comrak;
use std::borrow::Cow;
use url::Url;

use util::CargoResult;

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

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

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

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

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

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

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

        MarkdownRenderer {
            html_sanitizer: html_sanitizer,
        }
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        let result = markdown_to_html(readme_text, Some(&repository)).unwrap();
        assert_eq!(
            result,
            "\n"
        );
    }
}
<|endoftext|>


<commit_before>use std::intrinsics::TypeId;
use std::any::{ Any, AnyRefExt };

use GenericEvent;

/// Render argument.
#[deriving(Clone, PartialEq, Show)]
pub struct RenderArgs {
    /// Extrapolated time in seconds, used to do smooth animation.
    pub ext_dt: f64,
    /// The width of rendered area.
    pub width: u32,
    /// The height of rendered area.
    pub height: u32,
}

/// Implemented by event structures that supports render event.
pub trait RenderEvent {
    /// Creates a render event.
    fn from_render_args(args: &RenderArgs) -> Option<Self>;
    /// Calls closure if this is a render event.
    fn update(&self, f: |&RenderArgs|);
}

impl<T: GenericEvent> RenderEvent for T {
    #[inline(always)]
    fn from_render_args(args: &RenderArgs) -> Option<T> {
        let id = TypeId::of::<Box<RenderEvent>>();
        GenericEvent::from_event(id, args as &Any)
    }
    #[inline(always)]
    fn update(&self, f: |&RenderArgs|) {
        let id = TypeId::of::<Box<RenderEvent>>();
        self.with_event(id, |any| {
            match any.downcast_ref::<RenderArgs>() {
                Some(args) => f(args),
                None => fail!("Expected `RenderArgs`")
            }
        });
    }
}
<commit_msg>Fixed `render` method<commit_after>use std::intrinsics::TypeId;
use std::any::{ Any, AnyRefExt };

use GenericEvent;

/// Render argument.
#[deriving(Clone, PartialEq, Show)]
pub struct RenderArgs {
    /// Extrapolated time in seconds, used to do smooth animation.
    pub ext_dt: f64,
    /// The width of rendered area.
    pub width: u32,
    /// The height of rendered area.
    pub height: u32,
}

/// Implemented by event structures that supports render event.
pub trait RenderEvent {
    /// Creates a render event.
    fn from_render_args(args: &RenderArgs) -> Option<Self>;
    /// Calls closure if this is a render event.
    fn render(&self, f: |&RenderArgs|);
}

impl<T: GenericEvent> RenderEvent for T {
    #[inline(always)]
    fn from_render_args(args: &RenderArgs) -> Option<T> {
        let id = TypeId::of::<Box<RenderEvent>>();
        GenericEvent::from_event(id, args as &Any)
    }
    #[inline(always)]
    fn render(&self, f: |&RenderArgs|) {
        let id = TypeId::of::<Box<RenderEvent>>();
        self.with_event(id, |any| {
            match any.downcast_ref::<RenderArgs>() {
                Some(args) => f(args),
                None => fail!("Expected `RenderArgs`")
            }
        });
    }
}
<|endoftext|>


<commit_before>
use std::borrow::{ Cow };
use std::collections::{ HashMap };
use std::fmt::{ self, Write };

use maud::{ self, Escaper };
use pulldown_cmark::{ Event, Tag };

use escape::HrefEscaper;
use markdown::{ MarkdownString, MarkdownEvents };

struct Context<'a> {
  numbers: HashMap<Cow<'a, str>, usize>,
  within_image: bool,
}

impl<'a> maud::Render for MarkdownString<'a> {
  fn render(&self, w: &mut Write) -> fmt::Result {
    render_events(self.events(), w)
  }
}

impl<'a, I: 'a + Iterator<Item=Event<'a>>, F: Fn() -> I> maud::Render for MarkdownEvents<'a, I, F> {
  fn render(&self, w: &mut Write) -> fmt::Result {
    render_events(self.events(), w)
  }
}

fn render_events<'a, I: Iterator<Item=Event<'a>>>(events: I, mut w: &mut Write) -> fmt::Result {
  let mut context = Context {
    numbers: HashMap::new(),
    within_image: false,
  };
  for event in events {
    if context.within_image {
      try!(render_event_within_image(event, &mut context, &mut w));
    } else {
      try!(render_event(event, &mut context, &mut w));
    }
  }
  Ok(())
}

fn render_event<'a>(event: Event<'a>, context: &mut Context<'a>, w: &mut Write) -> fmt::Result {
  match event {
    Event::Start(tag) => render_start_tag(tag, context, w),
    Event::End(tag) => render_end_tag(tag, w),
    Event::Text(text) => Escaper::new(w).write_str(&*text),
    Event::Html(html) | Event::InlineHtml(html) => w.write_str(&*html),
    Event::SoftBreak => w.write_char('\n'),
    Event::HardBreak => w.write_str("
\n"),
    Event::FootnoteReference(name) => render_footnote_reference(name, &mut context.numbers, w),
  }
}

fn render_event_within_image<'a>(event: Event<'a>, context: &mut Context<'a>, w: &mut Write) -> fmt::Result {
  match event {
    Event::Start(_) => Err(fmt::Error),
    Event::End(Tag::Image(_, title)) => {
      context.within_image = false;
      render_image_end_tag(&*title, w)
    },
    Event::End(_) => Err(fmt::Error),
    Event::Text(text) => Escaper::new(w).write_str(&*text),
    Event::Html(_) => Err(fmt::Error),
    Event::InlineHtml(html) => Escaper::new(w).write_str(&*html),
    Event::SoftBreak | Event::HardBreak => w.write_char(' '),
    Event::FootnoteReference(name) => render_footnote_reference_within_image(name, &mut context.numbers, w),
  }
}

fn render_start_tag<'a>(tag: Tag<'a>, context: &mut Context<'a>, mut w: &mut Write) -> fmt::Result {
  match tag {
    Tag::Rule => w.write_str("
\n"),
    Tag::Code => w.write_str(""),
    Tag::Strong => w.write_str(""),
    Tag::Emphasis => w.write_str(""),
    Tag::Paragraph => w.write_str("
"),
    Tag::BlockQuote => w.write_str("
\n"),

    Tag::Table(_) => w.write_str("
"),
    Tag::TableHead => w.write_str("

"),
    Tag::TableRow => w.write_str("
"),
    Tag::TableCell => w.write_str(""),

    Tag::Item => w.write_str("
"),
    Tag::List(None) => w.write_str("
\n"),
    Tag::List(Some(1)) => w.write_str("
\n"),
    Tag::List(Some(start)) => write!(w, "
\n", start),

    Tag::Header(level) => render_header_start_tag(level as u8, w),
    Tag::CodeBlock(info) => render_code_block_start_tag(&*info, w),
    Tag::Image(src, _) => {
      context.within_image = false;
      render_image_start_tag(&*src, w)
    },
    Tag::Link(dest, title) => render_link_start_tag(&*dest, &*title, w),
    Tag::FootnoteDefinition(name) => render_footnote_definition_start_tag(name, &mut context.numbers, w),
  }
}

fn render_end_tag<'a>(tag: Tag<'a>, mut w: &mut Write) -> fmt::Result {
  match tag {
    Tag::Rule => Ok(()),
    Tag::Code => w.write_str(""),
    Tag::Strong => w.write_str(""),
    Tag::Emphasis => w.write_str(""),
    Tag::Paragraph => w.write_str("
\n"),
    Tag::BlockQuote => w.write_str("
\n"),

    Tag::Table(_) => w.write_str("
\n"),
    Tag::TableHead => w.write_str("

\n"),
    Tag::TableRow => w.write_str("
\n"),
    Tag::TableCell => w.write_str("	"),

    Tag::Item => w.write_str("
\n"),
    Tag::List(None) => w.write_str("
\n"),
    Tag::List(Some(_)) => w.write_str("
\n"),

    Tag::Header(level) => render_header_end_tag(level as u8, w),
    Tag::CodeBlock(_) => w.write_str("
\n"),
    Tag::Image(_, _) => Err(fmt::Error),
    Tag::Link(_, _) => w.write_str(""),
    Tag::FootnoteDefinition(_) => w.write_str("
\n"),
  }
}

fn render_link_start_tag(dest: &str, title: &str, mut w: &mut Write) -> fmt::Result {
  try!(w.write_str("")
}

fn render_image_start_tag(src: &str, mut w: &mut Write) -> fmt::Result {
  try!(w.write_str(" fmt::Result {
  if !title.is_empty() {
    try!(w.write_str("\" title=\""));
    try!(Escaper::new(&mut w).write_str(title));
  }
  w.write_str("\" />")
}

fn render_header_start_tag(level: u8, mut w: &mut Write) -> fmt::Result {
  try!(w.write_str("<h"));
  try!(w.write_char((b'0' + level) as char));
  w.write_char('>')
}

fn render_header_end_tag(level: u8, mut w: &mut Write) -> fmt::Result {
  try!(w.write_str("</h"));
  try!(w.write_char((b'0' + level) as char));
  w.write_str(">\n")
}

fn render_code_block_start_tag(info: &str, mut w: &mut Write) -> fmt::Result {
  let lang = info.split(' ').next().unwrap();
  if lang.is_empty() {
    w.write_str("
")
  } else {
    try!(w.write_str("
")
  }
}

fn render_footnote_definition_start_tag<'a>(name: Cow<'a, str>, numbers: &mut HashMap<Cow<'a, str>, usize>, mut w: &mut Write) -> fmt::Result {
  try!(w.write_str("
{}", number)
}

fn render_footnote_reference<'a>(name: Cow<'a, str>, numbers: &mut HashMap<Cow<'a, str>, usize>, mut w: &mut Write) -> fmt::Result {
  try!(w.write_str("{}", number)
}

fn render_footnote_reference_within_image<'a>(name: Cow<'a, str>, numbers: &mut HashMap<Cow<'a, str>, usize>, mut w: &mut Write) -> fmt::Result {
  let len = numbers.len();
  let number = numbers.entry(name).or_insert(len);
  write!(w, "[{}]", number)
}

<commit_msg>Fix setting within image<commit_after>
use std::borrow::{ Cow };
use std::collections::{ HashMap };
use std::fmt::{ self, Write };

use maud::{ self, Escaper };
use pulldown_cmark::{ Event, Tag };

use escape::HrefEscaper;
use markdown::{ MarkdownString, MarkdownEvents };

struct Context<'a> {
  numbers: HashMap<Cow<'a, str>, usize>,
  within_image: bool,
}

impl<'a> maud::Render for MarkdownString<'a> {
  fn render(&self, w: &mut Write) -> fmt::Result {
    render_events(self.events(), w)
  }
}

impl<'a, I: 'a + Iterator<Item=Event<'a>>, F: Fn() -> I> maud::Render for MarkdownEvents<'a, I, F> {
  fn render(&self, w: &mut Write) -> fmt::Result {
    render_events(self.events(), w)
  }
}

fn render_events<'a, I: Iterator<Item=Event<'a>>>(events: I, mut w: &mut Write) -> fmt::Result {
  let mut context = Context {
    numbers: HashMap::new(),
    within_image: false,
  };
  for event in events {
    if context.within_image {
      try!(render_event_within_image(event, &mut context, &mut w));
    } else {
      try!(render_event(event, &mut context, &mut w));
    }
  }
  Ok(())
}

fn render_event<'a>(event: Event<'a>, context: &mut Context<'a>, w: &mut Write) -> fmt::Result {
  match event {
    Event::Start(tag) => render_start_tag(tag, context, w),
    Event::End(tag) => render_end_tag(tag, w),
    Event::Text(text) => Escaper::new(w).write_str(&*text),
    Event::Html(html) | Event::InlineHtml(html) => w.write_str(&*html),
    Event::SoftBreak => w.write_char('\n'),
    Event::HardBreak => w.write_str("
\n"),
    Event::FootnoteReference(name) => render_footnote_reference(name, &mut context.numbers, w),
  }
}

fn render_event_within_image<'a>(event: Event<'a>, context: &mut Context<'a>, w: &mut Write) -> fmt::Result {
  match event {
    Event::Start(_) => Err(fmt::Error),
    Event::End(Tag::Image(_, title)) => {
      context.within_image = false;
      render_image_end_tag(&*title, w)
    },
    Event::End(_) => Err(fmt::Error),
    Event::Text(text) => Escaper::new(w).write_str(&*text),
    Event::Html(_) => Err(fmt::Error),
    Event::InlineHtml(html) => Escaper::new(w).write_str(&*html),
    Event::SoftBreak | Event::HardBreak => w.write_char(' '),
    Event::FootnoteReference(name) => render_footnote_reference_within_image(name, &mut context.numbers, w),
  }
}

fn render_start_tag<'a>(tag: Tag<'a>, context: &mut Context<'a>, mut w: &mut Write) -> fmt::Result {
  match tag {
    Tag::Rule => w.write_str("
\n"),
    Tag::Code => w.write_str(""),
    Tag::Strong => w.write_str(""),
    Tag::Emphasis => w.write_str(""),
    Tag::Paragraph => w.write_str("
"),
    Tag::BlockQuote => w.write_str("
\n"),

    Tag::Table(_) => w.write_str("
"),
    Tag::TableHead => w.write_str("

"),
    Tag::TableRow => w.write_str("
"),
    Tag::TableCell => w.write_str(""),

    Tag::Item => w.write_str("
"),
    Tag::List(None) => w.write_str("
\n"),
    Tag::List(Some(1)) => w.write_str("
\n"),
    Tag::List(Some(start)) => write!(w, "
\n", start),

    Tag::Header(level) => render_header_start_tag(level as u8, w),
    Tag::CodeBlock(info) => render_code_block_start_tag(&*info, w),
    Tag::Image(src, _) => {
      context.within_image = true;
      render_image_start_tag(&*src, w)
    },
    Tag::Link(dest, title) => render_link_start_tag(&*dest, &*title, w),
    Tag::FootnoteDefinition(name) => render_footnote_definition_start_tag(name, &mut context.numbers, w),
  }
}

fn render_end_tag<'a>(tag: Tag<'a>, mut w: &mut Write) -> fmt::Result {
  match tag {
    Tag::Rule => Ok(()),
    Tag::Code => w.write_str(""),
    Tag::Strong => w.write_str(""),
    Tag::Emphasis => w.write_str(""),
    Tag::Paragraph => w.write_str("
\n"),
    Tag::BlockQuote => w.write_str("
\n"),

    Tag::Table(_) => w.write_str("
\n"),
    Tag::TableHead => w.write_str("

\n"),
    Tag::TableRow => w.write_str("
\n"),
    Tag::TableCell => w.write_str("	"),

    Tag::Item => w.write_str("
\n"),
    Tag::List(None) => w.write_str("
\n"),
    Tag::List(Some(_)) => w.write_str("
\n"),

    Tag::Header(level) => render_header_end_tag(level as u8, w),
    Tag::CodeBlock(_) => w.write_str("
\n"),
    Tag::Image(_, _) => Err(fmt::Error),
    Tag::Link(_, _) => w.write_str(""),
    Tag::FootnoteDefinition(_) => w.write_str("
\n"),
  }
}

fn render_link_start_tag(dest: &str, title: &str, mut w: &mut Write) -> fmt::Result {
  try!(w.write_str("")
}

fn render_image_start_tag(src: &str, mut w: &mut Write) -> fmt::Result {
  try!(w.write_str(" fmt::Result {
  if !title.is_empty() {
    try!(w.write_str("\" title=\""));
    try!(Escaper::new(&mut w).write_str(title));
  }
  w.write_str("\" />")
}

fn render_header_start_tag(level: u8, mut w: &mut Write) -> fmt::Result {
  try!(w.write_str("<h"));
  try!(w.write_char((b'0' + level) as char));
  w.write_char('>')
}

fn render_header_end_tag(level: u8, mut w: &mut Write) -> fmt::Result {
  try!(w.write_str("</h"));
  try!(w.write_char((b'0' + level) as char));
  w.write_str(">\n")
}

fn render_code_block_start_tag(info: &str, mut w: &mut Write) -> fmt::Result {
  let lang = info.split(' ').next().unwrap();
  if lang.is_empty() {
    w.write_str("
")
  } else {
    try!(w.write_str("
")
  }
}

fn render_footnote_definition_start_tag<'a>(name: Cow<'a, str>, numbers: &mut HashMap<Cow<'a, str>, usize>, mut w: &mut Write) -> fmt::Result {
  try!(w.write_str("
{}", number)
}

fn render_footnote_reference<'a>(name: Cow<'a, str>, numbers: &mut HashMap<Cow<'a, str>, usize>, mut w: &mut Write) -> fmt::Result {
  try!(w.write_str("{}", number)
}

fn render_footnote_reference_within_image<'a>(name: Cow<'a, str>, numbers: &mut HashMap<Cow<'a, str>, usize>, mut w: &mut Write) -> fmt::Result {
  let len = numbers.len();
  let number = numbers.entry(name).or_insert(len);
  write!(w, "[{}]", number)
}

<|endoftext|>


<commit_before>

// FIXME: turn `: $pt:ident` into `ty`
// TODO: don't use a hashmap for perfs
// TODO: don't panic if parsing fails
#[macro_export]
macro_rules! router {
    ($request:expr, $(GET ($($pat:tt)+) => ($($closure:tt)+),)* _ => $def:expr) => {
        {
            use std;

            let ref request = $request;

            // ignoring the GET parameters (everything after `?`)
            let request_url = {
                let url = request.url();
                let pos = url.find('?').unwrap_or(url.len());
                &url[..pos]
            };

            let mut ret = None;

            $({
                // we use a RefCell just to avoid warnings about `values doesn't need to be mutable`
                let values = std::cell::RefCell::new(std::collections::HashMap::<&'static str, &str>::new());
                if ret.is_none() && router!(__check_pattern values request_url $($pat)+) {
                    let values = values.borrow();
                    ret = router!(__parse_closure values $($closure)+);
                }
            })+

            if let Some(ret) = ret {
                ret
            } else {
                $def()
            }
        }
    };

    (__parse_closure $values:ident |$($pn:ident $(: $pt:ident)*),*| $(-> $ret:ident)* $val:expr) => (
        {
            let closure = |$($pn$(:$pt)*),*| $(-> $ret)* { $val };
            let ret;
            loop {
                ret = Some(closure(
                    $(
                        match $values.get(stringify!($pn))
                                     .expect("Closure parameter missing from route").parse()
                        {
                            Ok(val) => val,
                            Err(_) => { ret = None; break; }
                        }
                    ),*
                ));
                break;
            }
            ret
        }
    );

    (__parse_closure $values:ident || $val:expr) => (
        {
            let closure = || $val;
            assert!($values.len() == 0);
            Some(closure())
        }
    );

    (__check_pattern $values:ident $url:ident /{$p:ident} $($rest:tt)*) => (
        if !$url.starts_with('/') {
            false
        } else {
            let url = &$url[1..];
            let pat_end = url.find('/').unwrap_or(url.len());
            let rest_url = &url[pat_end..];
            if router!(__check_pattern $values rest_url $($rest)*) {
                $values.borrow_mut().insert(stringify!($p), &url[0 .. pat_end]);
                true
            } else {
                false
            }
        }
    );

    (__check_pattern $values:ident $url:ident /$p:ident $($rest:tt)*) => (
        {
            let required = concat!("/", stringify!($p));
            if $url.starts_with(required) {
                let rest_url = &$url[required.len()..];
                router!(__check_pattern $values rest_url $($rest)*)
            } else {
                false
            }
        }
    );

    (__check_pattern $values:ident $url:ident) => (
        $url.len() == 0
    );

    (__check_pattern $values:ident $url:ident /) => (
        $url == "/"
    );
}
<commit_msg>Add documentation to the router! macro<commit_after>
/// Equivalent to a `match` expression but for routes.
///
/// Here is an example usage:
///
/// ```ignored      // FIXME:
/// # #[macro_use] extern crate rouille; fn main() {
/// # let request: rouille::Request = unsafe { std::mem::uninitialized() };
/// router!(request,
///     // first route
///     GET (/) => (|| {
///         12
///     }),
///
///     // other routes here
///
///     _ => || 5
/// )
/// # }
/// ```
///
/// The macro will take each route one by one and execute the first one that matches, similar to a
/// `match` language construct. The whole `router!` expression then returns what the closure
/// returns, therefore they must all return the same type.
///
/// You can use parameters by putting them inside `{}`:
///
/// ```ignore
/// GET (/{id}/foo) => (|id: u32| {
///     ...
/// }),
/// ```
///
/// If you use parameters inside `{}`, then a field with the same name must exist in the closure's
/// parameters list. The parameters do not need be in the same order.
///
/// Each parameter gets parsed through the `FromStr` trait. If the parsing fails, the route is
/// ignored.
///
/// Some other things to note:
///
/// - The right of the `=>` must have a closure-like syntax.
/// - The pattern of the URL and the closure must be inside parentheses. This is to bypass
///   limitations of Rust's macros system.
/// - The default handler (with `_`) must be present or will get a compilation error.
///
// FIXME: turn `: $pt:ident` into `ty`
// TODO: don't use a hashmap for perfs
// TODO: don't panic if parsing fails
#[macro_export]
macro_rules! router {
    ($request:expr, $(GET ($($pat:tt)+) => ($($closure:tt)+),)* _ => $def:expr) => {
        {
            use std;

            let ref request = $request;

            // ignoring the GET parameters (everything after `?`)
            let request_url = {
                let url = request.url();
                let pos = url.find('?').unwrap_or(url.len());
                &url[..pos]
            };

            let mut ret = None;

            $({
                // we use a RefCell just to avoid warnings about `values doesn't need to be mutable`
                let values = std::cell::RefCell::new(std::collections::HashMap::<&'static str, &str>::new());
                if ret.is_none() && router!(__check_pattern values request_url $($pat)+) {
                    let values = values.borrow();
                    ret = router!(__parse_closure values $($closure)+);
                }
            })+

            if let Some(ret) = ret {
                ret
            } else {
                $def()
            }
        }
    };

    (__parse_closure $values:ident |$($pn:ident $(: $pt:ident)*),*| $(-> $ret:ident)* $val:expr) => (
        {
            let closure = |$($pn$(:$pt)*),*| $(-> $ret)* { $val };
            let ret;
            loop {
                ret = Some(closure(
                    $(
                        match $values.get(stringify!($pn))
                                     .expect("Closure parameter missing from route").parse()
                        {
                            Ok(val) => val,
                            Err(_) => { ret = None; break; }
                        }
                    ),*
                ));
                break;
            }
            ret
        }
    );

    (__parse_closure $values:ident || $val:expr) => (
        {
            let closure = || $val;
            assert!($values.len() == 0);
            Some(closure())
        }
    );

    (__check_pattern $values:ident $url:ident /{$p:ident} $($rest:tt)*) => (
        if !$url.starts_with('/') {
            false
        } else {
            let url = &$url[1..];
            let pat_end = url.find('/').unwrap_or(url.len());
            let rest_url = &url[pat_end..];
            if router!(__check_pattern $values rest_url $($rest)*) {
                $values.borrow_mut().insert(stringify!($p), &url[0 .. pat_end]);
                true
            } else {
                false
            }
        }
    );

    (__check_pattern $values:ident $url:ident /$p:ident $($rest:tt)*) => (
        {
            let required = concat!("/", stringify!($p));
            if $url.starts_with(required) {
                let rest_url = &$url[required.len()..];
                router!(__check_pattern $values rest_url $($rest)*)
            } else {
                false
            }
        }
    );

    (__check_pattern $values:ident $url:ident) => (
        $url.len() == 0
    );

    (__check_pattern $values:ident $url:ident /) => (
        $url == "/"
    );
}
<|endoftext|>


<commit_before>//! Contains the routing logic for the website.

use std::error::Error;
use std::fs;
use std::path::Path;

use chrono::NaiveDate;
use mount::Mount;
use hbs::{self, DirectorySource, HandlebarsEngine, Template};
use iron::prelude::*;
use iron::{status, AfterMiddleware, Handler};
use persistent::{self, Read};
use router::{Router, NoRoute};
use staticfile::Static;

use blog;
use config;
use helpers;
use projects::Project;
use persistence::{self, Config, Projects};

/// The number of blog post summaries that should be displayed.
const NUM_SUMMARIES: usize = 3;

fn resume(req: &mut Request) -> IronResult<Response> {
    let mut res = Response::new();

    let data = btreemap!{
        "resume_link" => req.get::<Read<Config>>().unwrap().resume_link.to_owned(),
    };
    res.set_mut(Template::new("resume", data)).set_mut(status::Ok);
    Ok(res)
}

fn projects(req: &mut Request) -> IronResult<Response> {
    let mut res = Response::new();

    let projects = req.get::<Read<Projects>>().unwrap();
    let data = btreemap!{
        "projects" => projects,
    };
    res.set_mut(Template::new("projects", data)).set_mut(status::Ok);
    Ok(res)
}

fn blog_post(req: &mut Request) -> IronResult<Response> {
    let mut res = Response::new();

    let year = req.extensions.get::<Router>().unwrap().find("year").and_then(|y| y.parse().ok());
    let month = req.extensions.get::<Router>().unwrap().find("month").and_then(|m| m.parse().ok());
    let day = req.extensions.get::<Router>().unwrap().find("day").and_then(|d| d.parse().ok());
    let title = req.extensions.get::<Router>().unwrap().find("title");

    let date = match (year, month, day) {
        (Some(year), Some(month), Some(day)) => NaiveDate::from_ymd(year, month, day),
        _ => return Err(IronError::new(NoRoute, status::NotFound)),
    };

    let title = match title {
        Some(title) => title,
        None => return Err(IronError::new(NoRoute, status::NotFound)),
    };

    match blog::get_post(&date, title) {
        Ok(ref post) => {
            res.set_mut(Template::new("blog_post", post)).set_mut(status::Ok);
            Ok(res)
        }
        Err(err) => {
            error!("Error retrieving blog post: {}", err);
            Err(IronError::new(NoRoute, status::NotFound))
        }
    }
}

fn blog(_: &mut Request) -> IronResult<Response> {
    let mut res = Response::new();

    let connection = persistence::get_db_connection();
    let data = btreemap!{
        "posts" => blog::get_summaries(&connection).unwrap(),
    };
    res.set_mut(Template::new("blog", data)).set_mut(status::Ok);
    Ok(res)
}

fn about(_: &mut Request) -> IronResult<Response> {
    let mut res = Response::new();

    let images = Path::new("static/images/slideshow");
    let image_urls = fs::read_dir(images)
        .unwrap()
        .into_iter()
        .map(|path| path.unwrap().path().to_str().unwrap().to_owned())
        .collect::<Vec<_>>();

    let data = btreemap! {
        "image_urls" => image_urls,
    };
    res.set_mut(Template::new("about", data)).set_mut(status::Ok);
    Ok(res)
}

fn index(_: &mut Request) -> IronResult<Response> {
    let mut res = Response::new();

    let connection = persistence::get_db_connection();
    let posts = blog::get_summaries(&connection);

    let data = btreemap!{
        "posts" => posts.unwrap().into_iter().take(NUM_SUMMARIES).collect::<Vec<_>>(),
    };
    res.set_mut(Template::new("index", data)).set_mut(status::Ok);
    Ok(res)
}

/// Returns the router for the server.
fn get_router() -> Router {
    router!(
        get "/" => index,
        get "/about" => about,
        get "/blog" => blog,
        get "/blog/:year/:month/:day/:title" => blog_post,
        get "/projects" => projects,
        get "/resume" => resume,

        get "/favicon.ico" => Static::new(Path::new("static/images")),
        get "/robots.txt" => Static::new(Path::new("static")),
    )
}

fn initialize_templates(folder: &str,
                        extension: &str)
                        -> Result<HandlebarsEngine, hbs::SourceError> {
    let mut hbse = HandlebarsEngine::new();
    hbse.add(Box::new(DirectorySource::new(folder, extension)));
    try!(hbse.reload());

    {
        let mut reg = hbse.registry.write().unwrap();
        reg.register_helper("join", Box::new(helpers::join));
    }

    Ok(hbse)
}

fn mount(chain: Chain) -> Mount {
    let mut mount = Mount::new();
    mount.mount("/", chain);
    mount.mount("/static",
                Static::new(Path::new(env!("OUT_DIR")).join("static")));
    mount
}

/// Returns the main route handler for the website.
///
/// This handler contains a number of middleware:
///   - Routing for all page requests
///   - Routing for static files
///   - Persistence for the website configuration
///   - Rendering handlebars templates
///   - Error reporting
///   - Error handling
pub fn handler(config: config::Config, projects: Vec<Project>) -> Box<Handler> {
    let mut chain = Chain::new(get_router());

    chain.link_before(persistent::Read::<Config>::one(config));
    chain.link_before(persistent::Read::<Projects>::one(projects));

    chain.link_after(ErrorHandler);
    chain.link_after(initialize_templates("./templates/", ".hbs").unwrap());
    chain.link_after(ErrorReporter);

    let mount = mount(chain);
    Box::new(mount)
}

struct ErrorReporter;

impl AfterMiddleware for ErrorReporter {
    fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
        error!("{}", err.description());
        Err(err)
    }
}

struct ErrorHandler;

impl AfterMiddleware for ErrorHandler {
    fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
        if let Some(_) = err.error.downcast::<NoRoute>() {
            Ok(Response::with((status::NotFound, Template::new("not_found", ()))))
        } else {
            Err(err)
        }
    }
}

#[cfg(test)]
mod tests {
    extern crate iron;
    extern crate iron_test;
    extern crate url;

    use std::fs::File;
    use std::io::prelude::*;

    use self::iron::{Handler, Headers};
    use self::iron_test::{request, response};

    use persistence;

    fn create_handler() -> Box<Handler> {
        use self::url::Url;

        use config::Config;

        super::handler(Config { resume_link: Url::parse("http://google.com").unwrap() },
                       vec![])
    }

    #[test]
    fn index() {
        // Create the schema.
        //
        // FIXME: This should be handled by a server object, to avoid duplication between code and
        // tests..
        let connection = persistence::get_db_connection();
        let schema = {
            let mut schema_file = File::open("schema.sql").unwrap();
            let mut schema = String::new();
            schema_file.read_to_string(&mut schema).unwrap();
            schema
        };

        connection.execute_batch(&schema).unwrap();

        let handler = create_handler();
        let response = request::get("http://localhost:3000/", Headers::new(), &handler).unwrap();
        assert!(response.status.unwrap().is_success());
    }

    #[test]
    fn static_files() {
        let handler = create_handler();
        let response = request::get("http://localhost:3000/favicon.ico",
                                    Headers::new(),
                                    &handler)
            .unwrap();
        assert!(response.status.unwrap().is_success());

        let response = request::get("http://localhost:3000/robots.txt", Headers::new(), &handler)
            .unwrap();
        assert!(response.status.unwrap().is_success());
    }

    #[test]
    fn not_found() {
        let handler = create_handler();
        let response = request::get("http://localhost:3000/this/path/does/not/exist",
                                    Headers::new(),
                                    &handler)
            .unwrap();
        let body = response::extract_body_to_string(response);
        assert!(body.contains("Page Not Found"));
    }

    #[test]
    fn css() {
        let handler = create_handler();
        let response = request::get("http://localhost:3000/static/css/styles.css",
                                    Headers::new(),
                                    &handler)
            .unwrap();
        assert!(response.status.unwrap().is_success());
    }
}
<commit_msg>fix(blog): return 404 on invalid post dates<commit_after>//! Contains the routing logic for the website.

use std::error::Error;
use std::fs;
use std::path::Path;

use chrono::NaiveDate;
use mount::Mount;
use hbs::{self, DirectorySource, HandlebarsEngine, Template};
use iron::prelude::*;
use iron::{status, AfterMiddleware, Handler};
use persistent::{self, Read};
use router::{Router, NoRoute};
use staticfile::Static;

use blog;
use config;
use helpers;
use projects::Project;
use persistence::{self, Config, Projects};

/// The number of blog post summaries that should be displayed.
const NUM_SUMMARIES: usize = 3;

fn resume(req: &mut Request) -> IronResult<Response> {
    let mut res = Response::new();

    let data = btreemap!{
        "resume_link" => req.get::<Read<Config>>().unwrap().resume_link.to_owned(),
    };
    res.set_mut(Template::new("resume", data)).set_mut(status::Ok);
    Ok(res)
}

fn projects(req: &mut Request) -> IronResult<Response> {
    let mut res = Response::new();

    let projects = req.get::<Read<Projects>>().unwrap();
    let data = btreemap!{
        "projects" => projects,
    };
    res.set_mut(Template::new("projects", data)).set_mut(status::Ok);
    Ok(res)
}

fn blog_post(req: &mut Request) -> IronResult<Response> {
    let params = req.extensions.get::<Router>().unwrap();

    let year = params.find("year").and_then(|y| y.parse().ok());
    let month = params.find("month").and_then(|m| m.parse().ok());
    let day = params.find("day").and_then(|d| d.parse().ok());
    let title = try!(req.extensions
        .get::<Router>()
        .unwrap()
        .find("title")
        .ok_or(IronError::new(NoRoute, status::NotFound)));

    let date = match (year, month, day) {
        (Some(year), Some(month), Some(day)) => NaiveDate::from_ymd_opt(year, month, day),
        _ => None,
    };

    let date = try!(date.ok_or(IronError::new(NoRoute, status::NotFound)));

    match blog::get_post(&date, title) {
        Ok(ref post) => Ok(Response::with((status::Ok, Template::new("blog_post", post)))),
        Err(err) => {
            error!("Error retrieving blog post: {}", err);
            Err(IronError::new(NoRoute, status::NotFound))
        }
    }
}

fn blog(_: &mut Request) -> IronResult<Response> {
    let mut res = Response::new();

    let connection = persistence::get_db_connection();
    let data = btreemap!{
        "posts" => blog::get_summaries(&connection).unwrap(),
    };
    res.set_mut(Template::new("blog", data)).set_mut(status::Ok);
    Ok(res)
}

fn about(_: &mut Request) -> IronResult<Response> {
    let mut res = Response::new();

    let images = Path::new("static/images/slideshow");
    let image_urls = fs::read_dir(images)
        .unwrap()
        .into_iter()
        .map(|path| path.unwrap().path().to_str().unwrap().to_owned())
        .collect::<Vec<_>>();

    let data = btreemap! {
        "image_urls" => image_urls,
    };
    res.set_mut(Template::new("about", data)).set_mut(status::Ok);
    Ok(res)
}

fn index(_: &mut Request) -> IronResult<Response> {
    let mut res = Response::new();

    let connection = persistence::get_db_connection();
    let posts = blog::get_summaries(&connection);

    let data = btreemap!{
        "posts" => posts.unwrap().into_iter().take(NUM_SUMMARIES).collect::<Vec<_>>(),
    };
    res.set_mut(Template::new("index", data)).set_mut(status::Ok);
    Ok(res)
}

/// Returns the router for the server.
fn get_router() -> Router {
    router!(
        get "/" => index,
        get "/about" => about,
        get "/blog" => blog,
        get "/blog/:year/:month/:day/:title" => blog_post,
        get "/projects" => projects,
        get "/resume" => resume,

        get "/favicon.ico" => Static::new(Path::new("static/images")),
        get "/robots.txt" => Static::new(Path::new("static")),
    )
}

fn initialize_templates(folder: &str,
                        extension: &str)
                        -> Result<HandlebarsEngine, hbs::SourceError> {
    let mut hbse = HandlebarsEngine::new();
    hbse.add(Box::new(DirectorySource::new(folder, extension)));
    try!(hbse.reload());

    {
        let mut reg = hbse.registry.write().unwrap();
        reg.register_helper("join", Box::new(helpers::join));
    }

    Ok(hbse)
}

fn mount(chain: Chain) -> Mount {
    let mut mount = Mount::new();
    mount.mount("/", chain);
    mount.mount("/static",
                Static::new(Path::new(env!("OUT_DIR")).join("static")));
    mount
}

/// Returns the main route handler for the website.
///
/// This handler contains a number of middleware:
///   - Routing for all page requests
///   - Routing for static files
///   - Persistence for the website configuration
///   - Rendering handlebars templates
///   - Error reporting
///   - Error handling
pub fn handler(config: config::Config, projects: Vec<Project>) -> Box<Handler> {
    let mut chain = Chain::new(get_router());

    chain.link_before(persistent::Read::<Config>::one(config));
    chain.link_before(persistent::Read::<Projects>::one(projects));

    chain.link_after(ErrorHandler);
    chain.link_after(initialize_templates("./templates/", ".hbs").unwrap());
    chain.link_after(ErrorReporter);

    let mount = mount(chain);
    Box::new(mount)
}

struct ErrorReporter;

impl AfterMiddleware for ErrorReporter {
    fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
        error!("{}", err.description());
        Err(err)
    }
}

struct ErrorHandler;

impl AfterMiddleware for ErrorHandler {
    fn catch(&self, _: &mut Request, err: IronError) -> IronResult<Response> {
        if let Some(_) = err.error.downcast::<NoRoute>() {
            Ok(Response::with((status::NotFound, Template::new("not_found", ()))))
        } else {
            Err(err)
        }
    }
}

#[cfg(test)]
mod tests {
    extern crate iron;
    extern crate iron_test;
    extern crate url;

    use std::fs::File;
    use std::io::prelude::*;

    use self::iron::{Handler, Headers};
    use self::iron_test::{request, response};

    use persistence;

    fn create_handler() -> Box<Handler> {
        use self::url::Url;

        use config::Config;

        super::handler(Config { resume_link: Url::parse("http://google.com").unwrap() },
                       vec![])
    }

    #[test]
    fn index() {
        // Create the schema.
        //
        // FIXME: This should be handled by a server object, to avoid duplication between code and
        // tests..
        let connection = persistence::get_db_connection();
        let schema = {
            let mut schema_file = File::open("schema.sql").unwrap();
            let mut schema = String::new();
            schema_file.read_to_string(&mut schema).unwrap();
            schema
        };

        connection.execute_batch(&schema).unwrap();

        let handler = create_handler();
        let response = request::get("http://localhost:3000/", Headers::new(), &handler).unwrap();
        assert!(response.status.unwrap().is_success());
    }

    #[test]
    fn post_dates() {
        let handler = create_handler();

        let response = request::get("http://localhost:3000/blog/2016/13/31/invalid-date",
                                    Headers::new(),
                                    &handler)
            .unwrap();
        assert!(response.status.unwrap().is_client_error());
    }

    #[test]
    fn static_files() {
        let handler = create_handler();
        let response = request::get("http://localhost:3000/favicon.ico",
                                    Headers::new(),
                                    &handler)
            .unwrap();
        assert!(response.status.unwrap().is_success());

        let response = request::get("http://localhost:3000/robots.txt", Headers::new(), &handler)
            .unwrap();
        assert!(response.status.unwrap().is_success());
    }

    #[test]
    fn not_found() {
        let handler = create_handler();
        let response = request::get("http://localhost:3000/this/path/does/not/exist",
                                    Headers::new(),
                                    &handler)
            .unwrap();
        let body = response::extract_body_to_string(response);
        assert!(body.contains("Page Not Found"));
    }

    #[test]
    fn css() {
        let handler = create_handler();
        let response = request::get("http://localhost:3000/static/css/styles.css",
                                    Headers::new(),
                                    &handler)
            .unwrap();
        assert!(response.status.unwrap().is_success());
    }
}
<|endoftext|>


<commit_before>/*
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::sync::Arc;

use slog::{info, o};
use tokio::{signal, sync::watch};

use crate::{
    config::Config,
    filters::{DynFilterFactory, FilterRegistry, FilterSet},
    proxy::Builder,
};

#[cfg(doc)]
use crate::filters::FilterFactory;

pub type Error = Box<dyn std::error::Error>;

/// Calls [`run`] with the [`Config`] found by [`Config::find`] and the
/// default [`FilterSet`].
pub async fn run(
    filter_factories: impl IntoIterator<Item = DynFilterFactory>,
) -> Result<(), Error> {
    let log = crate::proxy::logger();
    run_with_config(
        log.clone(),
        Config::find(&log, None).map(Arc::new)?,
        filter_factories,
    )
    .await
}

/// Start and run a proxy. Any passed in [`FilterFactory`]s are included
/// alongside the default filter factories.
pub async fn run_with_config(
    base_log: slog::Logger,
    config: Arc<Config>,
    filter_factories: impl IntoIterator<Item = DynFilterFactory>,
) -> Result<(), Error> {
    let log = base_log.new(o!("source" => "run"));
    let server = Builder::from(config)
        .with_log(base_log)
        .with_filter_registry(FilterRegistry::new(FilterSet::default_with(
            &log,
            filter_factories.into_iter(),
        )))
        .validate()?
        .build();

    let (shutdown_tx, shutdown_rx) = watch::channel::<()>(());
    tokio::spawn(async move {
        // Don't unwrap in order to ensure that we execute
        // any subsequent shutdown tasks.
        signal::ctrl_c().await.ok();
        shutdown_tx.send(()).ok();
    });

    if let Err(err) = server.run(shutdown_rx).await {
        info!(log, "Shutting down with error"; "error" => %err);
        Err(Error::from(err))
    } else {
        info!(log, "Shutting down");
        Ok(())
    }
}
<commit_msg>Exit on SIGTERM (#393)<commit_after>/*
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

use std::sync::Arc;

use slog::{debug, info, o};
use tokio::{signal, sync::watch};

use crate::{
    config::Config,
    filters::{DynFilterFactory, FilterRegistry, FilterSet},
    proxy::Builder,
};

#[cfg(doc)]
use crate::filters::FilterFactory;

pub type Error = Box<dyn std::error::Error>;

/// Calls [`run`] with the [`Config`] found by [`Config::find`] and the
/// default [`FilterSet`].
pub async fn run(
    filter_factories: impl IntoIterator<Item = DynFilterFactory>,
) -> Result<(), Error> {
    let log = crate::proxy::logger();
    run_with_config(
        log.clone(),
        Config::find(&log, None).map(Arc::new)?,
        filter_factories,
    )
    .await
}

/// Start and run a proxy. Any passed in [`FilterFactory`]s are included
/// alongside the default filter factories.
pub async fn run_with_config(
    base_log: slog::Logger,
    config: Arc<Config>,
    filter_factories: impl IntoIterator<Item = DynFilterFactory>,
) -> Result<(), Error> {
    let log = base_log.new(o!("source" => "run"));
    let server = Builder::from(config)
        .with_log(base_log)
        .with_filter_registry(FilterRegistry::new(FilterSet::default_with(
            &log,
            filter_factories.into_iter(),
        )))
        .validate()?
        .build();

    #[cfg(target_os = "linux")]
    let mut sig_term_fut = signal::unix::signal(signal::unix::SignalKind::terminate())?;

    let (shutdown_tx, shutdown_rx) = watch::channel::<()>(());
    let signal_log = log.clone();
    tokio::spawn(async move {
        #[cfg(target_os = "linux")]
        let sig_term = sig_term_fut.recv();
        #[cfg(not(target_os = "linux"))]
        let sig_term = std::future::pending();

        tokio::select! {
            _ = signal::ctrl_c() => {
                debug!(signal_log, "Received SIGINT")
            }
            _ = sig_term => {
                debug!(signal_log, "Received SIGTERM")
            }
        }

        info!(signal_log, "Shutting down");
        // Don't unwrap in order to ensure that we execute
        // any subsequent shutdown tasks.
        shutdown_tx.send(()).ok();
    });

    if let Err(err) = server.run(shutdown_rx).await {
        info!(log, "Shutting down with error"; "error" => %err);
        Err(Error::from(err))
    } else {
        Ok(())
    }
}
<|endoftext|>


<commit_before>use argvalues::ArgValues;

use loader;

use gfx;
use glutin;
use gfx_window_glutin;

use gfx::traits::FactoryExt;
use gfx::Device;

use std::time::Instant;

type ColorFormat = gfx::format::Rgba8;
type DepthFormat = gfx::format::DepthStencil;

#[derive(PartialEq)]
enum Mouse {
    Released,
    Pressed,
}

gfx_defines! {
    vertex Vertex {
        pos: [f32; 2] = "position",
    }

    pipeline pipe {
        // Vertex buffer
        vbuf: gfx::VertexBuffer<Vertex> = (),

        // Uniforms
        i_global_time: gfx::Global<f32> = "iGlobalTime",
        i_resolution: gfx::Global<[f32; 3]> = "iResolution",
        i_mouse: gfx::Global<[f32; 4]> = "iMouse",
        i_frame: gfx::Global<i32> = "iFrame",

        // Output color
        frag_color: gfx::RenderTarget<ColorFormat> = "fragColor",
    }
}

const SCREEN: [Vertex; 4] = [
    Vertex{pos: [ 1.0,  1.0]}, // Top right
    Vertex{pos: [-1.0,  1.0]}, // Top left
    Vertex{pos: [-1.0, -1.0]}, // Bottom left
    Vertex{pos: [ 1.0, -1.0]}, // Bottom right
];

const SCREEN_INDICES: [u16; 6] = [
    0, 1, 2,
    0, 2, 3,
];

const CLEAR_COLOR: [f32; 4] = [1.0; 4];

pub fn run(av: &ArgValues) -> Result<(), String> {
    let ArgValues{mut width, mut height, ..} = *av;

    let (vert_src_res, frag_src_res) = loader::load_shaders(&av);

    let (vert_src_buf, frag_src_buf): (Vec<u8>, Vec<u8>);
    match (vert_src_res, frag_src_res) {
        (Ok(vsbuf), Ok(fsbuf)) => {
            vert_src_buf = vsbuf;
            frag_src_buf = fsbuf;
        },

        (Err(vse), _) => return Err(format!("Error reading vertex shader: {}", vse)),
        (_, Err(fse)) => return Err(format!("Error reading fragment shader: {}", fse)),
    }
    let (vert_src_buf, frag_src_buf) = (vert_src_buf.as_slice(), frag_src_buf.as_slice());

    let builder = glutin::WindowBuilder::new()
        .with_title("shadertoy-rs")
        .with_dimensions(width as u32, height as u32)
        .with_vsync();

    let (window, mut device, mut factory, main_color, mut main_depth) =
        gfx_window_glutin::init::<ColorFormat, DepthFormat>(builder);

    let mut encoder: gfx::Encoder<_,_> = factory.create_command_buffer().into();

    let pipeline;
    match factory.create_pipeline_simple(vert_src_buf, frag_src_buf, pipe::new()) {
        Ok(p)  => pipeline = p,
        Err(e) => return Err(format!("Error creating pipeline: {}", e)),
    }

    let (vertex_buffer, slice) = factory.create_vertex_buffer_with_slice(&SCREEN, &SCREEN_INDICES[..]);

    let mut data = pipe::Data {
        vbuf: vertex_buffer,

        i_global_time: 0.0,
        i_resolution: [width, height, width/height],
        i_mouse: [0.0; 4],
        i_frame: -1,

        frag_color: main_color,
    };

    let mut last_mouse = Mouse::Released;
    let mut current_mouse = Mouse::Released;

    let (mut mx, mut my) = (0.0, 0.0);

    let mut xyzw = [0.0; 4];

    let start_time = Instant::now();

    loop {
        for event in window.poll_events() {
            match event {
                glutin::Event::KeyboardInput(_, _, Some(glutin::VirtualKeyCode::Escape)) |
                    glutin::Event::Closed => {
                        return Ok(());
                    },

                    glutin::Event::Resized(new_width, new_height) => {
                        gfx_window_glutin::update_views(&window, &mut data.frag_color, &mut main_depth);

                        width = new_width as f32;
                        height = new_height as f32;
                    }

                glutin::Event::MouseMoved(x, y) => {
                    mx = x as f32;
                    my = height - y as f32; // Flip y-axis
                },

                glutin::Event::MouseInput(state, button) => {
                    last_mouse = current_mouse;
                    if state == glutin::ElementState::Pressed && button == glutin::MouseButton::Left {
                        current_mouse = Mouse::Pressed;
                    } else {
                        current_mouse = Mouse::Released;
                    }
                }

                _ => ()
            }
        }

        // Mouse
        if current_mouse == Mouse::Pressed {
            xyzw[0] = mx;
            xyzw[1] = my;
            if last_mouse == Mouse::Released {
                xyzw[2] = mx;
                xyzw[3] = my;
            }
        } else {
            xyzw[2] = 0.0;
            xyzw[3] = 0.0;
        }
        data.i_mouse = xyzw;

        // Elapsed time
        let elapsed = start_time.elapsed();
        let elapsed_ms = (elapsed.as_secs() * 1000) + (elapsed.subsec_nanos()/1000000) as u64;
        let elapsed_sec = (elapsed_ms as f32) / 1000.0;
        data.i_global_time = elapsed_sec;

        // Resolution
        data.i_resolution = [width, height, width/height];

        // Frame
        data.i_frame += 1;

        // Draw
        encoder.clear(&data.frag_color, CLEAR_COLOR);
        encoder.draw(&slice, &pipeline, &data);
        encoder.flush(&mut device);
        window.swap_buffers().unwrap();
        device.cleanup();
    }
}
<commit_msg>Setup for F5 reload<commit_after>use argvalues::ArgValues;

use loader;

use gfx;
use glutin;
use gfx_window_glutin;

use gfx::traits::FactoryExt;
use gfx::Device;

use glutin::{VirtualKeyCode, ElementState, MouseButton, Event};

use std::time::Instant;

type ColorFormat = gfx::format::Rgba8;
type DepthFormat = gfx::format::DepthStencil;

#[derive(PartialEq)]
enum Mouse {
    Released,
    Pressed,
}

gfx_defines! {
    vertex Vertex {
        pos: [f32; 2] = "position",
    }

    pipeline pipe {
        // Vertex buffer
        vbuf: gfx::VertexBuffer<Vertex> = (),

        // Uniforms
        i_global_time: gfx::Global<f32> = "iGlobalTime",
        i_resolution: gfx::Global<[f32; 3]> = "iResolution",
        i_mouse: gfx::Global<[f32; 4]> = "iMouse",
        i_frame: gfx::Global<i32> = "iFrame",

        // Output color
        frag_color: gfx::RenderTarget<ColorFormat> = "fragColor",
    }
}

const SCREEN: [Vertex; 4] = [
    Vertex{pos: [ 1.0,  1.0]}, // Top right
    Vertex{pos: [-1.0,  1.0]}, // Top left
    Vertex{pos: [-1.0, -1.0]}, // Bottom left
    Vertex{pos: [ 1.0, -1.0]}, // Bottom right
];

const SCREEN_INDICES: [u16; 6] = [
    0, 1, 2,
    0, 2, 3,
];

const CLEAR_COLOR: [f32; 4] = [1.0; 4];

pub fn run(av: &ArgValues) -> Result<(), String> {
    let ArgValues{mut width, mut height, ..} = *av;

    // Load vertex and fragment shaders into byte buffers
    let (vert_src_res, frag_src_res) = loader::load_shaders(&av);
    let (vert_src_buf, frag_src_buf): (Vec<u8>, Vec<u8>);
    match (vert_src_res, frag_src_res) {
        (Ok(vsbuf), Ok(fsbuf)) => {
            vert_src_buf = vsbuf;
            frag_src_buf = fsbuf;
        },

        (Err(vse), _) => return Err(format!("Error reading vertex shader: {}", vse)),
        (_, Err(fse)) => return Err(format!("Error reading fragment shader: {}", fse)),
    }
    let (vert_src_buf, frag_src_buf) = (vert_src_buf.as_slice(), frag_src_buf.as_slice());

    let builder = glutin::WindowBuilder::new()
        .with_title("shadertoy-rs")
        .with_dimensions(width as u32, height as u32)
        .with_vsync();

    let (window, mut device, mut factory, main_color, mut main_depth) =
        gfx_window_glutin::init::<ColorFormat, DepthFormat>(builder);

    let mut encoder: gfx::Encoder<_,_> = factory.create_command_buffer().into();

    let pipeline;
    match factory.create_pipeline_simple(vert_src_buf, frag_src_buf, pipe::new()) {
        Ok(p)  => pipeline = p,
        Err(e) => return Err(format!("Error creating pipeline: {}", e)),
    }

    let (vertex_buffer, slice) = factory.create_vertex_buffer_with_slice(&SCREEN, &SCREEN_INDICES[..]);

    let mut data = pipe::Data {
        vbuf: vertex_buffer,

        i_global_time: 0.0,
        i_resolution: [width, height, width/height],
        i_mouse: [0.0; 4],
        i_frame: -1,

        frag_color: main_color,
    };

    let mut last_mouse = Mouse::Released;
    let mut current_mouse = Mouse::Released;

    let (mut mx, mut my) = (0.0, 0.0);

    let mut xyzw = [0.0; 4];

    let start_time = Instant::now();

    loop {
        for event in window.poll_events() {
            match event {
                Event::KeyboardInput(_, _, Some(VirtualKeyCode::Escape)) | Event::Closed => {
                    return Ok(());
                },

                Event::KeyboardInput(ElementState::Pressed, _, Some(VirtualKeyCode::F5)) => {
                    println!("Reload!");
                },

                Event::Resized(new_width, new_height) => {
                    gfx_window_glutin::update_views(&window, &mut data.frag_color, &mut main_depth);

                    width = new_width as f32;
                    height = new_height as f32;
                },

                Event::MouseMoved(x, y) => {
                    mx = x as f32;
                    my = height - y as f32; // Flip y-axis
                },

                Event::MouseInput(state, button) => {
                    last_mouse = current_mouse;
                    if state == ElementState::Pressed && button == MouseButton::Left {
                        current_mouse = Mouse::Pressed;
                    } else {
                        current_mouse = Mouse::Released;
                    }
                },

                _ => ()
            }
        }

        // Mouse
        if current_mouse == Mouse::Pressed {
            xyzw[0] = mx;
            xyzw[1] = my;
            if last_mouse == Mouse::Released {
                xyzw[2] = mx;
                xyzw[3] = my;
            }
        } else {
            xyzw[2] = 0.0;
            xyzw[3] = 0.0;
        }
        data.i_mouse = xyzw;

        // Elapsed time
        let elapsed = start_time.elapsed();
        let elapsed_ms = (elapsed.as_secs() * 1000) + (elapsed.subsec_nanos()/1000000) as u64;
        let elapsed_sec = (elapsed_ms as f32) / 1000.0;
        data.i_global_time = elapsed_sec;

        // Resolution
        data.i_resolution = [width, height, width/height];

        // Frame
        data.i_frame += 1;

        // Draw
        encoder.clear(&data.frag_color, CLEAR_COLOR);
        encoder.draw(&slice, &pipeline, &data);
        encoder.flush(&mut device);
        window.swap_buffers().unwrap();
        device.cleanup();
    }
}
<|endoftext|>


<commit_before>use std::env::Args;
use std::net::{TcpListener, TcpStream};
use std::io::{Write, Read};
use std::thread;
use std::str;
use signal::{Signal, string_to_signal};

fn handle_signal(sig_str: &str) {
    match string_to_signal(sig_str) {
        Some(Signal::Reload)    => println!("reload"),
        None                    => println!("unknown signal"),
    }
}

fn handle_client(stream: &mut TcpStream) {
    let mut buff = vec![0u8; 1024];
    let rcount = stream.read(&mut buff).unwrap();
    handle_signal(str::from_utf8(&buff[0..rcount]).unwrap());
}

fn server() {
    let listener = TcpListener::bind("127.0.0.1:9630").unwrap();
    for stream in listener.incoming() {
        match stream {
            Ok(mut st) => {
                thread::spawn(move||{
                    handle_client(&mut st);
                });
            },
            Err(e) => {
                println!("Error: {}", e);
            },
        }
    }

    drop(listener);
}

pub fn start(args: &mut Args) {
    println!("start server ...");
    server();
}<commit_msg>Update sas daemon prompt<commit_after>use std::env::Args;
use std::net::{TcpListener, TcpStream};
use std::io::{Write, Read};
use std::thread;
use std::str;
use signal::{Signal, string_to_signal};

fn handle_signal(sig_str: &str) {
    match string_to_signal(sig_str) {
        Some(Signal::Reload)    => println!("reload"),
        None                    => println!("unknown signal"),
    }
}

fn handle_client(stream: &mut TcpStream) {
    let mut buff = vec![0u8; 1024];
    let rcount = stream.read(&mut buff).unwrap();
    handle_signal(str::from_utf8(&buff[0..rcount]).unwrap());
}

fn server() {
    let listener = TcpListener::bind("127.0.0.1:9630").unwrap();
    for stream in listener.incoming() {
        match stream {
            Ok(mut st) => {
                thread::spawn(move||{
                    handle_client(&mut st);
                });
            },
            Err(e) => {
                println!("Error: {}", e);
            },
        }
    }

    drop(listener);
}

pub fn start(args: &mut Args) {
    println!("Starting sas daemon ...");
    server();
}<|endoftext|>