<commit_before>use std::clone::Clone;
use std::collections::HashMap;
use std::default::Default;
use std::path::{Path, PathBuf};

use chrono::{Datelike, Timelike};
use failure::ResultExt;
use itertools;
use jsonfeed;
use liquid;
use liquid::model::Value;
use liquid::Object;
use liquid::ValueView;
use regex::Regex;
use rss;

use crate::cobalt_model;
use crate::cobalt_model::files;
use crate::cobalt_model::permalink;
use crate::cobalt_model::slug;
use crate::cobalt_model::Minify;
use crate::error::*;

pub struct RenderContex<'a> {
    pub parser: &'a cobalt_model::Liquid,
    pub markdown: &'a cobalt_model::Markdown,
    pub globals: &'a Object,
    pub minify: Minify,
}

#[cfg(not(feature = "html-minifier"))]
fn minify_if_enabled(html: String, _context: &RenderContex, _file_path: &Path) -> Result<String> {
    Ok(html)
}

#[cfg(feature = "html-minifier")]
fn minify_if_enabled(html: String, context: &RenderContex, file_path: &Path) -> Result<String> {
    let extension = file_path.extension().unwrap_or_else(|| Default::default());
    if context.minify.html && (extension == "html" || extension == "htm") {
        Ok(html_minifier::minify(html)?)
    } else {
        Ok(html)
    }
}

/// Convert the source file's relative path into a format useful for generating permalinks that
/// mirror the source directory hierarchy.
fn format_path_variable(source_file: &Path) -> String {
    let parent = source_file
        .parent()
        .and_then(|p| p.to_str())
        .unwrap_or("")
        .to_owned();
    let mut path = parent.replace("\\", "/");
    if path.starts_with("./") {
        path.remove(0);
    }
    if path.starts_with('/') {
        path.remove(0);
    }
    path
}

pub fn permalink_attributes(front: &cobalt_model::Frontmatter, dest_file: &Path) -> Object {
    let mut attributes = Object::new();

    attributes.insert(
        "parent".into(),
        Value::scalar(format_path_variable(dest_file)),
    );

    let filename = dest_file
        .file_stem()
        .and_then(|s| s.to_str())
        .unwrap_or("")
        .to_owned();
    attributes.insert("name".into(), Value::scalar(filename));

    attributes.insert("ext".into(), Value::scalar(".html"));

    // TODO(epage): Add `collection` (the collection's slug), see #257
    // or `parent.slug`, see #323

    attributes.insert("slug".into(), Value::scalar(front.slug.clone()));

    attributes.insert(
        "categories".into(),
        Value::scalar(itertools::join(
            front.categories.iter().map(slug::slugify),
            "/",
        )),
    );

    if let Some(ref date) = front.published_date {
        attributes.insert("year".into(), Value::scalar(date.year().to_string()));
        attributes.insert(
            "month".into(),
            Value::scalar(format!("{:02}", &date.month())),
        );
        attributes.insert("i_month".into(), Value::scalar(date.month().to_string()));
        attributes.insert("day".into(), Value::scalar(format!("{:02}", &date.day())));
        attributes.insert("i_day".into(), Value::scalar(date.day().to_string()));
        attributes.insert("hour".into(), Value::scalar(format!("{:02}", &date.hour())));
        attributes.insert(
            "minute".into(),
            Value::scalar(format!("{:02}", &date.minute())),
        );
        attributes.insert(
            "second".into(),
            Value::scalar(format!("{:02}", &date.second())),
        );
    }

    attributes.insert("data".into(), Value::Object(front.data.clone()));

    attributes
}

fn document_attributes(
    front: &cobalt_model::Frontmatter,
    source_file: &Path,
    url_path: &str,
) -> Object {
    let categories = Value::Array(
        front
            .categories
            .iter()
            .cloned()
            .map(Value::scalar)
            .collect(),
    );
    // Reason for `file`:
    // - Allow access to assets in the original location
    // - Ease linking back to page's source
    let file: Object = vec![
        (
            "permalink".into(),
            Value::scalar(source_file.to_str().unwrap_or("").to_owned()),
        ),
        (
            "parent".into(),
            Value::scalar(
                source_file
                    .parent()
                    .and_then(Path::to_str)
                    .unwrap_or("")
                    .to_owned(),
            ),
        ),
    ]
    .into_iter()
    .collect();
    let attributes = vec![
        ("permalink".into(), Value::scalar(url_path.to_owned())),
        ("title".into(), Value::scalar(front.title.clone())),
        ("slug".into(), Value::scalar(front.slug.clone())),
        (
            "description".into(),
            Value::scalar(front.description.as_deref().unwrap_or("").to_owned()),
        ),
        ("categories".into(), categories),
        ("is_draft".into(), Value::scalar(front.is_draft)),
        ("weight".into(), Value::scalar(front.weight)),
        ("file".into(), Value::Object(file)),
        ("collection".into(), Value::scalar(front.collection.clone())),
        ("data".into(), Value::Object(front.data.clone())),
    ];
    let mut attributes: Object = attributes.into_iter().collect();

    if let Some(ref tags) = front.tags {
        let tags = Value::Array(tags.iter().cloned().map(Value::scalar).collect());
        attributes.insert("tags".into(), tags);
    }

    if let Some(ref published_date) = front.published_date {
        attributes.insert(
            "published_date".into(),
            Value::scalar(liquid::model::DateTime::from(*published_date)),
        );
    }

    attributes
}

#[derive(Debug, Clone)]
pub struct Document {
    pub url_path: String,
    pub file_path: PathBuf,
    pub content: String,
    pub attributes: Object,
    pub front: cobalt_model::Frontmatter,
}

impl Document {
    pub fn new(
        url_path: String,
        file_path: PathBuf,
        content: String,
        attributes: Object,
        front: cobalt_model::Frontmatter,
    ) -> Document {
        Document {
            url_path,
            file_path,
            content,
            attributes,
            front,
        }
    }

    pub fn parse(
        src_path: &Path,
        rel_path: &Path,
        default_front: cobalt_model::FrontmatterBuilder,
    ) -> Result<Document> {
        trace!("Parsing {:?}", rel_path);
        let content = files::read_file(src_path)?;
        let builder =
            cobalt_model::DocumentBuilder::<cobalt_model::FrontmatterBuilder>::parse(&content)?;
        let (front, content) = builder.parts();
        let front = front.merge_path(rel_path).merge(default_front);

        let front = front.build()?;

        let (file_path, url_path) = {
            let perma_attributes = permalink_attributes(&front, rel_path);
            let url_path = permalink::explode_permalink(&front.permalink, &perma_attributes)
                .with_context(|_| {
                    failure::format_err!("Failed to create permalink `{}`", front.permalink)
                })?;
            let file_path = permalink::format_url_as_file(&url_path);
            (file_path, url_path)
        };

        let doc_attributes = document_attributes(&front, rel_path, url_path.as_ref());

        Ok(Document::new(
            url_path,
            file_path,
            content,
            doc_attributes,
            front,
        ))
    }

    /// Metadata for generating RSS feeds
    pub fn to_rss(&self, root_url: &str) -> Result<rss::Item> {
        let link = format!("{}/{}", root_url, &self.url_path);
        let guid = rss::GuidBuilder::default()
            .value(link.clone())
            .permalink(true)
            .build()
            .map_err(failure::err_msg)?;

        let item = rss::ItemBuilder::default()
            .title(Some(self.front.title.clone()))
            .link(Some(link))
            .guid(Some(guid))
            .pub_date(self.front.published_date.map(|date| date.to_rfc2822()))
            .description(self.description_to_str())
            .build()
            .map_err(failure::err_msg)?;
        Ok(item)
    }

    /// Metadata for generating JSON feeds
    pub fn to_jsonfeed(&self, root_url: &str) -> jsonfeed::Item {
        let link = format!("{}/{}", root_url, &self.url_path);

        jsonfeed::Item {
            id: link.clone(),
            url: Some(link),
            title: Some(self.front.title.clone()),
            content: jsonfeed::Content::Html(
                self.description_to_str().unwrap_or_else(|| "".into()),
            ),
            date_published: self.front.published_date.map(|date| date.to_rfc2822()),
            // TODO completely implement categories, see Issue 131
            tags: Some(self.front.categories.clone()),
            ..Default::default()
        }
    }

    pub fn to_sitemap<T: std::io::Write>(
        &self,
        root_url: &str,
        writer: &mut sitemap::writer::UrlSetWriter<T>,
    ) -> Result<()> {
        let link = format!("{}/{}", root_url, &self.url_path);
        let mut url = sitemap::structs::UrlEntry::builder();
        url = url.loc(link);
        if let Some(date) = self.front.published_date {
            url = url.lastmod(*date);
        }
        writer.url(url)?;
        Ok(())
    }

    fn description_to_str(&self) -> Option<String> {
        self.front
            .description
            .clone()
            .or_else(|| {
                self.attributes.get("excerpt").and_then(|excerpt| {
                    if excerpt.is_nil() {
                        None
                    } else {
                        Some(excerpt.render().to_string())
                    }
                })
            })
            .or_else(|| {
                self.attributes
                    .get("content")
                    .map(|s| s.render().to_string())
            })
    }

