<commit_before>use renderer::html_handlebars::helpers;
use renderer::Renderer;
use book::MDBook;
use book::bookitem::BookItem;
use {utils, theme};
use regex::{Regex, Captures};

use std::ascii::AsciiExt;
use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::error::Error;
use std::io::{self, Read};
use std::collections::BTreeMap;

use handlebars::Handlebars;

use serde_json;

#[derive(Default)]
pub struct HtmlHandlebars;

impl HtmlHandlebars {
    pub fn new() -> Self {
        HtmlHandlebars
    }
}

impl Renderer for HtmlHandlebars {
    fn render(&self, book: &MDBook) -> Result<(), Box<Error>> {
        debug!("[fn]: render");
        let mut handlebars = Handlebars::new();

        // Load theme
        let theme = theme::Theme::new(book.get_theme_path());

        // Register template
        debug!("[*]: Register handlebars template");
        try!(handlebars.register_template_string("index", try!(String::from_utf8(theme.index))));

        // Register helpers
        debug!("[*]: Register handlebars helpers");
        handlebars.register_helper("toc", Box::new(helpers::toc::RenderToc));
        handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
        handlebars.register_helper("next", Box::new(helpers::navigation::next));

        let mut data = try!(make_data(book));

        // Print version
        let mut print_content: String = String::new();

        // Check if dest directory exists
        debug!("[*]: Check if destination directory exists");
        if fs::create_dir_all(book.get_dest()).is_err() {
            return Err(Box::new(io::Error::new(io::ErrorKind::Other,
                                               "Unexpected error when constructing destination path")));
        }

        // Render a file for every entry in the book
        let mut index = true;
        for item in book.iter() {

            match *item {
                BookItem::Chapter(_, ref ch) |
                BookItem::Affix(ref ch) => {
                    if ch.path != PathBuf::new() {

                        let path = book.get_src().join(&ch.path);

                        debug!("[*]: Opening file: {:?}", path);
                        let mut f = try!(File::open(&path));
                        let mut content: String = String::new();

                        debug!("[*]: Reading file");
                        try!(f.read_to_string(&mut content));

                        // Parse for playpen links
                        if let Some(p) = path.parent() {
                            content = helpers::playpen::render_playpen(&content, p);
                        }

                        // Render markdown using the pulldown-cmark crate
                        content = utils::render_markdown(&content);
                        print_content.push_str(&content);

                        // Update the context with data for this file
                        let path = ch.path.to_str().ok_or_else(||
                            io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                        data.insert("path".to_owned(), json!(path));
                        data.insert("content".to_owned(), json!(content));
                        data.insert("chapter_title".to_owned(), json!(ch.name));
                        data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(&ch.path)));

                        // Render the handlebars template with the data
                        debug!("[*]: Render template");
                        let rendered = try!(handlebars.render("index", &data));

                        let filename = Path::new(&ch.path).with_extension("html");

                        // create links for headers
                        let rendered = build_header_links(rendered, filename.to_str().unwrap_or(""));

                        // fix code blocks
                        let rendered = fix_code_blocks(rendered);

                        // Write to file
                        info!("[*] Creating {:?} ✓", filename.display());
                        try!(book.write_file(filename, &rendered.into_bytes()));

                        // Create an index.html from the first element in SUMMARY.md
                        if index {
                            debug!("[*]: index.html");

                            let mut content = String::new();
                            let _source = try!(File::open(book.get_dest().join(&ch.path.with_extension("html"))))
                                .read_to_string(&mut content);

                            // This could cause a problem when someone displays code containing <base href=...>
                            // on the front page, however this case should be very very rare...
                            content = content.lines()
                                .filter(|line| !line.contains("<base href="))
                                .collect::<Vec<&str>>()
                                .join("\n");

                            try!(book.write_file("index.html", content.as_bytes()));

                            info!("[*] Creating index.html from {:?} ✓",
                                  book.get_dest().join(&ch.path.with_extension("html")));
                            index = false;
                        }
                    }
                },
                _ => {},
            }
        }

        // Print version

        // Update the context with data for this file
        data.insert("path".to_owned(), json!("print.md"));
        data.insert("content".to_owned(), json!(print_content));
        data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(Path::new("print.md"))));

        // Render the handlebars template with the data
        debug!("[*]: Render template");

        let rendered = try!(handlebars.render("index", &data));
        let rendered = build_header_links(rendered, "print.html");

        // fix code blocks
        let rendered = fix_code_blocks(rendered);

        try!(book.write_file(Path::new("print").with_extension("html"), &rendered.into_bytes()));
        info!("[*] Creating print.html ✓");

        // Copy static files (js, css, images, ...)

        debug!("[*] Copy static files");
        try!(book.write_file("book.js", &theme.js));
        try!(book.write_file("book.css", &theme.css));
        try!(book.write_file("favicon.png", &theme.favicon));
        try!(book.write_file("jquery.js", &theme.jquery));
        try!(book.write_file("highlight.css", &theme.highlight_css));
        try!(book.write_file("tomorrow-night.css", &theme.tomorrow_night_css));
        try!(book.write_file("highlight.js", &theme.highlight_js));
        try!(book.write_file("_FontAwesome/css/font-awesome.css", theme::FONT_AWESOME));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.eot", theme::FONT_AWESOME_EOT));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.svg", theme::FONT_AWESOME_SVG));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.ttf", theme::FONT_AWESOME_TTF));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff", theme::FONT_AWESOME_WOFF));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff2", theme::FONT_AWESOME_WOFF2));
        try!(book.write_file("_FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF));

        // Copy all remaining files
        try!(utils::fs::copy_files_except_ext(book.get_src(), book.get_dest(), true, &["md"]));

        Ok(())
    }
}

fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>, Box<Error>> {
    debug!("[fn]: make_data");

    let mut data = serde_json::Map::new();
    data.insert("language".to_owned(), json!("en"));
    data.insert("title".to_owned(), json!(book.get_title()));
    data.insert("description".to_owned(), json!(book.get_description()));
    data.insert("favicon".to_owned(), json!("favicon.png"));
    if let Some(livereload) = book.get_livereload() {
        data.insert("livereload".to_owned(), json!(livereload));
    }

    let mut chapters = vec![];

    for item in book.iter() {
        // Create the data to inject in the template
        let mut chapter = BTreeMap::new();

        match *item {
            BookItem::Affix(ref ch) => {
                chapter.insert("name".to_owned(), json!(ch.name));
                let path = ch.path.to_str().ok_or_else(||
                    io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                chapter.insert("path".to_owned(), json!(path));
            },
            BookItem::Chapter(ref s, ref ch) => {
                chapter.insert("section".to_owned(), json!(s));
                chapter.insert("name".to_owned(), json!(ch.name));
                let path = ch.path.to_str().ok_or_else(||
                    io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                chapter.insert("path".to_owned(), json!(path));
            },
            BookItem::Spacer => {
                chapter.insert("spacer".to_owned(), json!("_spacer_"));
            },

        }

        chapters.push(chapter);
    }

    data.insert("chapters".to_owned(), json!(chapters));

    debug!("[*]: JSON constructed");
    Ok(data)
}

fn build_header_links(html: String, filename: &str) -> String {
    let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();

    regex.replace_all(&html, |caps: &Captures| {
        let level = &caps[1];
        let text = &caps[2];
        let mut id = text.to_string();
        let repl_sub = vec!["", "", "", "",
                            "", "",
                            "<", ">", "&", "'", """];
        for sub in repl_sub {
            id = id.replace(sub, "");
        }
        let id = id.chars().filter_map(|c| {
            if c.is_alphanumeric() || c == '-' || c == '_' {
                if c.is_ascii() {
                    Some(c.to_ascii_lowercase())
                } else {
                    Some(c)
                }
            } else if c.is_whitespace() && c.is_ascii() {
                Some('-')
            } else {
                None
            }
        }).collect::<String>();

        format!("<h{level}>{text}</h{level}>",
            level=level, id=id, text=text, filename=filename)
    }).into_owned()
}

// The rust book uses annotations for rustdoc to test code snippets, like the following:
// ```rust,should_panic
// fn main() {
//     // Code here
// }
// ```
// This function replaces all commas by spaces in the code block classes
fn fix_code_blocks(html: String) -> String {
    let regex = Regex::new(r##"]+)class="([^"]+)"([^>]*)>"##).unwrap();
    regex.replace_all(&html, |caps: &Captures| {
        let before = &caps[1];
        let classes = &caps[2].replace(",", " ");
        let after = &caps[3];

        format!("", before=before, classes=classes, after=after)
    }).into_owned()
}
<commit_msg>fix anchor links<commit_after>use renderer::html_handlebars::helpers;
use renderer::Renderer;
use book::MDBook;
use book::bookitem::BookItem;
use {utils, theme};
use regex::{Regex, Captures};

use std::ascii::AsciiExt;
use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::error::Error;
use std::io::{self, Read};
use std::collections::BTreeMap;

use handlebars::Handlebars;

use serde_json;

#[derive(Default)]
pub struct HtmlHandlebars;

impl HtmlHandlebars {
    pub fn new() -> Self {
        HtmlHandlebars
    }
}

impl Renderer for HtmlHandlebars {
    fn render(&self, book: &MDBook) -> Result<(), Box<Error>> {
        debug!("[fn]: render");
        let mut handlebars = Handlebars::new();

        // Load theme
        let theme = theme::Theme::new(book.get_theme_path());

        // Register template
        debug!("[*]: Register handlebars template");
        try!(handlebars.register_template_string("index", try!(String::from_utf8(theme.index))));

        // Register helpers
        debug!("[*]: Register handlebars helpers");
        handlebars.register_helper("toc", Box::new(helpers::toc::RenderToc));
        handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
        handlebars.register_helper("next", Box::new(helpers::navigation::next));

        let mut data = try!(make_data(book));

        // Print version
        let mut print_content: String = String::new();

        // Check if dest directory exists
        debug!("[*]: Check if destination directory exists");
        if fs::create_dir_all(book.get_dest()).is_err() {
            return Err(Box::new(io::Error::new(io::ErrorKind::Other,
                                               "Unexpected error when constructing destination path")));
        }

        // Render a file for every entry in the book
        let mut index = true;
        for item in book.iter() {

            match *item {
                BookItem::Chapter(_, ref ch) |
                BookItem::Affix(ref ch) => {
                    if ch.path != PathBuf::new() {

                        let path = book.get_src().join(&ch.path);

                        debug!("[*]: Opening file: {:?}", path);
                        let mut f = try!(File::open(&path));
                        let mut content: String = String::new();

                        debug!("[*]: Reading file");
                        try!(f.read_to_string(&mut content));

                        // Parse for playpen links
                        if let Some(p) = path.parent() {
                            content = helpers::playpen::render_playpen(&content, p);
                        }

                        // Render markdown using the pulldown-cmark crate
                        content = utils::render_markdown(&content);
                        print_content.push_str(&content);

                        // Update the context with data for this file
                        let path = ch.path.to_str().ok_or_else(||
                            io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                        data.insert("path".to_owned(), json!(path));
                        data.insert("content".to_owned(), json!(content));
                        data.insert("chapter_title".to_owned(), json!(ch.name));
                        data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(&ch.path)));

                        // Render the handlebars template with the data
                        debug!("[*]: Render template");
                        let rendered = try!(handlebars.render("index", &data));

                        let filename = Path::new(&ch.path).with_extension("html");

                        // create links for headers and fix anchors
                        let rendered = build_header_links(rendered, filename.to_str().unwrap_or(""));
                        let rendered = fix_anchor_links(rendered, filename.to_str().unwrap_or(""));

                        // fix code blocks
                        let rendered = fix_code_blocks(rendered);

                        // Write to file
                        info!("[*] Creating {:?} ✓", filename.display());
                        try!(book.write_file(filename, &rendered.into_bytes()));

                        // Create an index.html from the first element in SUMMARY.md
                        if index {
                            debug!("[*]: index.html");

                            let mut content = String::new();
                            let _source = try!(File::open(book.get_dest().join(&ch.path.with_extension("html"))))
                                .read_to_string(&mut content);

                            // This could cause a problem when someone displays code containing <base href=...>
                            // on the front page, however this case should be very very rare...
                            content = content.lines()
                                .filter(|line| !line.contains("<base href="))
                                .collect::<Vec<&str>>()
                                .join("\n");

                            try!(book.write_file("index.html", content.as_bytes()));

                            info!("[*] Creating index.html from {:?} ✓",
                                  book.get_dest().join(&ch.path.with_extension("html")));
                            index = false;
                        }
                    }
                },
                _ => {},
            }
        }

        // Print version

        // Update the context with data for this file
        data.insert("path".to_owned(), json!("print.md"));
        data.insert("content".to_owned(), json!(print_content));
        data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(Path::new("print.md"))));

        // Render the handlebars template with the data
        debug!("[*]: Render template");

        let rendered = try!(handlebars.render("index", &data));
        let rendered = build_header_links(rendered, "print.html");
        let rendered = fix_anchor_links(rendered, "print.html");

        // fix code blocks
        let rendered = fix_code_blocks(rendered);

        try!(book.write_file(Path::new("print").with_extension("html"), &rendered.into_bytes()));
        info!("[*] Creating print.html ✓");

        // Copy static files (js, css, images, ...)

        debug!("[*] Copy static files");
        try!(book.write_file("book.js", &theme.js));
        try!(book.write_file("book.css", &theme.css));
        try!(book.write_file("favicon.png", &theme.favicon));
        try!(book.write_file("jquery.js", &theme.jquery));
        try!(book.write_file("highlight.css", &theme.highlight_css));
        try!(book.write_file("tomorrow-night.css", &theme.tomorrow_night_css));
        try!(book.write_file("highlight.js", &theme.highlight_js));
        try!(book.write_file("_FontAwesome/css/font-awesome.css", theme::FONT_AWESOME));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.eot", theme::FONT_AWESOME_EOT));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.svg", theme::FONT_AWESOME_SVG));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.ttf", theme::FONT_AWESOME_TTF));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff", theme::FONT_AWESOME_WOFF));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff2", theme::FONT_AWESOME_WOFF2));
        try!(book.write_file("_FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF));

        // Copy all remaining files
        try!(utils::fs::copy_files_except_ext(book.get_src(), book.get_dest(), true, &["md"]));

        Ok(())
    }
}

fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>, Box<Error>> {
    debug!("[fn]: make_data");

    let mut data = serde_json::Map::new();
    data.insert("language".to_owned(), json!("en"));
    data.insert("title".to_owned(), json!(book.get_title()));
    data.insert("description".to_owned(), json!(book.get_description()));
    data.insert("favicon".to_owned(), json!("favicon.png"));
    if let Some(livereload) = book.get_livereload() {
        data.insert("livereload".to_owned(), json!(livereload));
    }

    let mut chapters = vec![];

    for item in book.iter() {
        // Create the data to inject in the template
        let mut chapter = BTreeMap::new();

        match *item {
            BookItem::Affix(ref ch) => {
                chapter.insert("name".to_owned(), json!(ch.name));
                let path = ch.path.to_str().ok_or_else(||
                    io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                chapter.insert("path".to_owned(), json!(path));
            },
            BookItem::Chapter(ref s, ref ch) => {
                chapter.insert("section".to_owned(), json!(s));
                chapter.insert("name".to_owned(), json!(ch.name));
                let path = ch.path.to_str().ok_or_else(||
                    io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                chapter.insert("path".to_owned(), json!(path));
            },
            BookItem::Spacer => {
                chapter.insert("spacer".to_owned(), json!("_spacer_"));
            },

        }

        chapters.push(chapter);
    }

    data.insert("chapters".to_owned(), json!(chapters));

    debug!("[*]: JSON constructed");
    Ok(data)
}

fn build_header_links(html: String, filename: &str) -> String {
    let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();

    regex.replace_all(&html, |caps: &Captures| {
        let level = &caps[1];
        let text = &caps[2];
        let mut id = text.to_string();
        let repl_sub = vec!["", "", "", "",
                            "", "",
                            "<", ">", "&", "'", """];
        for sub in repl_sub {
            id = id.replace(sub, "");
        }
        let id = id.chars().filter_map(|c| {
            if c.is_alphanumeric() || c == '-' || c == '_' {
                if c.is_ascii() {
                    Some(c.to_ascii_lowercase())
                } else {
                    Some(c)
                }
            } else if c.is_whitespace() && c.is_ascii() {
                Some('-')
            } else {
                None
            }
        }).collect::<String>();

        format!("<h{level}>{text}</h{level}>",
            level=level, id=id, text=text, filename=filename)
    }).into_owned()
}

// anchors to the same page (href="#anchor") do not work because of
// <base href="../"> pointing to the root folder. This function *fixes*
// that in a very inelegant way
fn fix_anchor_links(html: String, filename: &str) -> String {
    let regex = Regex::new(r##"]+)href="#([^"]+)"([^>]*)>"##).unwrap();
    regex.replace_all(&html, |caps: &Captures| {
        let before = &caps[1];
        let anchor = &caps[2];
        let after = &caps[3];

        format!("",
            before=before, filename=filename, anchor=anchor, after=after)
    }).into_owned()
}

// The rust book uses annotations for rustdoc to test code snippets, like the following:
// ```rust,should_panic
// fn main() {
//     // Code here
// }
// ```
// This function replaces all commas by spaces in the code block classes
fn fix_code_blocks(html: String) -> String {
    let regex = Regex::new(r##"]+)class="([^"]+)"([^>]*)>"##).unwrap();
    regex.replace_all(&html, |caps: &Captures| {
        let before = &caps[1];
        let classes = &caps[2].replace(",", " ");
        let after = &caps[3];

        format!("", before=before, classes=classes, after=after)
    }).into_owned()
}
<|endoftext|>


<commit_before>use renderer::html_handlebars::helpers;
use renderer::Renderer;
use book::MDBook;
use book::bookitem::BookItem;
use {utils, theme};
use regex::{Regex, Captures};

use std::ascii::AsciiExt;
use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::error::Error;
use std::io::{self, Read};
use std::collections::BTreeMap;

use handlebars::Handlebars;

use serde_json;

#[derive(Default)]
pub struct HtmlHandlebars;

impl HtmlHandlebars {
    pub fn new() -> Self {
        HtmlHandlebars
    }
}

impl Renderer for HtmlHandlebars {
    fn render(&self, book: &MDBook) -> Result<(), Box<Error>> {
        debug!("[fn]: render");
        let mut handlebars = Handlebars::new();

        // Load theme
        let theme = theme::Theme::new(book.get_theme_path());

        // Register template
        debug!("[*]: Register handlebars template");
        try!(handlebars.register_template_string("index", try!(String::from_utf8(theme.index))));

        // Register helpers
        debug!("[*]: Register handlebars helpers");
        handlebars.register_helper("toc", Box::new(helpers::toc::RenderToc));
        handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
        handlebars.register_helper("next", Box::new(helpers::navigation::next));

        let mut data = try!(make_data(book));

        // Print version
        let mut print_content: String = String::new();

        // Check if dest directory exists
        debug!("[*]: Check if destination directory exists");
        if fs::create_dir_all(book.get_dest()).is_err() {
            return Err(Box::new(io::Error::new(io::ErrorKind::Other,
                                               "Unexpected error when constructing destination path")));
        }

        // Render a file for every entry in the book
        let mut index = true;
        for item in book.iter() {

            match *item {
                BookItem::Chapter(_, ref ch) |
                BookItem::Affix(ref ch) => {
                    if ch.path != PathBuf::new() {

                        let path = book.get_src().join(&ch.path);

                        debug!("[*]: Opening file: {:?}", path);
                        let mut f = try!(File::open(&path));
                        let mut content: String = String::new();

                        debug!("[*]: Reading file");
                        try!(f.read_to_string(&mut content));

                        // Parse for playpen links
                        if let Some(p) = path.parent() {
                            content = helpers::playpen::render_playpen(&content, p);
                        }

                        // Render markdown using the pulldown-cmark crate
                        content = utils::render_markdown(&content);
                        print_content.push_str(&content);

                        // Update the context with data for this file
                        let path = ch.path.to_str().ok_or_else(||
                            io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                        data.insert("path".to_owned(), json!(path));
                        data.insert("content".to_owned(), json!(content));
                        data.insert("chapter_title".to_owned(), json!(ch.name));
                        data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(&ch.path)));

                        // Render the handlebars template with the data
                        debug!("[*]: Render template");
                        let rendered = try!(handlebars.render("index", &data));

                        let filename = Path::new(&ch.path).with_extension("html");

                        // Do several kinds of post-processing
                        let rendered = build_header_links(rendered, filename.to_str().unwrap_or(""));
                        let rendered = fix_anchor_links(rendered, filename.to_str().unwrap_or(""));
                        let rendered = fix_code_blocks(rendered);
                        let rendered = add_playpen_pre(rendered);

                        // Write to file
                        info!("[*] Creating {:?} ✓", filename.display());
                        try!(book.write_file(filename, &rendered.into_bytes()));

                        // Create an index.html from the first element in SUMMARY.md
                        if index {
                            debug!("[*]: index.html");

                            let mut content = String::new();
                            let _source = try!(File::open(book.get_dest().join(&ch.path.with_extension("html"))))
                                .read_to_string(&mut content);

                            // This could cause a problem when someone displays code containing <base href=...>
                            // on the front page, however this case should be very very rare...
                            content = content.lines()
                                .filter(|line| !line.contains("<base href="))
                                .collect::<Vec<&str>>()
                                .join("\n");

                            try!(book.write_file("index.html", content.as_bytes()));

                            info!("[*] Creating index.html from {:?} ✓",
                                  book.get_dest().join(&ch.path.with_extension("html")));
                            index = false;
                        }
                    }
                },
                _ => {},
            }
        }

        // Print version

        // Update the context with data for this file
        data.insert("path".to_owned(), json!("print.md"));
        data.insert("content".to_owned(), json!(print_content));
        data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(Path::new("print.md"))));

        // Render the handlebars template with the data
        debug!("[*]: Render template");

        let rendered = try!(handlebars.render("index", &data));

        // do several kinds of post-processing
        let rendered = build_header_links(rendered, "print.html");
        let rendered = fix_anchor_links(rendered, "print.html");
        let rendered = fix_code_blocks(rendered);
        let rendered = add_playpen_pre(rendered);

        try!(book.write_file(Path::new("print").with_extension("html"), &rendered.into_bytes()));
        info!("[*] Creating print.html ✓");

        // Copy static files (js, css, images, ...)

        debug!("[*] Copy static files");
        try!(book.write_file("book.js", &theme.js));
        try!(book.write_file("book.css", &theme.css));
        try!(book.write_file("favicon.png", &theme.favicon));
        try!(book.write_file("jquery.js", &theme.jquery));
        try!(book.write_file("highlight.css", &theme.highlight_css));
        try!(book.write_file("tomorrow-night.css", &theme.tomorrow_night_css));
        try!(book.write_file("highlight.js", &theme.highlight_js));
        try!(book.write_file("_FontAwesome/css/font-awesome.css", theme::FONT_AWESOME));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.eot", theme::FONT_AWESOME_EOT));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.svg", theme::FONT_AWESOME_SVG));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.ttf", theme::FONT_AWESOME_TTF));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff", theme::FONT_AWESOME_WOFF));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff2", theme::FONT_AWESOME_WOFF2));
        try!(book.write_file("_FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF));

        // Copy all remaining files
        try!(utils::fs::copy_files_except_ext(book.get_src(), book.get_dest(), true, &["md"]));

        Ok(())
    }
}

fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>, Box<Error>> {
    debug!("[fn]: make_data");

    let mut data = serde_json::Map::new();
    data.insert("language".to_owned(), json!("en"));
    data.insert("title".to_owned(), json!(book.get_title()));
    data.insert("description".to_owned(), json!(book.get_description()));
    data.insert("favicon".to_owned(), json!("favicon.png"));
    if let Some(livereload) = book.get_livereload() {
        data.insert("livereload".to_owned(), json!(livereload));
    }

    let mut chapters = vec![];

    for item in book.iter() {
        // Create the data to inject in the template
        let mut chapter = BTreeMap::new();

        match *item {
            BookItem::Affix(ref ch) => {
                chapter.insert("name".to_owned(), json!(ch.name));
                let path = ch.path.to_str().ok_or_else(||
                    io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                chapter.insert("path".to_owned(), json!(path));
            },
            BookItem::Chapter(ref s, ref ch) => {
                chapter.insert("section".to_owned(), json!(s));
                chapter.insert("name".to_owned(), json!(ch.name));
                let path = ch.path.to_str().ok_or_else(||
                    io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                chapter.insert("path".to_owned(), json!(path));
            },
            BookItem::Spacer => {
                chapter.insert("spacer".to_owned(), json!("_spacer_"));
            },

        }

        chapters.push(chapter);
    }

    data.insert("chapters".to_owned(), json!(chapters));

    debug!("[*]: JSON constructed");
    Ok(data)
}

fn build_header_links(html: String, filename: &str) -> String {
    let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();

    regex.replace_all(&html, |caps: &Captures| {
        let level = &caps[1];
        let text = &caps[2];
        let mut id = text.to_string();
        let repl_sub = vec!["", "", "", "",
                            "", "",
                            "<", ">", "&", "'", """];
        for sub in repl_sub {
            id = id.replace(sub, "");
        }
        let id = id.chars().filter_map(|c| {
            if c.is_alphanumeric() || c == '-' || c == '_' {
                if c.is_ascii() {
                    Some(c.to_ascii_lowercase())
                } else {
                    Some(c)
                }
            } else if c.is_whitespace() && c.is_ascii() {
                Some('-')
            } else {
                None
            }
        }).collect::<String>();

        format!("<h{level}>{text}</h{level}>",
            level=level, id=id, text=text, filename=filename)
    }).into_owned()
}

// anchors to the same page (href="#anchor") do not work because of
// <base href="../"> pointing to the root folder. This function *fixes*
// that in a very inelegant way
fn fix_anchor_links(html: String, filename: &str) -> String {
    let regex = Regex::new(r##"]+)href="#([^"]+)"([^>]*)>"##).unwrap();
    regex.replace_all(&html, |caps: &Captures| {
        let before = &caps[1];
        let anchor = &caps[2];
        let after = &caps[3];

        format!("",
            before=before, filename=filename, anchor=anchor, after=after)
    }).into_owned()
}

// The rust book uses annotations for rustdoc to test code snippets, like the following:
// ```rust,should_panic
// fn main() {
//     // Code here
// }
// ```
// This function replaces all commas by spaces in the code block classes
fn fix_code_blocks(html: String) -> String {
    let regex = Regex::new(r##"]+)class="([^"]+)"([^>]*)>"##).unwrap();
    regex.replace_all(&html, |caps: &Captures| {
        let before = &caps[1];
        let classes = &caps[2].replace(",", " ");
        let after = &caps[3];

        format!("", before=before, classes=classes, after=after)
    }).into_owned()
}

fn add_playpen_pre(html: String) -> String {
    let regex = Regex::new(r##"((?s)]?class="([^"]+)".*?>(.*?))"##).unwrap();
    regex.replace_all(&html, |caps: &Captures| {
        let text = &caps[1];
        let classes = &caps[2];
        let code = &caps[3];

        if classes.contains("language-rust") && !classes.contains("ignore") {
            // wrap the contents in an external pre block

            if text.contains("fn main") {
                format!("
{}
", text)
            } else {
                // we need to inject our own main
                format!("
#fn main() {{
{}
#}}
", classes, code)
            }
        } else {
            // not language-rust, so no-op
            format!("{}", text)
        }
    }).into_owned()
}
<commit_msg>inject allow(unused_variables)<commit_after>use renderer::html_handlebars::helpers;
use renderer::Renderer;
use book::MDBook;
use book::bookitem::BookItem;
use {utils, theme};
use regex::{Regex, Captures};

use std::ascii::AsciiExt;
use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::error::Error;
use std::io::{self, Read};
use std::collections::BTreeMap;

use handlebars::Handlebars;

use serde_json;

#[derive(Default)]
pub struct HtmlHandlebars;

impl HtmlHandlebars {
    pub fn new() -> Self {
        HtmlHandlebars
    }
}

impl Renderer for HtmlHandlebars {
    fn render(&self, book: &MDBook) -> Result<(), Box<Error>> {
        debug!("[fn]: render");
        let mut handlebars = Handlebars::new();

        // Load theme
        let theme = theme::Theme::new(book.get_theme_path());

        // Register template
        debug!("[*]: Register handlebars template");
        try!(handlebars.register_template_string("index", try!(String::from_utf8(theme.index))));

        // Register helpers
        debug!("[*]: Register handlebars helpers");
        handlebars.register_helper("toc", Box::new(helpers::toc::RenderToc));
        handlebars.register_helper("previous", Box::new(helpers::navigation::previous));
        handlebars.register_helper("next", Box::new(helpers::navigation::next));

        let mut data = try!(make_data(book));

        // Print version
        let mut print_content: String = String::new();

        // Check if dest directory exists
        debug!("[*]: Check if destination directory exists");
        if fs::create_dir_all(book.get_dest()).is_err() {
            return Err(Box::new(io::Error::new(io::ErrorKind::Other,
                                               "Unexpected error when constructing destination path")));
        }

        // Render a file for every entry in the book
        let mut index = true;
        for item in book.iter() {

            match *item {
                BookItem::Chapter(_, ref ch) |
                BookItem::Affix(ref ch) => {
                    if ch.path != PathBuf::new() {

                        let path = book.get_src().join(&ch.path);

                        debug!("[*]: Opening file: {:?}", path);
                        let mut f = try!(File::open(&path));
                        let mut content: String = String::new();

                        debug!("[*]: Reading file");
                        try!(f.read_to_string(&mut content));

                        // Parse for playpen links
                        if let Some(p) = path.parent() {
                            content = helpers::playpen::render_playpen(&content, p);
                        }

                        // Render markdown using the pulldown-cmark crate
                        content = utils::render_markdown(&content);
                        print_content.push_str(&content);

                        // Update the context with data for this file
                        let path = ch.path.to_str().ok_or_else(||
                            io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                        data.insert("path".to_owned(), json!(path));
                        data.insert("content".to_owned(), json!(content));
                        data.insert("chapter_title".to_owned(), json!(ch.name));
                        data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(&ch.path)));

                        // Render the handlebars template with the data
                        debug!("[*]: Render template");
                        let rendered = try!(handlebars.render("index", &data));

                        let filename = Path::new(&ch.path).with_extension("html");

                        // Do several kinds of post-processing
                        let rendered = build_header_links(rendered, filename.to_str().unwrap_or(""));
                        let rendered = fix_anchor_links(rendered, filename.to_str().unwrap_or(""));
                        let rendered = fix_code_blocks(rendered);
                        let rendered = add_playpen_pre(rendered);

                        // Write to file
                        info!("[*] Creating {:?} ✓", filename.display());
                        try!(book.write_file(filename, &rendered.into_bytes()));

                        // Create an index.html from the first element in SUMMARY.md
                        if index {
                            debug!("[*]: index.html");

                            let mut content = String::new();
                            let _source = try!(File::open(book.get_dest().join(&ch.path.with_extension("html"))))
                                .read_to_string(&mut content);

                            // This could cause a problem when someone displays code containing <base href=...>
                            // on the front page, however this case should be very very rare...
                            content = content.lines()
                                .filter(|line| !line.contains("<base href="))
                                .collect::<Vec<&str>>()
                                .join("\n");

                            try!(book.write_file("index.html", content.as_bytes()));

                            info!("[*] Creating index.html from {:?} ✓",
                                  book.get_dest().join(&ch.path.with_extension("html")));
                            index = false;
                        }
                    }
                },
                _ => {},
            }
        }

        // Print version

        // Update the context with data for this file
        data.insert("path".to_owned(), json!("print.md"));
        data.insert("content".to_owned(), json!(print_content));
        data.insert("path_to_root".to_owned(), json!(utils::fs::path_to_root(Path::new("print.md"))));

        // Render the handlebars template with the data
        debug!("[*]: Render template");

        let rendered = try!(handlebars.render("index", &data));

        // do several kinds of post-processing
        let rendered = build_header_links(rendered, "print.html");
        let rendered = fix_anchor_links(rendered, "print.html");
        let rendered = fix_code_blocks(rendered);
        let rendered = add_playpen_pre(rendered);

        try!(book.write_file(Path::new("print").with_extension("html"), &rendered.into_bytes()));
        info!("[*] Creating print.html ✓");

        // Copy static files (js, css, images, ...)

        debug!("[*] Copy static files");
        try!(book.write_file("book.js", &theme.js));
        try!(book.write_file("book.css", &theme.css));
        try!(book.write_file("favicon.png", &theme.favicon));
        try!(book.write_file("jquery.js", &theme.jquery));
        try!(book.write_file("highlight.css", &theme.highlight_css));
        try!(book.write_file("tomorrow-night.css", &theme.tomorrow_night_css));
        try!(book.write_file("highlight.js", &theme.highlight_js));
        try!(book.write_file("_FontAwesome/css/font-awesome.css", theme::FONT_AWESOME));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.eot", theme::FONT_AWESOME_EOT));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.svg", theme::FONT_AWESOME_SVG));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.ttf", theme::FONT_AWESOME_TTF));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff", theme::FONT_AWESOME_WOFF));
        try!(book.write_file("_FontAwesome/fonts/fontawesome-webfont.woff2", theme::FONT_AWESOME_WOFF2));
        try!(book.write_file("_FontAwesome/fonts/FontAwesome.ttf", theme::FONT_AWESOME_TTF));

        // Copy all remaining files
        try!(utils::fs::copy_files_except_ext(book.get_src(), book.get_dest(), true, &["md"]));

        Ok(())
    }
}

fn make_data(book: &MDBook) -> Result<serde_json::Map<String, serde_json::Value>, Box<Error>> {
    debug!("[fn]: make_data");

    let mut data = serde_json::Map::new();
    data.insert("language".to_owned(), json!("en"));
    data.insert("title".to_owned(), json!(book.get_title()));
    data.insert("description".to_owned(), json!(book.get_description()));
    data.insert("favicon".to_owned(), json!("favicon.png"));
    if let Some(livereload) = book.get_livereload() {
        data.insert("livereload".to_owned(), json!(livereload));
    }

    let mut chapters = vec![];

    for item in book.iter() {
        // Create the data to inject in the template
        let mut chapter = BTreeMap::new();

        match *item {
            BookItem::Affix(ref ch) => {
                chapter.insert("name".to_owned(), json!(ch.name));
                let path = ch.path.to_str().ok_or_else(||
                    io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                chapter.insert("path".to_owned(), json!(path));
            },
            BookItem::Chapter(ref s, ref ch) => {
                chapter.insert("section".to_owned(), json!(s));
                chapter.insert("name".to_owned(), json!(ch.name));
                let path = ch.path.to_str().ok_or_else(||
                    io::Error::new(io::ErrorKind::Other, "Could not convert path to str"))?;
                chapter.insert("path".to_owned(), json!(path));
            },
            BookItem::Spacer => {
                chapter.insert("spacer".to_owned(), json!("_spacer_"));
            },

        }

        chapters.push(chapter);
    }

    data.insert("chapters".to_owned(), json!(chapters));

    debug!("[*]: JSON constructed");
    Ok(data)
}

fn build_header_links(html: String, filename: &str) -> String {
    let regex = Regex::new(r"<h(\d)>(.*?)</h\d>").unwrap();

    regex.replace_all(&html, |caps: &Captures| {
        let level = &caps[1];
        let text = &caps[2];
        let mut id = text.to_string();
        let repl_sub = vec!["", "", "", "",
                            "", "",
                            "<", ">", "&", "'", """];
        for sub in repl_sub {
            id = id.replace(sub, "");
        }
        let id = id.chars().filter_map(|c| {
            if c.is_alphanumeric() || c == '-' || c == '_' {
                if c.is_ascii() {
                    Some(c.to_ascii_lowercase())
                } else {
                    Some(c)
                }
            } else if c.is_whitespace() && c.is_ascii() {
                Some('-')
            } else {
                None
            }
        }).collect::<String>();

        format!("<h{level}>{text}</h{level}>",
            level=level, id=id, text=text, filename=filename)
    }).into_owned()
}

// anchors to the same page (href="#anchor") do not work because of
// <base href="../"> pointing to the root folder. This function *fixes*
// that in a very inelegant way
fn fix_anchor_links(html: String, filename: &str) -> String {
    let regex = Regex::new(r##"]+)href="#([^"]+)"([^>]*)>"##).unwrap();
    regex.replace_all(&html, |caps: &Captures| {
        let before = &caps[1];
        let anchor = &caps[2];
        let after = &caps[3];

        format!("",
            before=before, filename=filename, anchor=anchor, after=after)
    }).into_owned()
}

// The rust book uses annotations for rustdoc to test code snippets, like the following:
// ```rust,should_panic
// fn main() {
//     // Code here
// }
// ```
// This function replaces all commas by spaces in the code block classes
fn fix_code_blocks(html: String) -> String {
    let regex = Regex::new(r##"]+)class="([^"]+)"([^>]*)>"##).unwrap();
    regex.replace_all(&html, |caps: &Captures| {
        let before = &caps[1];
        let classes = &caps[2].replace(",", " ");
        let after = &caps[3];

        format!("", before=before, classes=classes, after=after)
    }).into_owned()
}

fn add_playpen_pre(html: String) -> String {
    let regex = Regex::new(r##"((?s)]?class="([^"]+)".*?>(.*?))"##).unwrap();
    regex.replace_all(&html, |caps: &Captures| {
        let text = &caps[1];
        let classes = &caps[2];
        let code = &caps[3];

        if classes.contains("language-rust") && !classes.contains("ignore") {
            // wrap the contents in an external pre block

            if text.contains("fn main") {
                format!("
{}
", text)
            } else {
                // we need to inject our own main
                format!("
# #![allow(unused_variables)]
#
#fn main() {{
{}
#}}
", classes, code)
            }
        } else {
            // not language-rust, so no-op
            format!("{}", text)
        }
    }).into_owned()
}
<|endoftext|>


<commit_before>#![crate_id = "argparse"]
#![crate_type = "lib"]
#![feature(struct_variant)]

use std::os;

pub enum Action<'a> {
    StoreTrue(&'a mut bool),
    StoreFalse(&'a mut bool),
}

pub enum Argument<'a> {
    DashOption {
        options: &'a[&'a str],
        help: &'a str,
        metavar: &'a str,
        action: Action<'a>,
    },
    Positional {
        name: &'a str,
        help: &'a str,
        action: Action<'a>,
    },
}

pub struct ArgumentParser<'a> {
    priv arguments: Vec<Argument<'a>>,
}

impl<'a> ArgumentParser<'a> {
    fn new() -> ArgumentParser {
        return ArgumentParser { arguments: Vec::new() };
    }
    fn add(&mut self, arg: Argument<'a>) {
        self.arguments.push(arg);
    }
    fn parse_args(&self, args: ~[~str]) {
    }
}

#[test]
fn test_no_arg() {
    let ap = ArgumentParser::new();
    ap.parse_args(os::args());
}
<commit_msg>Boolean options in long form work<commit_after>#![crate_id = "argparse"]
#![crate_type = "lib"]

use std::os;
use std::rc::Rc;
use std::cell::RefCell;
use std::slice::Items;

pub enum Action<'a> {
    StoreTrue(Rc<RefCell<&'a mut bool>>),
    StoreFalse(Rc<RefCell<&'a mut bool>>),
}

struct DashOption<'a> {
    options: ~[&'a str],
    help: &'a str,
    action: Action<'a>,
}

struct Argument<'a> {
    name: &'a str,
    help: &'a str,
    action: Action<'a>,
}

pub struct ArgumentParser<'a> {
    priv options: ~[DashOption<'a>],
    priv arguments: ~[Argument<'a>],
}

struct Context<'a, 'b> {
    //parser: &'a ArgumentParser<'b>,
    options: &'a [DashOption<'b>],
    arguments: &'a [Argument<'b>],
    iter: Items<'a, ~str>,
}

impl<'a, 'b> Context<'a, 'b> {
    fn parse_argument(&mut self, arg: &str) {
    }

    fn parse_option(&mut self, opt: &DashOption) {
        match opt.action {
            StoreTrue(ref cell) => {
                **cell.borrow_mut() = true;
            }
            StoreFalse(ref cell) => {
                **cell.borrow_mut() = false;
            }
        }
    }

    fn parse_long_option<'c>(&'c mut self, arg: &str) {
        for opt in self.options.iter() {
            for tname in opt.options.iter() {
                if arg.eq(tname) {
                    self.parse_option(opt);
                    return;
                }
            }
        }
    }

    fn new<'c, 'd>(parser: &'c ArgumentParser<'d>, args: &'c[~str])
        -> Context<'c, 'd>
    {
        return Context {
            options: parser.options,
            arguments: parser.arguments,
            iter: args.iter(),
        };
    }

    fn parse(&mut self) {
        loop {
            let next = self.iter.next();
            match next {
                Some(arg) => {
                    // positional args: ^$, ^-$, ^[^-].*
                    if arg.len() < 2 || arg[0] != ('-' as u8) {
                        self.parse_argument(*arg);
                    } else if arg[1] == ('-' as u8) {
                        self.parse_long_option(*arg);
                    }
                }
                None => {
                    break;
                }
            }
        }
    }
}

impl<'a> ArgumentParser<'a> {
    fn new() -> ArgumentParser {
        return ArgumentParser {
            arguments: ~[],
            options: ~[],
            };
    }
    fn add_option<'c>(&'c mut self, names: ~[&'a str],
        help: &'a str, action: Action<'a>) {
        self.options.push(DashOption {
            options: names,
            help: help,
            action: action,
            });
    }
    /*
    fn add_argument<'b>(&'b mut self, name: &'a str,
        help: &'a str, action: Action<'a>) {
        self.arguments.push(Argument {
            name: name,
            help: help,
            action: action,
        })
    }
    */

    fn parse_args(&self, args: ~[~str]) {
        Context::new(self, args).parse();
    }
}

fn cell<'a, T>(val: &'a mut T) -> Rc<RefCell<&'a mut T>> {
    return Rc::new(RefCell::new(val));
}

#[test]
fn test_no_arg() {
    let ap = ArgumentParser::new();
    ap.parse_args(os::args());
}

#[test]
fn test_store_true() {
    let mut verbose = false;
    let mut ap = ArgumentParser::new();
    ap.add_option(~["-t", "--true"],
        "Store true action",
        StoreTrue(cell(&mut verbose)));
    assert!(!verbose);
    ap.parse_args(~[~"./argparse_test"]);
    assert!(!verbose);
    ap.parse_args(~[~"./argparse_test", ~"--true"]);
    assert!(verbose);
}

#[test]
fn test_store_false() {
    let mut verbose = true;
    let mut ap = ArgumentParser::new();
    ap.add_option(~["-f", "--false"],
        "Store false action",
        StoreFalse(cell(&mut verbose)));
    assert!(verbose);
    ap.parse_args(~[~"./argparse_test"]);
    assert!(verbose);
    ap.parse_args(~[~"./argparse_test", ~"--false"]);
    assert!(!verbose);
}

/*
#[test]
fn test_verbose() {
    let mut opt = Options {
        verbose: false,
        debug: false,
        };
    {
        let mut ap = ArgumentParser::new();
        let vcell = cell(&mut opt.verbose);
        ap.add_option(~["-v", "--verbose"],
            "Turn on verbose mode",
            StoreTrue(vcell.clone()));
        ap.add_option(~["-q", "--quiet"],
            "Turn on quiet mode",
            StoreFalse(vcell.clone()));
        ap.add_option(~["-d", "--debug"],
            "Enable debugging",
            StoreTrue(cell(&mut opt.debug)));
        ap.parse_args(~[~"./argparse_test", ~"-v"]);
    }
    assert!(opt.verbose);
    assert!(opt.debug);
}

*/
<|endoftext|>


<commit_before>use anyhow::Result;
use log::warn;
use std::{collections::HashMap, convert::From, fmt, iter};
use thiserror::Error;
use uuid::Uuid;

#[derive(Debug, Error, Clone)]
pub enum Error {
    #[error("UniqueViolation")]
    UniqueViolation,
    #[error("NoTestQuery")]
    NoTestQuery { cmd: String },
    #[error("NoTestExecute")]
    NoTestExecute { cmd: String },
    #[error("PostgresError")]
    PostgresError { query: String, cause: String },
    #[error("R2D2Error")]
    R2D2Error { cause: String },
    #[error("NoSuchTable")]
    NoSuchTable,
    #[error("Value was NULL")]
    NullValue,
}

#[derive(Debug)]
pub struct HexSlice(Vec<u8>);

impl HexSlice {
    pub fn new(data: Vec<u8>) -> HexSlice {
        HexSlice(data)
    }

    pub fn sql(&self) -> String {
        format!("decode('{}', 'hex')", self)
    }
}

impl fmt::Display for HexSlice {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for byte in self.0.iter() {
            write!(f, "{:02X}", byte)?;
        }
        Ok(())
    }
}

pub trait FromSql {}
impl<T: FromSql> FromSql for Option<T> {}
impl FromSql for u32 {}
impl FromSql for Uuid {}
impl FromSql for String {}
impl<'a> FromSql for &'a str {}
impl FromSql for Vec<u8> {}
impl FromSql for serde_json::Value {}

#[derive(Debug, Clone)]
pub enum SqlValue {
    Null,
    U32(u32),
    UUID(Uuid),
    String(String),
    U8Bytes(Vec<u8>),
    JSON(serde_json::Value),
}

impl From<u32> for SqlValue {
    fn from(i: u32) -> SqlValue {
        SqlValue::U32(i)
    }
}

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub enum ValueIndex {
    U32(u32),
    String(String),
}

impl<'a> From<&'a str> for ValueIndex {
    fn from(s: &str) -> ValueIndex {
        ValueIndex::String(String::from(s))
    }
}

pub trait RowIndex {
    fn val(&self) -> ValueIndex;
}
impl RowIndex for u32 {
    fn val(&self) -> ValueIndex {
        ValueIndex::U32(*self)
    }
}
impl RowIndex for String {
    fn val(&self) -> ValueIndex {
        ValueIndex::String(self.clone())
    }
}
impl<'a> RowIndex for &'a str {
    fn val(&self) -> ValueIndex {
        ValueIndex::String(String::from(*self))
    }
}

#[derive(Debug, Clone, Default)]
pub struct TestRow {
    data: HashMap<ValueIndex, SqlValue>,
}

pub trait GetRow<T> {
    fn get<R>(&self, id: R) -> T
    where
        T: FromSql,
        R: RowIndex + fmt::Display;
}

impl TestRow {
    pub fn new() -> TestRow {
        TestRow { data: HashMap::new() }
    }

    pub fn insert<K, V>(&mut self, k: K, v: V) -> Option<SqlValue>
    where
        K: Into<ValueIndex>,
        V: Into<SqlValue>,
    {
        self.data.insert(k.into(), v.into())
    }
}

macro_rules! get_row {
    ($type: ty, $kind: path) => {
        impl GetRow<$type> for TestRow {
            fn get<R>(&self, id: R) -> $type
            where
                R: RowIndex + fmt::Display,
            {
                if !self.data.contains_key(&id.val()) {
                    panic!("Can't find key {} in row", id);
                }
                match self.data[&id.val()] {
                    $kind(ref val) => val.clone(),
                    _ => panic!(),
                }
            }
        }

        impl GetRow<Option<$type>> for TestRow {
            fn get<R>(&self, id: R) -> Option<$type>
            where
                R: RowIndex + fmt::Display,
            {
                if !self.data.contains_key(&id.val()) {
                    panic!("Can't find key {} in row", id);
                }
                match self.data[&id.val()] {
                    $kind(ref val) => Some(val.clone()),
                    SqlValue::Null => None,
                    _ => panic!(),
                }
            }
        }
    };
}

get_row!(u32, SqlValue::U32);
get_row!(Uuid, SqlValue::UUID);
get_row!(String, SqlValue::String);
get_row!(serde_json::Value, SqlValue::JSON);
get_row!(Vec<u8>, SqlValue::U8Bytes);

#[derive(Debug)]
pub enum Row<'a> {
    Postgres(postgres::rows::Row<'a>),
    Test(&'a TestRow),
}
impl<'a> Row<'a> {
    pub fn get<T, R>(&self, id: R) -> T
    where
        T: FromSql + postgres::types::FromSql,
        R: RowIndex + postgres::rows::RowIndex + fmt::Display + fmt::Debug,
        TestRow: GetRow<T>,
    {
        match *self {
            Row::Postgres(ref rows) => rows.get(id),
            Row::Test(ref rows) => rows.get(id),
        }
    }

    pub fn get_opt<T, R>(&self, id: R) -> Option<Result<T, Error>>
    where
        T: FromSql + postgres::types::FromSql,
        R: RowIndex + postgres::rows::RowIndex + fmt::Display + fmt::Debug,
        TestRow: GetRow<T>,
    {
        match *self {
            Row::Postgres(ref rows) => match rows.get_opt(id) {
                None => None,
                Some(val) => Some(val.map_err(|e| convert_postgres_error(e, ""))),
            },
            Row::Test(ref rows) => Some(Ok(rows.get(id))),
        }
    }

    pub fn get_with_null<T>(&self, index: &str) -> Option<T>
    where
        T: FromSql + postgres::types::FromSql,
        TestRow: GetRow<T>,
    {
        match self.get_opt(index) {
            Some(val) => match val {
                Ok(val) => Some(val),
                Err(Error::NullValue) => None,
                Err(err) => panic!("{:?}", err),
            },
            None => None,
        }
    }
}

#[derive(Debug)]
pub struct TestRowIterator<'a> {
    rows: &'a [TestRow],
    location: usize,
}

impl<'a> TestRowIterator<'a> {
    fn new(r: &'a [TestRow]) -> TestRowIterator<'a> {
        TestRowIterator { rows: r, location: 0 }
    }

    fn next(&mut self) -> Option<&'a TestRow> {
        self.location += 1;
        if self.location < self.rows.len() {
            Some(&self.rows[self.location])
        } else {
            None
        }
    }
}

pub enum RowIterator<'a> {
    Postgres(postgres::rows::Iter<'a>),
    Test(TestRowIterator<'a>),
}

impl<'a> fmt::Debug for RowIterator<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            RowIterator::Postgres(_) => write!(f, "RowIterator(PostgresRows)"),
            RowIterator::Test(ref rows) => write!(f, "RowIterator({:?})", rows),
        }
    }
}