    /// Renders liquid templates into HTML in the context of current document.
    ///
    /// Takes `content` string and returns rendered HTML. This function doesn't
    /// take `"extends"` attribute into account. This function can be used for
    /// rendering content or excerpt.
    fn render_html(&self, content: &str, context: &RenderContex) -> Result<String> {
        let template = context.parser.parse(content)?;
        let html = template.render(context.globals)?;

        let html = match self.front.format {
            cobalt_model::SourceFormat::Raw => html,
            cobalt_model::SourceFormat::Markdown => context.markdown.parse(&html)?,
        };

        let html = minify_if_enabled(html, context, &self.file_path)?;
        Ok(html)
    }

    /// Renders the excerpt and adds it to attributes of the document.
    ///
    /// The excerpt is either taken from the `excerpt` frontmatter setting, if
    /// given, or extracted from the content, if `excerpt_separator` is not
    /// empty. When neither condition applies, the excerpt is set to the `Nil`
    /// value.
    pub fn render_excerpt(&mut self, context: &RenderContex) -> Result<()> {
        let value = if let Some(excerpt_str) = self.front.excerpt.as_ref() {
            let excerpt = self.render_html(excerpt_str, context)?;
            Value::scalar(excerpt)
        } else if self.front.excerpt_separator.is_empty() {
            Value::Nil
        } else {
            let excerpt = extract_excerpt(
                &self.content,
                self.front.format,
                &self.front.excerpt_separator,
            );
            let excerpt = self.render_html(&excerpt, context)?;
            Value::scalar(excerpt)
        };

        self.attributes.insert("excerpt".into(), value);
        Ok(())
    }

    /// Renders the content and adds it to attributes of the document.
    ///
    /// When we say "content" we mean only this document without extended layout.
    pub fn render_content(&mut self, context: &RenderContex) -> Result<()> {
        let content_html = self.render_html(&self.content, context)?;
        self.attributes
            .insert("content".into(), Value::scalar(content_html));
        Ok(())
    }

    /// Renders the document to an HTML string.
    ///
    /// Side effects:
    ///
    /// * layout may be inserted to layouts cache
    pub fn render(
        &mut self,
        context: &RenderContex,
        layouts: &HashMap<String, String>,
    ) -> Result<String> {
        if let Some(ref layout) = self.front.layout {
            let layout_data_ref = layouts.get(layout).ok_or_else(|| {
                failure::format_err!(
                    "Layout {} does not exist (referenced in {}).",
                    layout,
                    self.file_path.display()
                )
            })?;

            let template = context
                .parser
                .parse(layout_data_ref)
                .with_context(|_| failure::format_err!("Failed to parse layout {:?}", layout))?;
            let content_html = template
                .render(context.globals)
                .with_context(|_| failure::format_err!("Failed to render layout {:?}", layout))?;
            let content_html = minify_if_enabled(content_html, context, &self.file_path)?;
            Ok(content_html)
        } else {
            let path = &[
                kstring::KStringCow::from_static("page").into(),
                kstring::KStringCow::from_static("content").into(),
            ];
            let content_html = liquid::model::try_find(context.globals, path)
                .ok_or_else(|| failure::err_msg("Internal error: page isn't in globals"))?
                .render()
                .to_string();

            let content_html = minify_if_enabled(content_html, context, &self.file_path)?;
            Ok(content_html)
        }
    }
}

fn extract_excerpt_raw(content: &str, excerpt_separator: &str) -> String {
    content
        .split(excerpt_separator)
        .next()
        .unwrap_or(content)
        .to_owned()
}

fn extract_excerpt_markdown(content: &str, excerpt_separator: &str) -> String {
    lazy_static! {
        static ref MARKDOWN_REF: Regex = Regex::new(r"(?m:^ {0,3}\[[^\]]+\]:.+$)").unwrap();
    }

    let mut trail = String::new();

    if MARKDOWN_REF.is_match(content) {
        for mat in MARKDOWN_REF.find_iter(content) {
            trail.push_str(mat.as_str());
            trail.push('\n');
        }
    }
    trail + content.split(excerpt_separator).next().unwrap_or(content)
}

fn extract_excerpt(
    content: &str,
    format: cobalt_model::SourceFormat,
    excerpt_separator: &str,
) -> String {
    match format {
        cobalt_model::SourceFormat::Markdown => {
            extract_excerpt_markdown(content, excerpt_separator)
        }
        cobalt_model::SourceFormat::Raw => extract_excerpt_raw(content, excerpt_separator),
    }
}

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

    #[test]
    fn format_path_variable_file() {
        let input = Path::new("/hello/world/file.liquid");
        let actual = format_path_variable(input);
        assert_eq!(actual, "hello/world");
    }

    #[test]
    fn format_path_variable_relative() {
        let input = Path::new("hello/world/file.liquid");
        let actual = format_path_variable(input);
        assert_eq!(actual, "hello/world");

        let input = Path::new("./hello/world/file.liquid");
        let actual = format_path_variable(input);
        assert_eq!(actual, "hello/world");
    }
}
<commit_msg>perf: Don't double-minify<commit_after>use std::clone::Clone;
use std::collections::HashMap;
use std::default::Default;
use std::path::{Path, PathBuf};

use chrono::{Datelike, Timelike};
use failure::ResultExt;
use itertools;
use jsonfeed;
use liquid;
use liquid::model::Value;
use liquid::Object;
use liquid::ValueView;
use regex::Regex;
use rss;

use crate::cobalt_model;
use crate::cobalt_model::files;
use crate::cobalt_model::permalink;
use crate::cobalt_model::slug;
use crate::cobalt_model::Minify;
use crate::error::*;

pub struct RenderContex<'a> {
    pub parser: &'a cobalt_model::Liquid,
    pub markdown: &'a cobalt_model::Markdown,
    pub globals: &'a Object,
    pub minify: Minify,
}

#[cfg(not(feature = "html-minifier"))]
fn minify_if_enabled(html: String, _context: &RenderContex, _file_path: &Path) -> Result<String> {
    Ok(html)
}

#[cfg(feature = "html-minifier")]
fn minify_if_enabled(html: String, context: &RenderContex, file_path: &Path) -> Result<String> {
    let extension = file_path.extension().unwrap_or_else(|| Default::default());
    if context.minify.html && (extension == "html" || extension == "htm") {
        Ok(html_minifier::minify(html)?)
    } else {
        Ok(html)
    }
}

/// Convert the source file's relative path into a format useful for generating permalinks that
/// mirror the source directory hierarchy.
fn format_path_variable(source_file: &Path) -> String {
    let parent = source_file
        .parent()
        .and_then(|p| p.to_str())
        .unwrap_or("")
        .to_owned();
    let mut path = parent.replace("\\", "/");
    if path.starts_with("./") {
        path.remove(0);
    }
    if path.starts_with('/') {
        path.remove(0);
    }
    path
}

pub fn permalink_attributes(front: &cobalt_model::Frontmatter, dest_file: &Path) -> Object {
    let mut attributes = Object::new();

    attributes.insert(
        "parent".into(),
        Value::scalar(format_path_variable(dest_file)),
    );

    let filename = dest_file
        .file_stem()
        .and_then(|s| s.to_str())
        .unwrap_or("")
        .to_owned();
    attributes.insert("name".into(), Value::scalar(filename));

    attributes.insert("ext".into(), Value::scalar(".html"));

    // TODO(epage): Add `collection` (the collection's slug), see #257
    // or `parent.slug`, see #323

    attributes.insert("slug".into(), Value::scalar(front.slug.clone()));

    attributes.insert(
        "categories".into(),
        Value::scalar(itertools::join(
            front.categories.iter().map(slug::slugify),
            "/",
        )),
    );

    if let Some(ref date) = front.published_date {
        attributes.insert("year".into(), Value::scalar(date.year().to_string()));
        attributes.insert(
            "month".into(),
            Value::scalar(format!("{:02}", &date.month())),
        );
        attributes.insert("i_month".into(), Value::scalar(date.month().to_string()));
        attributes.insert("day".into(), Value::scalar(format!("{:02}", &date.day())));
        attributes.insert("i_day".into(), Value::scalar(date.day().to_string()));
        attributes.insert("hour".into(), Value::scalar(format!("{:02}", &date.hour())));
        attributes.insert(
            "minute".into(),
            Value::scalar(format!("{:02}", &date.minute())),
        );
        attributes.insert(
            "second".into(),
            Value::scalar(format!("{:02}", &date.second())),
        );
    }

    attributes.insert("data".into(), Value::Object(front.data.clone()));

    attributes
}

fn document_attributes(
    front: &cobalt_model::Frontmatter,
    source_file: &Path,
    url_path: &str,
) -> Object {
    let categories = Value::Array(
        front
            .categories
            .iter()
            .cloned()
            .map(Value::scalar)
            .collect(),
    );
    // Reason for `file`:
    // - Allow access to assets in the original location
    // - Ease linking back to page's source
    let file: Object = vec![
        (
            "permalink".into(),
            Value::scalar(source_file.to_str().unwrap_or("").to_owned()),
        ),
        (
            "parent".into(),
            Value::scalar(
                source_file
                    .parent()
                    .and_then(Path::to_str)
                    .unwrap_or("")
                    .to_owned(),
            ),
        ),
    ]
    .into_iter()
    .collect();
    let attributes = vec![
        ("permalink".into(), Value::scalar(url_path.to_owned())),
        ("title".into(), Value::scalar(front.title.clone())),
        ("slug".into(), Value::scalar(front.slug.clone())),
        (
            "description".into(),
            Value::scalar(front.description.as_deref().unwrap_or("").to_owned()),
        ),
        ("categories".into(), categories),
        ("is_draft".into(), Value::scalar(front.is_draft)),
        ("weight".into(), Value::scalar(front.weight)),
        ("file".into(), Value::Object(file)),
        ("collection".into(), Value::scalar(front.collection.clone())),
        ("data".into(), Value::Object(front.data.clone())),
    ];
    let mut attributes: Object = attributes.into_iter().collect();

    if let Some(ref tags) = front.tags {
        let tags = Value::Array(tags.iter().cloned().map(Value::scalar).collect());
        attributes.insert("tags".into(), tags);
    }

    if let Some(ref published_date) = front.published_date {
        attributes.insert(
            "published_date".into(),
            Value::scalar(liquid::model::DateTime::from(*published_date)),
        );
    }

    attributes
}

#[derive(Debug, Clone)]
pub struct Document {
    pub url_path: String,
    pub file_path: PathBuf,
    pub content: String,
    pub attributes: Object,
    pub front: cobalt_model::Frontmatter,
}

impl Document {
    pub fn new(
        url_path: String,
        file_path: PathBuf,
        content: String,
        attributes: Object,
        front: cobalt_model::Frontmatter,
    ) -> Document {
        Document {
            url_path,
            file_path,
            content,
            attributes,
            front,
        }
    }

    pub fn parse(
        src_path: &Path,
        rel_path: &Path,
        default_front: cobalt_model::FrontmatterBuilder,
    ) -> Result<Document> {
        trace!("Parsing {:?}", rel_path);
        let content = files::read_file(src_path)?;
        let builder =
            cobalt_model::DocumentBuilder::<cobalt_model::FrontmatterBuilder>::parse(&content)?;
        let (front, content) = builder.parts();
        let front = front.merge_path(rel_path).merge(default_front);

        let front = front.build()?;

        let (file_path, url_path) = {
            let perma_attributes = permalink_attributes(&front, rel_path);
            let url_path = permalink::explode_permalink(&front.permalink, &perma_attributes)
                .with_context(|_| {
                    failure::format_err!("Failed to create permalink `{}`", front.permalink)
                })?;
            let file_path = permalink::format_url_as_file(&url_path);
            (file_path, url_path)
        };

        let doc_attributes = document_attributes(&front, rel_path, url_path.as_ref());

        Ok(Document::new(
            url_path,
            file_path,
            content,
            doc_attributes,
            front,
        ))
    }

    /// Metadata for generating RSS feeds
    pub fn to_rss(&self, root_url: &str) -> Result<rss::Item> {
        let link = format!("{}/{}", root_url, &self.url_path);
        let guid = rss::GuidBuilder::default()
            .value(link.clone())
            .permalink(true)
            .build()
            .map_err(failure::err_msg)?;

        let item = rss::ItemBuilder::default()
            .title(Some(self.front.title.clone()))
            .link(Some(link))
            .guid(Some(guid))
            .pub_date(self.front.published_date.map(|date| date.to_rfc2822()))
            .description(self.description_to_str())
            .build()
            .map_err(failure::err_msg)?;
        Ok(item)
    }

    /// Metadata for generating JSON feeds
    pub fn to_jsonfeed(&self, root_url: &str) -> jsonfeed::Item {
        let link = format!("{}/{}", root_url, &self.url_path);

        jsonfeed::Item {
            id: link.clone(),
            url: Some(link),
            title: Some(self.front.title.clone()),
            content: jsonfeed::Content::Html(
                self.description_to_str().unwrap_or_else(|| "".into()),
            ),
            date_published: self.front.published_date.map(|date| date.to_rfc2822()),
            // TODO completely implement categories, see Issue 131
            tags: Some(self.front.categories.clone()),
            ..Default::default()
        }
    }

    pub fn to_sitemap<T: std::io::Write>(
        &self,
        root_url: &str,
        writer: &mut sitemap::writer::UrlSetWriter<T>,
    ) -> Result<()> {
        let link = format!("{}/{}", root_url, &self.url_path);
        let mut url = sitemap::structs::UrlEntry::builder();
        url = url.loc(link);
        if let Some(date) = self.front.published_date {
            url = url.lastmod(*date);
        }
        writer.url(url)?;
        Ok(())
    }

    fn description_to_str(&self) -> Option<String> {
        self.front
            .description
            .clone()
            .or_else(|| {
                self.attributes.get("excerpt").and_then(|excerpt| {
                    if excerpt.is_nil() {
                        None
                    } else {
                        Some(excerpt.render().to_string())
                    }
                })
            })
            .or_else(|| {
                self.attributes
                    .get("content")
                    .map(|s| s.render().to_string())
            })
    }

    /// Renders liquid templates into HTML in the context of current document.
    ///
    /// Takes `content` string and returns rendered HTML. This function doesn't
    /// take `"extends"` attribute into account. This function can be used for
    /// rendering content or excerpt.
    fn render_html(&self, content: &str, context: &RenderContex) -> Result<String> {
        let template = context.parser.parse(content)?;
        let html = template.render(context.globals)?;

        let html = match self.front.format {
            cobalt_model::SourceFormat::Raw => html,
            cobalt_model::SourceFormat::Markdown => context.markdown.parse(&html)?,
        };

        Ok(html)
    }

    /// Renders the excerpt and adds it to attributes of the document.
    ///
    /// The excerpt is either taken from the `excerpt` frontmatter setting, if
    /// given, or extracted from the content, if `excerpt_separator` is not
    /// empty. When neither condition applies, the excerpt is set to the `Nil`
    /// value.
    pub fn render_excerpt(&mut self, context: &RenderContex) -> Result<()> {
        let value = if let Some(excerpt_str) = self.front.excerpt.as_ref() {
            let excerpt = self.render_html(excerpt_str, context)?;
            Value::scalar(excerpt)
        } else if self.front.excerpt_separator.is_empty() {
            Value::Nil
        } else {
            let excerpt = extract_excerpt(
                &self.content,
                self.front.format,
                &self.front.excerpt_separator,
            );
            let excerpt = self.render_html(&excerpt, context)?;
            Value::scalar(excerpt)
        };

        self.attributes.insert("excerpt".into(), value);
        Ok(())
    }

    /// Renders the content and adds it to attributes of the document.
    ///
    /// When we say "content" we mean only this document without extended layout.
    pub fn render_content(&mut self, context: &RenderContex) -> Result<()> {
        let content_html = self.render_html(&self.content, context)?;
        self.attributes
            .insert("content".into(), Value::scalar(content_html));
        Ok(())
    }

    /// Renders the document to an HTML string.
    ///
    /// Side effects:
    ///
    /// * layout may be inserted to layouts cache
    pub fn render(
        &mut self,
        context: &RenderContex,
        layouts: &HashMap<String, String>,
    ) -> Result<String> {
        if let Some(ref layout) = self.front.layout {
            let layout_data_ref = layouts.get(layout).ok_or_else(|| {
                failure::format_err!(
                    "Layout {} does not exist (referenced in {}).",
                    layout,
                    self.file_path.display()
                )
            })?;

            let template = context
                .parser
                .parse(layout_data_ref)
                .with_context(|_| failure::format_err!("Failed to parse layout {:?}", layout))?;
            let content_html = template
                .render(context.globals)
                .with_context(|_| failure::format_err!("Failed to render layout {:?}", layout))?;
            let content_html = minify_if_enabled(content_html, context, &self.file_path)?;
            Ok(content_html)
        } else {
            let path = &[
                kstring::KStringCow::from_static("page").into(),
                kstring::KStringCow::from_static("content").into(),
            ];
            let content_html = liquid::model::try_find(context.globals, path)
                .ok_or_else(|| failure::err_msg("Internal error: page isn't in globals"))?
                .render()
                .to_string();

            let content_html = minify_if_enabled(content_html, context, &self.file_path)?;
            Ok(content_html)
        }
    }
}

fn extract_excerpt_raw(content: &str, excerpt_separator: &str) -> String {
    content
        .split(excerpt_separator)
        .next()
        .unwrap_or(content)
        .to_owned()
}

fn extract_excerpt_markdown(content: &str, excerpt_separator: &str) -> String {
    lazy_static! {
        static ref MARKDOWN_REF: Regex = Regex::new(r"(?m:^ {0,3}\[[^\]]+\]:.+$)").unwrap();
    }

    let mut trail = String::new();

    if MARKDOWN_REF.is_match(content) {
        for mat in MARKDOWN_REF.find_iter(content) {
            trail.push_str(mat.as_str());
            trail.push('\n');
        }
    }
    trail + content.split(excerpt_separator).next().unwrap_or(content)
}

fn extract_excerpt(
    content: &str,
    format: cobalt_model::SourceFormat,
    excerpt_separator: &str,
) -> String {
    match format {
        cobalt_model::SourceFormat::Markdown => {
            extract_excerpt_markdown(content, excerpt_separator)
        }
        cobalt_model::SourceFormat::Raw => extract_excerpt_raw(content, excerpt_separator),
    }
}

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

    #[test]
    fn format_path_variable_file() {
        let input = Path::new("/hello/world/file.liquid");
        let actual = format_path_variable(input);
        assert_eq!(actual, "hello/world");
    }

    #[test]
    fn format_path_variable_relative() {
        let input = Path::new("hello/world/file.liquid");
        let actual = format_path_variable(input);
        assert_eq!(actual, "hello/world");

        let input = Path::new("./hello/world/file.liquid");
        let actual = format_path_variable(input);
        assert_eq!(actual, "hello/world");
    }
}
<|endoftext|>