impl<'a> Iterator for RowIterator<'a> {
    type Item = Row<'a>;

    fn next(&mut self) -> Option<Row<'a>> {
        match *self {
            RowIterator::Postgres(ref mut rows) => rows.next().map(Row::Postgres),
            RowIterator::Test(ref mut rows) => rows.next().map(Row::Test),
        }
    }
}

#[derive(Debug)]
pub enum Rows {
    Postgres(postgres::rows::Rows),
    Test(Vec<TestRow>),
}

impl<'stmt> Rows {
    pub fn get(&self, id: usize) -> Row {
        match *self {
            Rows::Postgres(ref rows) => Row::Postgres(rows.get(id)),
            Rows::Test(ref rows) => Row::Test(&rows[id]),
        }
    }
    pub fn is_empty(&self) -> bool {
        match *self {
            Rows::Postgres(ref rows) => rows.is_empty(),
            Rows::Test(ref rows) => rows.is_empty(),
        }
    }
    pub fn len(&self) -> usize {
        match *self {
            Rows::Postgres(ref rows) => rows.len(),
            Rows::Test(ref rows) => rows.len(),
        }
    }
    pub fn iter(&self) -> RowIterator {
        match *self {
            Rows::Postgres(ref rows) => RowIterator::Postgres(rows.iter()),
            Rows::Test(ref rows) => RowIterator::Test(TestRowIterator::new(rows)),
        }
    }
}