<commit_before>//! Define explicit schemes

use ndarray::*;
use ndarray_linalg::*;
use super::traits::*;

macro_rules! def_explicit {
    ($method:ident, $constructor:ident) => {

#[derive(new)]
pub struct $method<F, Time: RealScalar> {
    f: F,
    dt: Time,
}

impl<F, D, Time> ModelSize<D> for $method<F, Time>
    where F: ModelSize<D>,
          D: Dimension,
          Time: RealScalar
{
    fn model_size(&self) -> D::Pattern {
        self.f.model_size()
    }
}

impl<F, Time: RealScalar> TimeStep for $method<F, Time> {
    type Time = Time;
    fn get_dt(&self) -> Self::Time {
        self.dt
    }
    fn set_dt(&mut self, dt: Self::Time) {
        self.dt = dt;
    }
}

pub fn $constructor<F, Time: RealScalar>(f: F, dt: Time) -> $method<F, Time> {
    $method::new(f, dt)
}

}} // def_explicit

def_explicit!(Euler, euler);
def_explicit!(Heun, heun);
def_explicit!(RK4, rk4);

pub struct EulerBuffer<A, D> {
    x: Array<A, D>,
}

impl<A, F, D> TimeEvolution<D> for Euler<F, A::Real>
    where A: Scalar,
          F: Explicit<D, Scalar = A, Time = A::Real>,
          D: Dimension
{
    type Scalar = A;
    type Buffer = EulerBuffer<A, D>;

    fn new_buffer(&self) -> Self::Buffer {
        EulerBuffer { x: Array::zeros(self.f.model_size()) }
    }

    fn iterate<'a, S>(&self,
                      mut x: &'a mut ArrayBase<S, D>,
                      mut buf: &mut Self::Buffer)
                      -> &'a mut ArrayBase<S, D>
        where S: DataMut<Elem = A>
    {
        buf.x.zip_mut_with(x, |buf, x| *buf = *x);
        let mut fx = self.f.rhs(x);
        Zip::from(&mut *fx)
            .and(&buf.x)
            .apply(|vfx, vx| { *vfx = *vx + vfx.mul_real(self.dt); });
        fx
    }
}

pub struct HeunBuffer<A, D> {
    x: Array<A, D>,
    k1: Array<A, D>,
}

impl<A, F, D> TimeEvolution<D> for Heun<F, A::Real>
    where A: Scalar,
          F: Explicit<D, Time = A::Real, Scalar = A>,
          D: Dimension
{
    type Scalar = A;
    type Buffer = HeunBuffer<A, D>;

    fn new_buffer(&self) -> Self::Buffer {
        HeunBuffer {
            x: Array::zeros(self.f.model_size()),
            k1: Array::zeros(self.f.model_size()),
        }
    }

    fn iterate<'a, S>(&self,
                      mut x: &'a mut ArrayBase<S, D>,
                      mut buf: &mut HeunBuffer<A, D>)
                      -> &'a mut ArrayBase<S, D>
        where S: DataMut<Elem = A>
    {
        let dt = self.dt;
        let dt_2 = self.dt * into_scalar(0.5);
        // calc
        buf.x.zip_mut_with(x, |buf, x| *buf = *x);
        let k1 = self.f.rhs(x);
        buf.k1.zip_mut_with(k1, |buf, k1| *buf = *k1);
        Zip::from(&mut *k1)
            .and(&buf.x)
            .apply(|k1, &x_| { *k1 = k1.mul_real(dt) + x_; });
        let k2 = self.f.rhs(k1);
        Zip::from(&mut *k2)
            .and(&buf.x)
            .and(&buf.k1)
            .apply(|k2, &x_, &k1_| { *k2 = x_ + (k1_ + *k2).mul_real(dt_2); });
        k2
    }
}

pub struct RK4Buffer<A, D> {
    x: Array<A, D>,
    k1: Array<A, D>,
    k2: Array<A, D>,
    k3: Array<A, D>,
}

impl<A, D> RK4Buffer<A, D>
    where A: Scalar,
          D: Dimension
{
}

impl<A, F, D> TimeEvolution<D> for RK4<F, A::Real>
    where A: Scalar,
          F: Explicit<D, Time = A::Real, Scalar = A>,
          D: Dimension
{
    type Scalar = A;
    type Buffer = RK4Buffer<A, D>;

    fn new_buffer(&self) -> Self::Buffer {
        RK4Buffer {
            x: Array::zeros(self.f.model_size()),
            k1: Array::zeros(self.f.model_size()),
            k2: Array::zeros(self.f.model_size()),
            k3: Array::zeros(self.f.model_size()),
        }
    }

    fn iterate<'a, S>(&self,
                      mut x: &'a mut ArrayBase<S, D>,
                      mut buf: &mut RK4Buffer<A, D>)
                      -> &'a mut ArrayBase<S, D>
        where S: DataMut<Elem = A>
    {
        let two = into_scalar(2.0);
        let dt = self.dt;
        let dt_2 = self.dt * into_scalar(0.5);
        let dt_6 = self.dt / into_scalar(6.0);
        buf.x.zip_mut_with(x, |buf, x| *buf = *x);
        // k1
        let mut k1 = self.f.rhs(x);
        buf.k1.zip_mut_with(k1, |buf, k1| *buf = *k1);
        Zip::from(&mut *k1)
            .and(&buf.x)
            .apply(|k1, &x| { *k1 = k1.mul_real(dt_2) + x; });
        // k2
        let mut k2 = self.f.rhs(k1);
        buf.k2.zip_mut_with(k2, |buf, k| *buf = *k);
        Zip::from(&mut *k2)
            .and(&buf.x)
            .apply(|k2, &x| { *k2 = x + k2.mul_real(dt_2); });
        // k3
        let mut k3 = self.f.rhs(k2);
        buf.k3.zip_mut_with(k3, |buf, k| *buf = *k);
        Zip::from(&mut *k3)
            .and(&buf.x)
            .apply(|k3, &x| { *k3 = x + k3.mul_real(dt); });
        let mut k4 = self.f.rhs(k3);
        Zip::from(&mut *k4)
            .and(&buf.x)
            .and(&buf.k1)
            .and(&buf.k2)
            .and(&buf.k3)
            .apply(|k4, &x, &k1, &k2, &k3| {
                       *k4 = x + (k1 + (k2 + k3).mul_real(two) + *k4).mul_real(dt_6);
                   });
        k4
    }
}
<commit_msg>Add A: Scalar to Explicit::RK4 and others for better type-inference<commit_after>//! Define explicit schemes

use ndarray::*;
use ndarray_linalg::*;
use super::traits::*;

use std::marker::PhantomData;

macro_rules! def_explicit {
    ($method:ident, $constructor:ident) => {

pub struct $method<A, D, F>
where A: Scalar,
      D: Dimension,
{
    f: F,
    dt: A::Real,
    phantom: PhantomData<D>
}

impl<A, D, F> ModelSize<D> for $method<A, D, F>
where A: Scalar,
      D: Dimension,
      F: ModelSize<D>
{
    fn model_size(&self) -> D::Pattern {
        self.f.model_size()
    }
}

impl<A, D, F> TimeStep for $method<A, D, F>
where A: Scalar,
      D: Dimension
{
    type Time = A::Real;
    fn get_dt(&self) -> Self::Time {
        self.dt
    }
    fn set_dt(&mut self, dt: Self::Time) {
        self.dt = dt;
    }
}

pub fn $constructor<A, D, F>(f: F, dt: A::Real) -> $method<A, D, F>
where A: Scalar,
      D: Dimension
{
    $method { f: f, dt: dt, phantom: PhantomData }
}

}} // def_explicit

def_explicit!(Euler, euler);
def_explicit!(Heun, heun);
def_explicit!(RK4, rk4);

pub struct EulerBuffer<A, D> {
    x: Array<A, D>,
}

impl<A, D, F> TimeEvolution<D> for Euler<A, D, F>
    where A: Scalar,
          D: Dimension,
          F: Explicit<D, Scalar = A, Time = A::Real>
{
    type Scalar = A;
    type Buffer = EulerBuffer<A, D>;

    fn new_buffer(&self) -> Self::Buffer {
        EulerBuffer { x: Array::zeros(self.f.model_size()) }
    }

    fn iterate<'a, S>(&self,
                      mut x: &'a mut ArrayBase<S, D>,
                      mut buf: &mut Self::Buffer)
                      -> &'a mut ArrayBase<S, D>
        where S: DataMut<Elem = A>
    {
        buf.x.zip_mut_with(x, |buf, x| *buf = *x);
        let mut fx = self.f.rhs(x);
        Zip::from(&mut *fx)
            .and(&buf.x)
            .apply(|vfx, vx| { *vfx = *vx + vfx.mul_real(self.dt); });
        fx
    }
}

pub struct HeunBuffer<A, D> {
    x: Array<A, D>,
    k1: Array<A, D>,
}