impl<'a> iter::IntoIterator for &'a Rows {
    type Item = Row<'a>;
    type IntoIter = RowIterator<'a>;
    fn into_iter(self) -> RowIterator<'a> {
        self.iter()
    }
}

#[derive(Debug, Default)]
pub struct TestConnection {
    query_results: Vec<(regex::Regex, Result<Vec<TestRow>, Error>)>,
    execute_results: Vec<(regex::Regex, Result<u64, Error>)>,
}

impl Clone for TestConnection {
    fn clone(&self) -> Self {
        todo!()
    }
}

impl TestConnection {
    pub fn new() -> TestConnection {
        TestConnection {
            query_results: Vec::new(),
            execute_results: Vec::new(),
        }
    }

    pub fn add_test_query(&mut self, cmd: &str, results: Vec<TestRow>) {
        self.query_results.push((regex::Regex::new(cmd).unwrap(), Ok(results)));
    }

    // pub fn add_failed_query(&mut self, cmd: &str, err: ErrorKind) {
    //     self.query_results
    //         .push((regex::Regex::new(cmd).unwrap(), Err(Error::from(err))));
    // }

    pub fn add_test_execute(&mut self, cmd: &str, results: u64) {
        self.execute_results
            .push((regex::Regex::new(cmd).unwrap(), Ok(results)));
    }

    fn get_rows(&self, cmd: &str) -> Result<Vec<TestRow>, Error> {
        for &(ref patt, ref res) in self.query_results.iter() {
            if patt.is_match(cmd) {
                return match res {
                    Ok(val) => Ok(val.clone()),
                    Err(err) => Err(err.clone().into()),
                };
            }
        }
        Err(Error::NoTestQuery { cmd: String::from(cmd) }.into())
    }
    fn execute(&self, cmd: &str) -> Result<u64, Error> {
        for &(ref patt, ref res) in self.execute_results.iter() {
            if patt.is_match(cmd) {
                return match res {
                    Ok(val) => Ok(val.clone()),
                    Err(err) => Err(err.clone().into()),
                };
            }
        }
        Err(Error::NoTestExecute { cmd: String::from(cmd) }.into())
    }
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum Connection {
    Postgres(r2d2::PooledConnection<r2d2_postgres::PostgresConnectionManager>),
    Test(TestConnection),
}

fn convert_postgres_error(e: postgres_shared::error::Error, query: &str) -> Error {
    if let Some(dberror) = e.as_db() {
        if dberror.code == postgres_shared::error::UNIQUE_VIOLATION {
            return Error::UniqueViolation;
        }
    } else if let Some(converror) = e.as_conversion() {
        if converror.is::<postgres::types::WasNull>() {
            return Error::NullValue;
        }
    }
    warn!("Error: {:?}", &e);
    Error::PostgresError {
        query: query.to_string(),
        cause: e.to_string(),
    }
}

impl<'conn> Connection {
    pub fn query(&'conn self, query: &str) -> Result<Rows, Error> {
        match *self {
            Connection::Postgres(ref conn) => Ok(Rows::Postgres(
                conn.query(query, &[]).map_err(|e| convert_postgres_error(e, query))?,
            )),
            Connection::Test(ref conn) => Ok(Rows::Test(conn.get_rows(query)?)),
        }
    }
    pub fn execute(&self, equery: &str) -> Result<u64, Error> {
        match *self {
            Connection::Postgres(ref conn) => conn
                .execute(equery, &[])
                .map_err(|e| convert_postgres_error(e, equery).into()),
            Connection::Test(ref conn) => conn.execute(equery),
        }
    }
}