impl<A, D, F> TimeEvolution<D> for Heun<A, D, F>
    where A: Scalar,
          D: Dimension,
          F: Explicit<D, Time = A::Real, Scalar = A>
{
    type Scalar = A;
    type Buffer = HeunBuffer<A, D>;

    fn new_buffer(&self) -> Self::Buffer {
        HeunBuffer {
            x: Array::zeros(self.f.model_size()),
            k1: Array::zeros(self.f.model_size()),
        }
    }

    fn iterate<'a, S>(&self,
                      mut x: &'a mut ArrayBase<S, D>,
                      mut buf: &mut HeunBuffer<A, D>)
                      -> &'a mut ArrayBase<S, D>
        where S: DataMut<Elem = A>
    {
        let dt = self.dt;
        let dt_2 = self.dt * into_scalar(0.5);
        // calc
        buf.x.zip_mut_with(x, |buf, x| *buf = *x);
        let k1 = self.f.rhs(x);
        buf.k1.zip_mut_with(k1, |buf, k1| *buf = *k1);
        Zip::from(&mut *k1)
            .and(&buf.x)
            .apply(|k1, &x_| { *k1 = k1.mul_real(dt) + x_; });
        let k2 = self.f.rhs(k1);
        Zip::from(&mut *k2)
            .and(&buf.x)
            .and(&buf.k1)
            .apply(|k2, &x_, &k1_| { *k2 = x_ + (k1_ + *k2).mul_real(dt_2); });
        k2
    }
}

pub struct RK4Buffer<A, D> {
    x: Array<A, D>,
    k1: Array<A, D>,
    k2: Array<A, D>,
    k3: Array<A, D>,
}

impl<A, D, F> TimeEvolution<D> for RK4<A, D, F>
    where A: Scalar,
          D: Dimension,
          F: Explicit<D, Time = A::Real, Scalar = A>
{
    type Scalar = A;
    type Buffer = RK4Buffer<A, D>;

    fn new_buffer(&self) -> Self::Buffer {
        RK4Buffer {
            x: Array::zeros(self.f.model_size()),
            k1: Array::zeros(self.f.model_size()),
            k2: Array::zeros(self.f.model_size()),
            k3: Array::zeros(self.f.model_size()),
        }
    }

    fn iterate<'a, S>(&self,
                      mut x: &'a mut ArrayBase<S, D>,
                      mut buf: &mut RK4Buffer<A, D>)
                      -> &'a mut ArrayBase<S, D>
        where S: DataMut<Elem = A>
    {
        let two = into_scalar(2.0);
        let dt = self.dt;
        let dt_2 = self.dt * into_scalar(0.5);
        let dt_6 = self.dt / into_scalar(6.0);
        buf.x.zip_mut_with(x, |buf, x| *buf = *x);
        // k1
        let mut k1 = self.f.rhs(x);
        buf.k1.zip_mut_with(k1, |buf, k1| *buf = *k1);
        Zip::from(&mut *k1)
            .and(&buf.x)
            .apply(|k1, &x| { *k1 = k1.mul_real(dt_2) + x; });
        // k2
        let mut k2 = self.f.rhs(k1);
        buf.k2.zip_mut_with(k2, |buf, k| *buf = *k);
        Zip::from(&mut *k2)
            .and(&buf.x)
            .apply(|k2, &x| { *k2 = x + k2.mul_real(dt_2); });
        // k3
        let mut k3 = self.f.rhs(k2);
        buf.k3.zip_mut_with(k3, |buf, k| *buf = *k);
        Zip::from(&mut *k3)
            .and(&buf.x)
            .apply(|k3, &x| { *k3 = x + k3.mul_real(dt); });
        let mut k4 = self.f.rhs(k3);
        Zip::from(&mut *k4)
            .and(&buf.x)
            .and(&buf.k1)
            .and(&buf.k2)
            .and(&buf.k3)
            .apply(|k4, &x, &k1, &k2, &k3| {
                       *k4 = x + (k1 + (k2 + k3).mul_real(two) + *k4).mul_real(dt_6);
                   });
        k4
    }
}
<|endoftext|>


<commit_before>// contains methods for generating pawn moves
use super::slider::*;
use bb::*;
use mv_list::MoveList;
use piece::*;
use position::Position;
use side::{Side, WHITE};
use square;

pub fn pawn_moves<L: MoveList>(
    position: &Position,
    capture_mask: BB,
    push_mask: BB,
    from_mask: BB,
    list: &mut L,
) {
    pawn_pushes(position, push_mask, from_mask, list);
    pawn_captures(position, capture_mask, push_mask, from_mask, list);
}

// white, left  = +7 remove FILE_H
// white, right = +9 remove FILE_A
// black, left  = -9 remove FILE_H
// black, right = -7 remove FILE_A
// maps: side -> capture-direction -> shift amount + overflow mask
pub const PAWN_CAPTURE_FILE_MASKS: [[(usize, BB); 2]; 2] = [
    [(7, NOT_FILE_H), (9, NOT_FILE_A)],
    [(64 - 9, NOT_FILE_H), (64 - 7, NOT_FILE_A)],
];

pub fn pawn_pushes<L: MoveList>(position: &Position, to_mask: BB, from_mask: BB, list: &mut L) {
    // NOTE in the case of EP capture, mask is for the enemy piece taken
    let stm = position.state().stm;
    let piece = PAWN.pc(stm);
    let movers = position.bb_pc(piece) & from_mask;

    if movers == EMPTY {
        return;
    }

    let shift = if stm == WHITE { 8 } else { 64 - 8 };
    let empty_squares = position.bb_empty();

    // Dont add mask here to avoid masking double pushes
    let single_pushes = movers.rot_left(shift as u32) & empty_squares;

    list.add_pawn_pushes(shift, single_pushes & to_mask);

    let mask = if stm == WHITE { ROW_4 } else { ROW_5 };
    let double_pushes = single_pushes.rot_left(shift as u32) & empty_squares & mask;

    // DOUBLE PUSHES
    let double_push_shift = (shift * 2) % 64;
    list.add_pawn_pushes(double_push_shift, double_pushes & to_mask);
}

pub fn pawn_captures<L: MoveList>(
    position: &Position,
    capture_mask: BB,
    push_mask: BB,
    from_mask: BB,
    list: &mut L,
) {
    let move_mask = capture_mask | push_mask;
    let stm = position.state().stm;
    let piece = PAWN.pc(stm);
    let movers = position.bb_pc(piece) & from_mask;

    if movers == EMPTY {
        return;
    }

    let enemies = position.bb_side(stm.flip());

    if capture_mask != EMPTY {
        // CAPTURES
        for &(shift, file_mask) in PAWN_CAPTURE_FILE_MASKS[stm.to_usize()].iter() {
            let targets = movers.rot_left(shift as u32) & file_mask;
            let occupied_targets = targets & enemies & move_mask;
            list.add_pawn_captures(shift, occupied_targets);
        }
    }

    if position.state().ep_square.is_some() {
        let ep = position.state().ep_square.unwrap();
        // This is rare so worth duplicating work here to avoid doing it above
        for &(shift, file_mask) in PAWN_CAPTURE_FILE_MASKS[stm.to_usize()].iter() {
            // EN-PASSANT CAPTURES
            let targets = movers.rot_left(shift as u32) & file_mask;
            let ep_captures = targets & BB::new(ep);
            for (to, to_bb) in ep_captures.iter() {
                let from = to.rotate_right(shift as square::Internal);

                let capture_sq = from.along_row_with_col(to);
                let capture_sq_bb = BB::new(capture_sq);

                // can only make ep capture if moving to push_mask, or capturing on capture mask
                if ((to_bb & push_mask) | (capture_sq_bb & capture_mask)).any() {
                    // here we need to ensure that there is no discovered check
                    let from_bb = to_bb.rot_right(shift as u32);
                    // This is expensive but very infrequent
                    if !ep_move_discovers_check(from_bb, capture_sq_bb, stm, position) {
                        list.add_pawn_ep_capture(from_bb.bitscan(), ep);
                    }
                }
            }
        }
    }
}

pub fn ep_move_discovers_check(from: BB, to: BB, side: Side, position: &Position) -> bool {
    let occupied = position.bb_occupied() ^ from ^ to;
    let attacker = side.flip();
    let queens = position.bb_pc(QUEEN.pc(attacker));
    let rooks = position.bb_pc(ROOK.pc(attacker));
    let non_diag_attackers = queens | rooks;

    let king_sq = position.bb_pc(KING.pc(side)).bitscan();

    rank_attacks_from_sq(king_sq, occupied) & non_diag_attackers != EMPTY
}

#[cfg(test)]
mod test {
    use super::*;
    use gen::util::{assert_list_excludes_moves, assert_list_includes_moves};
    use mv_list::MoveVec;

    #[test]
    fn captures() {
        let position = &Position::from_fen("rnbqkbnr/pppppppp/P7/8/8/8/8/RNBQKBNR w").unwrap();
        let mut list = MoveVec::new();
        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        assert_eq!(list.len(), 1);
        assert_list_includes_moves(&list, &["a6xb7"]);

        let position =
            &Position::from_fen("rnbqkbnr/pppppppp/7P/8/8/8/P1PPPPPP/RNBQKBNR b").unwrap();
        let mut list = MoveVec::new();
        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        assert_list_includes_moves(&list, &["g7xh6"]);
    }