#[derive(Clone, Debug)]
pub enum Pool {
    Postgres(r2d2::Pool<r2d2_postgres::PostgresConnectionManager>),
    TestPool(TestConnection),
}

impl Pool {
    pub fn get(&self) -> Result<Connection> {
        match *self {
            Pool::Postgres(ref pool) => {
                let conn = pool.get().map_err(|e| Error::R2D2Error { cause: e.to_string() })?;
                Ok(Connection::Postgres(conn))
            }
            Pool::TestPool(ref conn) => Ok(Connection::Test(conn.clone())),
        }
    }

    pub fn wipe_db(&self) -> Result<()> {
        let conn = self.get()?;
        conn.execute("DROP SCHEMA public CASCADE")?;
        conn.execute("CREATE SCHEMA public")?;
        Ok(())
    }
}
<commit_msg>Fix clone issue in tests<commit_after>use anyhow::Result;
use log::warn;
use std::{collections::HashMap, convert::From, fmt, iter};
use thiserror::Error;
use uuid::Uuid;

#[derive(Debug, Error, Clone)]
pub enum Error {
    #[error("UniqueViolation")]
    UniqueViolation,
    #[error("NoTestQuery")]
    NoTestQuery { cmd: String },
    #[error("NoTestExecute")]
    NoTestExecute { cmd: String },
    #[error("PostgresError")]
    PostgresError { query: String, cause: String },
    #[error("R2D2Error")]
    R2D2Error { cause: String },
    #[error("NoSuchTable")]
    NoSuchTable,
    #[error("Value was NULL")]
    NullValue,
}

#[derive(Debug)]
pub struct HexSlice(Vec<u8>);

impl HexSlice {
    pub fn new(data: Vec<u8>) -> HexSlice {
        HexSlice(data)
    }

    pub fn sql(&self) -> String {
        format!("decode('{}', 'hex')", self)
    }
}

impl fmt::Display for HexSlice {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        for byte in self.0.iter() {
            write!(f, "{:02X}", byte)?;
        }
        Ok(())
    }
}

pub trait FromSql {}
impl<T: FromSql> FromSql for Option<T> {}
impl FromSql for u32 {}
impl FromSql for Uuid {}
impl FromSql for String {}
impl<'a> FromSql for &'a str {}
impl FromSql for Vec<u8> {}
impl FromSql for serde_json::Value {}

#[derive(Debug, Clone)]
pub enum SqlValue {
    Null,
    U32(u32),
    UUID(Uuid),
    String(String),
    U8Bytes(Vec<u8>),
    JSON(serde_json::Value),
}

impl From<u32> for SqlValue {
    fn from(i: u32) -> SqlValue {
        SqlValue::U32(i)
    }
}

#[derive(Hash, Eq, PartialEq, Clone, Debug)]
pub enum ValueIndex {
    U32(u32),
    String(String),
}

impl<'a> From<&'a str> for ValueIndex {
    fn from(s: &str) -> ValueIndex {
        ValueIndex::String(String::from(s))
    }
}

pub trait RowIndex {
    fn val(&self) -> ValueIndex;
}
impl RowIndex for u32 {
    fn val(&self) -> ValueIndex {
        ValueIndex::U32(*self)
    }
}
impl RowIndex for String {
    fn val(&self) -> ValueIndex {
        ValueIndex::String(self.clone())
    }
}
impl<'a> RowIndex for &'a str {
    fn val(&self) -> ValueIndex {
        ValueIndex::String(String::from(*self))
    }
}

#[derive(Debug, Clone, Default)]
pub struct TestRow {
    data: HashMap<ValueIndex, SqlValue>,
}

pub trait GetRow<T> {
    fn get<R>(&self, id: R) -> T
    where
        T: FromSql,
        R: RowIndex + fmt::Display;
}

impl TestRow {
    pub fn new() -> TestRow {
        TestRow { data: HashMap::new() }
    }

    pub fn insert<K, V>(&mut self, k: K, v: V) -> Option<SqlValue>
    where
        K: Into<ValueIndex>,
        V: Into<SqlValue>,
    {
        self.data.insert(k.into(), v.into())
    }
}

macro_rules! get_row {
    ($type: ty, $kind: path) => {
        impl GetRow<$type> for TestRow {
            fn get<R>(&self, id: R) -> $type
            where
                R: RowIndex + fmt::Display,
            {
                if !self.data.contains_key(&id.val()) {
                    panic!("Can't find key {} in row", id);
                }
                match self.data[&id.val()] {
                    $kind(ref val) => val.clone(),
                    _ => panic!(),
                }
            }
        }

        impl GetRow<Option<$type>> for TestRow {
            fn get<R>(&self, id: R) -> Option<$type>
            where
                R: RowIndex + fmt::Display,
            {
                if !self.data.contains_key(&id.val()) {
                    panic!("Can't find key {} in row", id);
                }
                match self.data[&id.val()] {
                    $kind(ref val) => Some(val.clone()),
                    SqlValue::Null => None,
                    _ => panic!(),
                }
            }
        }
    };
}

get_row!(u32, SqlValue::U32);
get_row!(Uuid, SqlValue::UUID);
get_row!(String, SqlValue::String);
get_row!(serde_json::Value, SqlValue::JSON);
get_row!(Vec<u8>, SqlValue::U8Bytes);

#[derive(Debug)]
pub enum Row<'a> {
    Postgres(postgres::rows::Row<'a>),
    Test(&'a TestRow),
}
impl<'a> Row<'a> {
    pub fn get<T, R>(&self, id: R) -> T
    where
        T: FromSql + postgres::types::FromSql,
        R: RowIndex + postgres::rows::RowIndex + fmt::Display + fmt::Debug,
        TestRow: GetRow<T>,
    {
        match *self {
            Row::Postgres(ref rows) => rows.get(id),
            Row::Test(ref rows) => rows.get(id),
        }
    }

    pub fn get_opt<T, R>(&self, id: R) -> Option<Result<T, Error>>
    where
        T: FromSql + postgres::types::FromSql,
        R: RowIndex + postgres::rows::RowIndex + fmt::Display + fmt::Debug,
        TestRow: GetRow<T>,
    {
        match *self {
            Row::Postgres(ref rows) => match rows.get_opt(id) {
                None => None,
                Some(val) => Some(val.map_err(|e| convert_postgres_error(e, ""))),
            },
            Row::Test(ref rows) => Some(Ok(rows.get(id))),
        }
    }

    pub fn get_with_null<T>(&self, index: &str) -> Option<T>
    where
        T: FromSql + postgres::types::FromSql,
        TestRow: GetRow<T>,
    {
        match self.get_opt(index) {
            Some(val) => match val {
                Ok(val) => Some(val),
                Err(Error::NullValue) => None,
                Err(err) => panic!("{:?}", err),
            },
            None => None,
        }
    }
}

#[derive(Debug)]
pub struct TestRowIterator<'a> {
    rows: &'a [TestRow],
    location: usize,
}

impl<'a> TestRowIterator<'a> {
    fn new(r: &'a [TestRow]) -> TestRowIterator<'a> {
        TestRowIterator { rows: r, location: 0 }
    }

    fn next(&mut self) -> Option<&'a TestRow> {
        self.location += 1;
        if self.location < self.rows.len() {
            Some(&self.rows[self.location])
        } else {
            None
        }
    }
}

pub enum RowIterator<'a> {
    Postgres(postgres::rows::Iter<'a>),
    Test(TestRowIterator<'a>),
}

impl<'a> fmt::Debug for RowIterator<'a> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            RowIterator::Postgres(_) => write!(f, "RowIterator(PostgresRows)"),
            RowIterator::Test(ref rows) => write!(f, "RowIterator({:?})", rows),
        }
    }
}

impl<'a> Iterator for RowIterator<'a> {
    type Item = Row<'a>;

    fn next(&mut self) -> Option<Row<'a>> {
        match *self {
            RowIterator::Postgres(ref mut rows) => rows.next().map(Row::Postgres),
            RowIterator::Test(ref mut rows) => rows.next().map(Row::Test),
        }
    }
}

#[derive(Debug)]
pub enum Rows {
    Postgres(postgres::rows::Rows),
    Test(Vec<TestRow>),
}

impl<'stmt> Rows {
    pub fn get(&self, id: usize) -> Row {
        match *self {
            Rows::Postgres(ref rows) => Row::Postgres(rows.get(id)),
            Rows::Test(ref rows) => Row::Test(&rows[id]),
        }
    }
    pub fn is_empty(&self) -> bool {
        match *self {
            Rows::Postgres(ref rows) => rows.is_empty(),
            Rows::Test(ref rows) => rows.is_empty(),
        }
    }
    pub fn len(&self) -> usize {
        match *self {
            Rows::Postgres(ref rows) => rows.len(),
            Rows::Test(ref rows) => rows.len(),
        }
    }
    pub fn iter(&self) -> RowIterator {
        match *self {
            Rows::Postgres(ref rows) => RowIterator::Postgres(rows.iter()),
            Rows::Test(ref rows) => RowIterator::Test(TestRowIterator::new(rows)),
        }
    }
}

impl<'a> iter::IntoIterator for &'a Rows {
    type Item = Row<'a>;
    type IntoIter = RowIterator<'a>;
    fn into_iter(self) -> RowIterator<'a> {
        self.iter()
    }
}

#[derive(Debug, Clone, Default)]
pub struct TestConnection {
    query_results: Vec<(regex::Regex, Result<Vec<TestRow>, Error>)>,
    execute_results: Vec<(regex::Regex, Result<u64, Error>)>,
}

impl TestConnection {
    pub fn new() -> TestConnection {
        TestConnection {
            query_results: Vec::new(),
            execute_results: Vec::new(),
        }
    }

    pub fn add_test_query(&mut self, cmd: &str, results: Vec<TestRow>) {
        self.query_results.push((regex::Regex::new(cmd).unwrap(), Ok(results)));
    }

    // pub fn add_failed_query(&mut self, cmd: &str, err: ErrorKind) {
    //     self.query_results
    //         .push((regex::Regex::new(cmd).unwrap(), Err(Error::from(err))));
    // }

    pub fn add_test_execute(&mut self, cmd: &str, results: u64) {
        self.execute_results
            .push((regex::Regex::new(cmd).unwrap(), Ok(results)));
    }

    fn get_rows(&self, cmd: &str) -> Result<Vec<TestRow>, Error> {
        for &(ref patt, ref res) in self.query_results.iter() {
            if patt.is_match(cmd) {
                return match res {
                    Ok(val) => Ok(val.clone()),
                    Err(err) => Err(err.clone().into()),
                };
            }
        }
        Err(Error::NoTestQuery { cmd: String::from(cmd) }.into())
    }
    fn execute(&self, cmd: &str) -> Result<u64, Error> {
        for &(ref patt, ref res) in self.execute_results.iter() {
            if patt.is_match(cmd) {
                return match res {
                    Ok(val) => Ok(val.clone()),
                    Err(err) => Err(err.clone().into()),
                };
            }
        }
        Err(Error::NoTestExecute { cmd: String::from(cmd) }.into())
    }
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum Connection {
    Postgres(r2d2::PooledConnection<r2d2_postgres::PostgresConnectionManager>),
    Test(TestConnection),
}

fn convert_postgres_error(e: postgres_shared::error::Error, query: &str) -> Error {
    if let Some(dberror) = e.as_db() {
        if dberror.code == postgres_shared::error::UNIQUE_VIOLATION {
            return Error::UniqueViolation;
        }
    } else if let Some(converror) = e.as_conversion() {
        if converror.is::<postgres::types::WasNull>() {
            return Error::NullValue;
        }
    }
    warn!("Error: {:?}", &e);
    Error::PostgresError {
        query: query.to_string(),
        cause: e.to_string(),
    }
}

impl<'conn> Connection {
    pub fn query(&'conn self, query: &str) -> Result<Rows, Error> {
        match *self {
            Connection::Postgres(ref conn) => Ok(Rows::Postgres(
                conn.query(query, &[]).map_err(|e| convert_postgres_error(e, query))?,
            )),
            Connection::Test(ref conn) => Ok(Rows::Test(conn.get_rows(query)?)),
        }
    }
    pub fn execute(&self, equery: &str) -> Result<u64, Error> {
        match *self {
            Connection::Postgres(ref conn) => conn
                .execute(equery, &[])
                .map_err(|e| convert_postgres_error(e, equery).into()),
            Connection::Test(ref conn) => conn.execute(equery),
        }
    }
}

#[derive(Clone, Debug)]
pub enum Pool {
    Postgres(r2d2::Pool<r2d2_postgres::PostgresConnectionManager>),
    TestPool(TestConnection),
}

impl Pool {
    pub fn get(&self) -> Result<Connection> {
        match *self {
            Pool::Postgres(ref pool) => {
                let conn = pool.get().map_err(|e| Error::R2D2Error { cause: e.to_string() })?;
                Ok(Connection::Postgres(conn))
            }
            Pool::TestPool(ref conn) => Ok(Connection::Test(conn.clone())),
        }
    }

    pub fn wipe_db(&self) -> Result<()> {
        let conn = self.get()?;
        conn.execute("DROP SCHEMA public CASCADE")?;
        conn.execute("CREATE SCHEMA public")?;
        Ok(())
    }
}
<|endoftext|>