    #[test]
    fn ep_captures() {
        let position = &Position::from_fen("rnbqkbnr/8/8/Pp6/8/8/8/RNBQKBNR w - b6").unwrap();
        let mut list = MoveVec::new();
        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        assert_eq!(list.len(), 2);
        assert_list_includes_moves(&list, &["a5xb6e.p.", "a5a6"]);
    }

    #[test]
    fn illegal_ep_captures() {
        // test that ep captures that lead to discovered check due to moving
        // two pieces are not allowed
        //   ABCDEFGH
        // 8|....k...|8
        // 7|........|7
        // 6|....X...|6 -> e6 en-passant square
        // 5|r..Pp..K|5 -> discovered check due to EP
        // 4|........|4
        // 3|........|3
        // 2|........|2
        // 1|........|1
        //   ABCDEFGH
        let position = &Position::from_fen("4k3/8/8/r2Pp2K/8/8/8/8 w - e6").unwrap();
        let mut list = MoveVec::new();
        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        assert_list_excludes_moves(&list, &["d5xe6e.p."]);
        assert_list_includes_moves(&list, &["d5d6"]);
        assert_eq!(list.len(), 1);
    }

    #[test]
    fn single_pushes() {
        let position =
            &Position::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w").unwrap();
        let mut list = MoveVec::new();
        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        println!("{}", list);
        assert_list_includes_moves(
            &list,
            &[
                "a2a3", "b2b3", "c2c3", "d2d3", "e2e3", "f2f3", "g2g3", "h2h3",
            ],
        );

        let position =
            &Position::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b").unwrap();
        let mut list = MoveVec::new();
        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        assert_list_includes_moves(
            &list,
            &[
                "a7a6", "b7b6", "c7c6", "d7d6", "e7e6", "f7f6", "g7g6", "h7h6",
            ],
        );
    }

    #[test]
    fn double_pushes() {
        let position =
            &Position::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w").unwrap();
        let mut list = MoveVec::new();

        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);

        assert_eq!(list.len(), 16);
        assert_list_includes_moves(
            &list,
            &[
                "a2a4", "b2b4", "c2c4", "d2d4", "e2e4", "f2f4", "g2g4", "h2h4",
            ],
        );

        let position =
            &Position::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b").unwrap();
        let mut list = MoveVec::new();

        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        assert_eq!(list.len(), 16);
        assert_list_includes_moves(
            &list,
            &[
                "a7a5", "b7b5", "c7c5", "d7d5", "e7e5", "f7f5", "g7g5", "h7h5",
            ],
        );
    }

    #[test]
    fn promotions() {
        let position = &Position::from_fen("R1Rqkbnr/PPpppppp/8/8/8/8/8/RNBQKBNR w").unwrap();
        let mut list = MoveVec::new();

        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        assert_eq!(list.len(), 4);
        assert_list_includes_moves(&list, &["b7b8=N", "b7b8=B", "b7b8=R", "b7b8=Q"]);

        let position = &Position::from_fen("rnbqkbnr/8/8/8/8/8/PPPpPPPP/RN3BNR b").unwrap();
        let mut list = MoveVec::new();

        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        assert_eq!(list.len(), 4);
        assert_list_includes_moves(&list, &["d2d1=N", "d2d1=B", "d2d1=R", "d2d1=Q"]);
    }

    #[test]
    fn promotion_captures() {
        let position = &Position::from_fen("rnbqkbnr/8/8/8/8/8/PPPpPPPP/RNBBNR2 b").unwrap();
        let mut list = MoveVec::new();

        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        println!("{}", &list);
        assert_eq!(list.len(), 8);
        assert_list_includes_moves(
            &list,
            &[
                "d2xc1=N", "d2xc1=B", "d2xc1=R", "d2xc1=Q", "d2xe1=N", "d2xe1=B", "d2xe1=R",
                "d2xe1=Q",
            ],
        );
    }
}
<commit_msg>remove printlns<commit_after>// contains methods for generating pawn moves
use super::slider::*;
use bb::*;
use mv_list::MoveList;
use piece::*;
use position::Position;
use side::{Side, WHITE};
use square;

pub fn pawn_moves<L: MoveList>(
    position: &Position,
    capture_mask: BB,
    push_mask: BB,
    from_mask: BB,
    list: &mut L,
) {
    pawn_pushes(position, push_mask, from_mask, list);
    pawn_captures(position, capture_mask, push_mask, from_mask, list);
}

// white, left  = +7 remove FILE_H
// white, right = +9 remove FILE_A
// black, left  = -9 remove FILE_H
// black, right = -7 remove FILE_A
// maps: side -> capture-direction -> shift amount + overflow mask
pub const PAWN_CAPTURE_FILE_MASKS: [[(usize, BB); 2]; 2] = [
    [(7, NOT_FILE_H), (9, NOT_FILE_A)],
    [(64 - 9, NOT_FILE_H), (64 - 7, NOT_FILE_A)],
];

pub fn pawn_pushes<L: MoveList>(position: &Position, to_mask: BB, from_mask: BB, list: &mut L) {
    // NOTE in the case of EP capture, mask is for the enemy piece taken
    let stm = position.state().stm;
    let piece = PAWN.pc(stm);
    let movers = position.bb_pc(piece) & from_mask;

    if movers == EMPTY {
        return;
    }

    let shift = if stm == WHITE { 8 } else { 64 - 8 };
    let empty_squares = position.bb_empty();

    // Dont add mask here to avoid masking double pushes
    let single_pushes = movers.rot_left(shift as u32) & empty_squares;

    list.add_pawn_pushes(shift, single_pushes & to_mask);

    let mask = if stm == WHITE { ROW_4 } else { ROW_5 };
    let double_pushes = single_pushes.rot_left(shift as u32) & empty_squares & mask;

    // DOUBLE PUSHES
    let double_push_shift = (shift * 2) % 64;
    list.add_pawn_pushes(double_push_shift, double_pushes & to_mask);
}

pub fn pawn_captures<L: MoveList>(
    position: &Position,
    capture_mask: BB,
    push_mask: BB,
    from_mask: BB,
    list: &mut L,
) {
    let move_mask = capture_mask | push_mask;
    let stm = position.state().stm;
    let piece = PAWN.pc(stm);
    let movers = position.bb_pc(piece) & from_mask;

    if movers == EMPTY {
        return;
    }

    let enemies = position.bb_side(stm.flip());

    if capture_mask != EMPTY {
        // CAPTURES
        for &(shift, file_mask) in PAWN_CAPTURE_FILE_MASKS[stm.to_usize()].iter() {
            let targets = movers.rot_left(shift as u32) & file_mask;
            let occupied_targets = targets & enemies & move_mask;
            list.add_pawn_captures(shift, occupied_targets);
        }
    }

    if position.state().ep_square.is_some() {
        let ep = position.state().ep_square.unwrap();
        // This is rare so worth duplicating work here to avoid doing it above
        for &(shift, file_mask) in PAWN_CAPTURE_FILE_MASKS[stm.to_usize()].iter() {
            // EN-PASSANT CAPTURES
            let targets = movers.rot_left(shift as u32) & file_mask;
            let ep_captures = targets & BB::new(ep);
            for (to, to_bb) in ep_captures.iter() {
                let from = to.rotate_right(shift as square::Internal);

                let capture_sq = from.along_row_with_col(to);
                let capture_sq_bb = BB::new(capture_sq);

                // can only make ep capture if moving to push_mask, or capturing on capture mask
                if ((to_bb & push_mask) | (capture_sq_bb & capture_mask)).any() {
                    // here we need to ensure that there is no discovered check
                    let from_bb = to_bb.rot_right(shift as u32);
                    // This is expensive but very infrequent
                    if !ep_move_discovers_check(from_bb, capture_sq_bb, stm, position) {
                        list.add_pawn_ep_capture(from_bb.bitscan(), ep);
                    }
                }
            }
        }
    }
}

pub fn ep_move_discovers_check(from: BB, to: BB, side: Side, position: &Position) -> bool {
    let occupied = position.bb_occupied() ^ from ^ to;
    let attacker = side.flip();
    let queens = position.bb_pc(QUEEN.pc(attacker));
    let rooks = position.bb_pc(ROOK.pc(attacker));
    let non_diag_attackers = queens | rooks;

    let king_sq = position.bb_pc(KING.pc(side)).bitscan();

    rank_attacks_from_sq(king_sq, occupied) & non_diag_attackers != EMPTY
}

#[cfg(test)]
mod test {
    use super::*;
    use gen::util::{assert_list_excludes_moves, assert_list_includes_moves};
    use mv_list::MoveVec;

    #[test]
    fn captures() {
        let position = &Position::from_fen("rnbqkbnr/pppppppp/P7/8/8/8/8/RNBQKBNR w").unwrap();
        let mut list = MoveVec::new();
        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        assert_eq!(list.len(), 1);
        assert_list_includes_moves(&list, &["a6xb7"]);

        let position =
            &Position::from_fen("rnbqkbnr/pppppppp/7P/8/8/8/P1PPPPPP/RNBQKBNR b").unwrap();
        let mut list = MoveVec::new();
        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        assert_list_includes_moves(&list, &["g7xh6"]);
    }

    #[test]
    fn ep_captures() {
        let position = &Position::from_fen("rnbqkbnr/8/8/Pp6/8/8/8/RNBQKBNR w - b6").unwrap();
        let mut list = MoveVec::new();
        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        assert_eq!(list.len(), 2);
        assert_list_includes_moves(&list, &["a5xb6e.p.", "a5a6"]);
    }

    #[test]
    fn illegal_ep_captures() {
        // test that ep captures that lead to discovered check due to moving
        // two pieces are not allowed
        //   ABCDEFGH
        // 8|....k...|8
        // 7|........|7
        // 6|....X...|6 -> e6 en-passant square
        // 5|r..Pp..K|5 -> discovered check due to EP
        // 4|........|4
        // 3|........|3
        // 2|........|2
        // 1|........|1
        //   ABCDEFGH
        let position = &Position::from_fen("4k3/8/8/r2Pp2K/8/8/8/8 w - e6").unwrap();
        let mut list = MoveVec::new();
        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        assert_list_excludes_moves(&list, &["d5xe6e.p."]);
        assert_list_includes_moves(&list, &["d5d6"]);
        assert_eq!(list.len(), 1);
    }

    #[test]
    fn single_pushes() {
        let position =
            &Position::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w").unwrap();
        let mut list = MoveVec::new();
        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);

        assert_list_includes_moves(
            &list,
            &[
                "a2a3", "b2b3", "c2c3", "d2d3", "e2e3", "f2f3", "g2g3", "h2h3",
            ],
        );

        let position =
            &Position::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b").unwrap();
        let mut list = MoveVec::new();
        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        assert_list_includes_moves(
            &list,
            &[
                "a7a6", "b7b6", "c7c6", "d7d6", "e7e6", "f7f6", "g7g6", "h7h6",
            ],
        );
    }

    #[test]
    fn double_pushes() {
        let position =
            &Position::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w").unwrap();
        let mut list = MoveVec::new();

        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);

        assert_eq!(list.len(), 16);
        assert_list_includes_moves(
            &list,
            &[
                "a2a4", "b2b4", "c2c4", "d2d4", "e2e4", "f2f4", "g2g4", "h2h4",
            ],
        );

        let position =
            &Position::from_fen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR b").unwrap();
        let mut list = MoveVec::new();

        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        assert_eq!(list.len(), 16);
        assert_list_includes_moves(
            &list,
            &[
                "a7a5", "b7b5", "c7c5", "d7d5", "e7e5", "f7f5", "g7g5", "h7h5",
            ],
        );
    }

    #[test]
    fn promotions() {
        let position = &Position::from_fen("R1Rqkbnr/PPpppppp/8/8/8/8/8/RNBQKBNR w").unwrap();
        let mut list = MoveVec::new();

        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        assert_eq!(list.len(), 4);
        assert_list_includes_moves(&list, &["b7b8=N", "b7b8=B", "b7b8=R", "b7b8=Q"]);

        let position = &Position::from_fen("rnbqkbnr/8/8/8/8/8/PPPpPPPP/RN3BNR b").unwrap();
        let mut list = MoveVec::new();

        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        assert_eq!(list.len(), 4);
        assert_list_includes_moves(&list, &["d2d1=N", "d2d1=B", "d2d1=R", "d2d1=Q"]);
    }

    #[test]
    fn promotion_captures() {
        let position = &Position::from_fen("rnbqkbnr/8/8/8/8/8/PPPpPPPP/RNBBNR2 b").unwrap();
        let mut list = MoveVec::new();

        pawn_moves::<MoveVec>(position, !EMPTY, !EMPTY, !EMPTY, &mut list);
        assert_eq!(list.len(), 8);
        assert_list_includes_moves(
            &list,
            &[
                "d2xc1=N", "d2xc1=B", "d2xc1=R", "d2xc1=Q", "d2xe1=N", "d2xe1=B", "d2xe1=R",
                "d2xe1=Q",
            ],
        );
    }
}
<|endoftext|>


<commit_before>//! # generator
//!
//! Rust generator implementation
//!

use std::mem;
use std::panic;
use std::thread;
use std::any::Any;
use std::boxed::FnBox;
use std::marker::PhantomData;

use yield_::yield_now;
use generator::Generator;
use rt::{Error, Context, ContextStack};
use reg_context::Context as RegContext;

// default stack size, in usize
// windows has a minimal size as 0x4a8!!!!
pub const DEFAULT_STACK_SIZE: usize = 0x800;

/// Generator helper
pub struct Gn<A> {
    dummy: PhantomData<A>,
}

impl<A: Any> Gn<A> {
    /// create a new generator with default stack size
    pub fn new<'a, T: Any, F>(f: F) -> Box<Generator<A, Output = T> + 'a>
        where F: FnOnce() -> T + 'a
    {
        Self::new_opt(f, DEFAULT_STACK_SIZE)
    }

    /// create a new generator with specified stack size
    pub fn new_opt<'a, T: Any, F>(f: F, size: usize) -> Box<Generator<A, Output = T> + 'a>
        where F: FnOnce() -> T + 'a
    {
        let mut g = Box::new(GeneratorImpl::<A, T, F>::new(f, size));
        g.init();
        g
    }
}

/// `GeneratorImpl`
pub struct GeneratorImpl<A: Any, T: Any, F>
    where F: FnOnce() -> T
{
    // run time context
    context: Context,
    // save the input
    para: Option<A>,
    // save the output
    ret: Option<T>,
    // boxed functor
    f: Option<F>,
}

impl<'a, A: Any, T: Any, F> GeneratorImpl<A, T, F>
    where F: FnOnce() -> T + 'a
{
    /// create a new generator with default stack size
    pub fn new(f: F, size: usize) -> Self {
        GeneratorImpl {
            para: None,
            ret: None,
            f: Some(f),
            context: Context::new(size),
        }
    }

    /// init a heap based generator
    pub fn init(&mut self) {
        self.context.para = &mut self.para as &mut Any;
        self.context.ret = &mut self.ret as &mut Any;

        unsafe {
            let ptr = self as *mut Self;

            let start: Box<FnBox()> = Box::new(move || {
                let f = (*ptr).f.take().unwrap();
                (*ptr).ret = Some(f());
            });

            let stk = &(*ptr).context.stack;
            let reg = &mut (*ptr).context.regs;
            reg.init_with(gen_init,
                          ptr as usize,
                          Box::into_raw(Box::new(start)) as *mut usize,
                          stk);
        }
    }

    /// resume the generator
    #[inline]
    fn resume_gen(&mut self) {
        let env = ContextStack::current();
        let cur = &mut env.top().regs;
        let ctx = &mut self.context as *mut Context;
        let to = unsafe { &mut (*ctx).regs };
        // save current generator context on stack
        env.push(ctx);
        // switch context
        RegContext::swap(cur, to);

        // check the panic status
        // this would propagate the panic until root context
        let err = self.context.err;
        if err.is_some() {
            panic!(err.unwrap());
        }
    }

    #[inline]
    fn is_started(&self) -> bool {
        // when the f is consumed we think it's running
        self.f.is_none()
    }
}

impl<A: Any, T: Any, F> Drop for GeneratorImpl<A, T, F>
    where F: FnOnce() -> T
{
    fn drop(&mut self) {
        // when the thread is already panic, do nothing
        if thread::panicking() {
            return;
        }

        let mut i = 0;
        while !self.is_done() {
            if i > 2 {
                self.cancel();
                break;
            }
            self.raw_send(None);
            i += 1;
        }

        let (total_stack, used_stack) = self.stack_usage();
        if used_stack < total_stack {
            // here we should record the stack in the class
            // next time will just use
            // set_stack_size::<F>(used_stack);
        } else {
            error!("stack overflow detected!");
            panic!(Error::StackErr);
        }
    }
}

impl<A: Any, T: Any, F> Generator<A> for GeneratorImpl<A, T, F>
    where F: FnOnce() -> T
{
    type Output = T;

    #[inline]
    fn raw_send(&mut self, para: Option<A>) -> Option<T> {
        if self.is_done() {
            return None;
        }

        // this is the passed in value of the send primitive
        // the yield part would read out this value in the next round
        // use the replace is would drop the old value
        mem::replace(&mut self.para, para);

        // every time we call the function, increase the ref count
        // yiled will decrease it and return will not
        self.context._ref += 1;
        self.resume_gen();

        self.ret.take()
    }

    fn cancel(&mut self) {
        // consume the fun if it's not started
        if !self.is_started() {
            self.f.take();
            self.context._ref = 1;
        } else {
            // tell the func to panic
            // so that we can stop the inner func
            self.context._ref = 2;
            self.resume_gen();
        }
    }

    #[inline]
    fn is_done(&self) -> bool {
        self.is_started() && self.context._ref != 0
    }

    fn stack_usage(&self) -> (usize, usize) {
        (self.context.stack.size(), self.context.stack.get_used_size())
    }
}

fn gen_init(_: usize, f: *mut usize) -> ! {
    {
        let f = f as usize;
        let clo = move || {
            let func: Box<Box<FnBox()>> = unsafe { Box::from_raw(f as *mut Box<FnBox()>) };
            func();
        };

        // we can't panic inside the generator context
        // need to propagate the panic to the main thread
        // It is currently undefined behavior to unwind from Rust code into foreign code
        if let Err(cause) = panic::catch_unwind(clo) {
            if cause.downcast_ref::<Error>().is_some() {
                match cause.downcast_ref::<Error>().unwrap() {
                    &Error::Cancel => {}
                    err => {
                        let ctx = ContextStack::current().top();
                        ctx.err = Some(*err);
                    }
                }
            } else {
                error!("Panicked inside: {:?}", cause.downcast::<&str>());
            }
        }
        // drop the clo here
    }

    yield_now();

    unreachable!("Should never comeback");
}
<commit_msg>filter panic update<commit_after>//! # generator
//!
//! Rust generator implementation
//!

use std::mem;
use std::panic;
use std::thread;
use std::any::Any;
use std::boxed::FnBox;
use std::marker::PhantomData;

use yield_::yield_now;
use generator::Generator;
use rt::{Error, Context, ContextStack};
use reg_context::Context as RegContext;

// default stack size, in usize
// windows has a minimal size as 0x4a8!!!!
pub const DEFAULT_STACK_SIZE: usize = 0x800;

/// Generator helper
pub struct Gn<A> {
    dummy: PhantomData<A>,
}

impl<A: Any> Gn<A> {
    /// create a new generator with default stack size
    pub fn new<'a, T: Any, F>(f: F) -> Box<Generator<A, Output = T> + 'a>
        where F: FnOnce() -> T + 'a
    {
        Self::new_opt(f, DEFAULT_STACK_SIZE)
    }

    /// create a new generator with specified stack size
    pub fn new_opt<'a, T: Any, F>(f: F, size: usize) -> Box<Generator<A, Output = T> + 'a>
        where F: FnOnce() -> T + 'a
    {
        let mut g = Box::new(GeneratorImpl::<A, T, F>::new(f, size));
        g.init();
        g
    }
}

/// `GeneratorImpl`
pub struct GeneratorImpl<A: Any, T: Any, F>
    where F: FnOnce() -> T
{
    // run time context
    context: Context,
    // save the input
    para: Option<A>,
    // save the output
    ret: Option<T>,
    // boxed functor
    f: Option<F>,
}

impl<'a, A: Any, T: Any, F> GeneratorImpl<A, T, F>
    where F: FnOnce() -> T + 'a
{
    /// create a new generator with default stack size
    pub fn new(f: F, size: usize) -> Self {
        GeneratorImpl {
            para: None,
            ret: None,
            f: Some(f),
            context: Context::new(size),
        }
    }

    /// init a heap based generator
    pub fn init(&mut self) {
        self.context.para = &mut self.para as &mut Any;
        self.context.ret = &mut self.ret as &mut Any;

        unsafe {
            let ptr = self as *mut Self;

            let start: Box<FnBox()> = Box::new(move || {
                let f = (*ptr).f.take().unwrap();
                (*ptr).ret = Some(f());
            });

            let stk = &(*ptr).context.stack;
            let reg = &mut (*ptr).context.regs;
            reg.init_with(gen_init,
                          ptr as usize,
                          Box::into_raw(Box::new(start)) as *mut usize,
                          stk);
        }
    }

    /// resume the generator
    #[inline]
    fn resume_gen(&mut self) {
        let env = ContextStack::current();
        let cur = &mut env.top().regs;
        let ctx = &mut self.context as *mut Context;
        let to = unsafe { &mut (*ctx).regs };
        // save current generator context on stack
        env.push(ctx);
        // switch context
        RegContext::swap(cur, to);

        // check the panic status
        // this would propagate the panic until root context
        let err = self.context.err;
        if err.is_some() {
            panic!(err.unwrap());
        }
    }

    #[inline]
    fn is_started(&self) -> bool {
        // when the f is consumed we think it's running
        self.f.is_none()
    }
}

impl<A: Any, T: Any, F> Drop for GeneratorImpl<A, T, F>
    where F: FnOnce() -> T
{
    fn drop(&mut self) {
        // when the thread is already panic, do nothing
        if thread::panicking() {
            return;
        }

        let mut i = 0;
        while !self.is_done() {
            if i > 2 {
                self.cancel();
                break;
            }
            self.raw_send(None);
            i += 1;
        }

        let (total_stack, used_stack) = self.stack_usage();
        if used_stack < total_stack {
            // here we should record the stack in the class
            // next time will just use
            // set_stack_size::<F>(used_stack);
        } else {
            error!("stack overflow detected!");
            panic!(Error::StackErr);
        }
    }
}

impl<A: Any, T: Any, F> Generator<A> for GeneratorImpl<A, T, F>
    where F: FnOnce() -> T
{
    type Output = T;

    #[inline]
    fn raw_send(&mut self, para: Option<A>) -> Option<T> {
        if self.is_done() {
            return None;
        }

        // this is the passed in value of the send primitive
        // the yield part would read out this value in the next round
        // use the replace is would drop the old value
        mem::replace(&mut self.para, para);

        // every time we call the function, increase the ref count
        // yiled will decrease it and return will not
        self.context._ref += 1;
        self.resume_gen();

        self.ret.take()
    }

    fn cancel(&mut self) {
        // consume the fun if it's not started
        if !self.is_started() {
            self.f.take();
            self.context._ref = 1;
        } else {
            // tell the func to panic
            // so that we can stop the inner func
            self.context._ref = 2;
            self.resume_gen();
        }
    }

    #[inline]
    fn is_done(&self) -> bool {
        self.is_started() && self.context._ref != 0
    }

    fn stack_usage(&self) -> (usize, usize) {
        (self.context.stack.size(), self.context.stack.get_used_size())
    }
}

fn gen_init(_: usize, f: *mut usize) -> ! {
    {
        let f = f as usize;
        let clo = move || {
            let func: Box<Box<FnBox()>> = unsafe { Box::from_raw(f as *mut Box<FnBox()>) };
            func();
        };

        // we can't panic inside the generator context
        // need to propagate the panic to the main thread
        // It is currently undefined behavior to unwind from Rust code into foreign code
        if let Err(cause) = panic::catch_unwind(clo) {
            if cause.downcast_ref::<Error>().is_some() {
                match cause.downcast_ref::<Error>().unwrap() {
                    &Error::Cancel => {}
                    err => {
                        let ctx = ContextStack::current().top();
                        ctx.err = Some(*err);
                    }
                }
            } else {
                // we hope all other panic could covert to a string
                // here we forget the panic to avoid shutdown the whole thread
                let e = cause.downcast::<&str>().unwrap_or(Box::new("unkown panic"));
                error!("Panicked inside: {:?}", e);
            }
        }
        // drop the clo here
    }

    yield_now();

    unreachable!("Should never comeback");
}
<|endoftext|>


<commit_before>
/// Implemented by contexts that can color.
///
/// The context can color to type `T` by adding value `U`.
pub trait CanColor<'a, T, U> {
    /// Create a new context with color.
    fn color(&'a self, value: U) -> T;
}

/// Implemented by contexts that can set rectangle information.
pub trait CanRectangle<'a, T, U> {
    /// Create new context with recangle information.
    fn rectangle(&'a self, value: U) -> T;
}

/// Implemented by contexts that can transform.
///
/// The context can transform to type `T` by adding value `U`.
pub trait CanTransform<'a, T, U> {
    /// Create a new context with transformation.
    fn transform(&'a self, value: U) -> T;
}

/// Implemented by contexts that uses type `U` as current color.
pub trait HasColor<'a, U> {
    /// Returns the current color.
    fn get_color(&'a self) -> &'a U;
}

/// Implemented by contexts that uses type `U` as current rectangle.
pub trait HasRectangle<'a, U> {
    /// Returns the current rectangle.
    fn get_rectangle(&'a self) -> &'a U;
}

/// Implemented by contexts that uses type `U` as current transform.
///
/// This helps to remove redundant code.
pub trait HasTransform<'a, U> {
    /// Returns the current transform.
    fn get_transform(&'a self) -> &'a U;
}

<commit_msg>Added `HasViewTransform` to `internal`<commit_after>
/// Implemented by contexts that can color.
///
/// The context can color to type `T` by adding value `U`.
pub trait CanColor<'a, T, U> {
    /// Create a new context with color.
    fn color(&'a self, value: U) -> T;
}

/// Implemented by contexts that can set rectangle information.
pub trait CanRectangle<'a, T, U> {
    /// Create new context with recangle information.
    fn rectangle(&'a self, value: U) -> T;
}

/// Implemented by contexts that can transform.
///
/// The context can transform to type `T` by adding value `U`.
pub trait CanTransform<'a, T, U> {
    /// Create a new context with transformation.
    fn transform(&'a self, value: U) -> T;
}

/// Implemented by contexts that uses type `U` as current color.
pub trait HasColor<'a, U> {
    /// Returns the current color.
    fn get_color(&'a self) -> &'a U;
}

/// Implemented by contexts that uses type `U` as current rectangle.
pub trait HasRectangle<'a, U> {
    /// Returns the current rectangle.
    fn get_rectangle(&'a self) -> &'a U;
}

/// Implemented by contexts that uses type `U` as current transform.
///
/// This helps to remove redundant code.
pub trait HasTransform<'a, U> {
    /// Returns the current transform.
    fn get_transform(&'a self) -> &'a U;
}

/// Implemented by contexts that uses type `U` as current transform.
///
/// This helps to remove redundant code.
pub trait HasViewTransform<'a, U> {
    /// Returns the current view transform.
    fn get_view_transform(&'a self) -> &'a U;
}

<|endoftext|>