<commit_before>use std::env;
use ansi_term::Colour::{Cyan, Blue, Red};
use ansi_term::ANSIStrings;
use git2;
use git2::{Repository, StatusOptions};
use regex::Regex;
use clap::{ArgMatches, App, SubCommand};

fn first_char(s: &str) -> String {
  let chars: Vec<char> = s.chars().collect();
  match chars.len() {
    0 => String::from(""),
    _ => chars[0].to_string(),
  }
}

fn fmt_current_path(cwd: &str) -> String {
  let friendly_path = if let Some(path) = env::home_dir() {
    Regex::new(path.to_str().unwrap()).unwrap().replace(cwd, "~").to_string()
  } else {
    String::from("")
  };

  let mut friendly_path_split: Vec<&str> = friendly_path.split("/").collect();
  let current_dir = friendly_path_split.pop().unwrap().to_string();
  let mut short_path: Vec<String> = friendly_path_split.iter().map(|s| first_char(s)).collect();
  short_path.push(current_dir);
  short_path.join("/")
}

fn repo_status(r: Repository) -> String {
  let mut opts = StatusOptions::new();
  opts.include_untracked(true);
  let head = r.head().unwrap();
  let shorthand = Cyan.paint(head.shorthand().unwrap().to_string());
  let statuses = r.statuses(Some(&mut opts)).unwrap();

  let mut is_dirty = false;

  for entry in statuses.iter().filter(
    |e| e.status() != git2::STATUS_CURRENT,
  ) {
    is_dirty = match entry.status() {
      s if s.contains(git2::STATUS_INDEX_NEW) => true,
      s if s.contains(git2::STATUS_INDEX_MODIFIED) => true,
      s if s.contains(git2::STATUS_INDEX_DELETED) => true,
      s if s.contains(git2::STATUS_INDEX_RENAMED) => true,
      s if s.contains(git2::STATUS_INDEX_TYPECHANGE) => true,
      s if s.contains(git2::STATUS_WT_NEW) => true,
      s if s.contains(git2::STATUS_WT_MODIFIED) => true,
      s if s.contains(git2::STATUS_WT_DELETED) => true,
      s if s.contains(git2::STATUS_WT_RENAMED) => true,
      s if s.contains(git2::STATUS_WT_TYPECHANGE) => true,
      _ => false,
    };
    match is_dirty {
      true => break,
      false => continue,
    }
  }
  let mut out = vec![shorthand];
  if is_dirty == true {
    out.push(Red.bold().paint("*"));
  }

  ANSIStrings(&out).to_string()
}

pub fn display(_sub: &ArgMatches) {
  let my_path = env::current_dir().unwrap();
  let display_path = Blue.paint(fmt_current_path(my_path.to_str().unwrap()));

  let branch = match Repository::open(my_path) {
    Ok(repo) => repo_status(repo),
    Err(_e) => String::from(""),
  };
  let display_branch = Cyan.paint(branch);

  println!("");
  println!("{} {}", display_path, display_branch);
}

pub fn cli_arguments<'a>() -> App<'a, 'a> {
  SubCommand::with_name("precmd")
}
<commit_msg>Replace str pattern with char pattern<commit_after>use std::env;
use ansi_term::Colour::{Cyan, Blue, Red};
use ansi_term::ANSIStrings;
use git2;
use git2::{Repository, StatusOptions};
use regex::Regex;
use clap::{ArgMatches, App, SubCommand};

fn first_char(s: &str) -> String {
  let chars: Vec<char> = s.chars().collect();
  match chars.len() {
    0 => String::from(""),
    _ => chars[0].to_string(),
  }
}

fn fmt_current_path(cwd: &str) -> String {
  let friendly_path = if let Some(path) = env::home_dir() {
    Regex::new(path.to_str().unwrap()).unwrap().replace(cwd, "~").to_string()
  } else {
    String::from("")
  };

  let mut friendly_path_split: Vec<&str> = friendly_path.split('/').collect();
  let current_dir = friendly_path_split.pop().unwrap().to_string();
  let mut short_path: Vec<String> = friendly_path_split.iter().map(|s| first_char(s)).collect();
  short_path.push(current_dir);
  short_path.join("/")
}

fn repo_status(r: Repository) -> String {
  let mut opts = StatusOptions::new();
  opts.include_untracked(true);
  let head = r.head().unwrap();
  let shorthand = Cyan.paint(head.shorthand().unwrap().to_string());
  let statuses = r.statuses(Some(&mut opts)).unwrap();

  let mut is_dirty = false;

  for entry in statuses.iter().filter(
    |e| e.status() != git2::STATUS_CURRENT,
  ) {
    is_dirty = match entry.status() {
      s if s.contains(git2::STATUS_INDEX_NEW) => true,
      s if s.contains(git2::STATUS_INDEX_MODIFIED) => true,
      s if s.contains(git2::STATUS_INDEX_DELETED) => true,
      s if s.contains(git2::STATUS_INDEX_RENAMED) => true,
      s if s.contains(git2::STATUS_INDEX_TYPECHANGE) => true,
      s if s.contains(git2::STATUS_WT_NEW) => true,
      s if s.contains(git2::STATUS_WT_MODIFIED) => true,
      s if s.contains(git2::STATUS_WT_DELETED) => true,
      s if s.contains(git2::STATUS_WT_RENAMED) => true,
      s if s.contains(git2::STATUS_WT_TYPECHANGE) => true,
      _ => false,
    };
    match is_dirty {
      true => break,
      false => continue,
    }
  }
  let mut out = vec![shorthand];
  if is_dirty == true {
    out.push(Red.bold().paint("*"));
  }

  ANSIStrings(&out).to_string()
}

pub fn display(_sub: &ArgMatches) {
  let my_path = env::current_dir().unwrap();
  let display_path = Blue.paint(fmt_current_path(my_path.to_str().unwrap()));

  let branch = match Repository::open(my_path) {
    Ok(repo) => repo_status(repo),
    Err(_e) => String::from(""),
  };
  let display_branch = Cyan.paint(branch);

  println!("");
  println!("{} {}", display_path, display_branch);
}

pub fn cli_arguments<'a>() -> App<'a, 'a> {
  SubCommand::with_name("precmd")
}
<|endoftext|>


<commit_before>use players::*;
use std::path::Path;
use std::thread;

use std::time::*;
use std::fs;

use glob::*;

pub struct MctsPlayer {
    mcts_m  : mcts::MctsTree<CoinNet>,
    rounds  : isize,
    solve_depth : usize,
}

impl MctsPlayer {
    pub fn new(_s : Turn, model_path : &Path, params_path : &Path, rounds : isize, solve_depth : usize) -> MctsPlayer {

        let tmp_dir = &Path::new("/tmp/coin_othello/");
        fs::create_dir_all(tmp_dir).unwrap();

        let model_filename = model_path.file_name().expect("Error getting graph file name.");
        let params_filename = params_path.file_name().expect("Error getting graph file name.");

        eprintln!("[COIN] Copying {:?} to {:?}", model_path, tmp_dir);
        while let Err(_) = fs::copy(model_path, tmp_dir.join(model_filename)) {
            thread::sleep(Duration::from_millis(100));
        }
        let glob_path = format!("{}*", params_path.display());
        for path in glob(&glob_path).expect("Failed to find parameter files.") {
            if let Ok(path) = path {
                eprintln!("[COIN] Copying {:?} to {:?}", path, tmp_dir);
                while let Err(_) = fs::copy(path.clone(), tmp_dir.join(path.file_name().unwrap())) {
                    thread::sleep(Duration::from_millis(100));
                }
            }
        }

        let model_path = &tmp_dir.join(model_filename);
        let params_path = &tmp_dir.join(params_filename);

        let mut net = CoinNet::new(model_path).unwrap();
        net.load(params_path).unwrap();

        let mut mcts_m = MctsTree::new(net);
        mcts_m.set_temp(1.0);
        MctsPlayer {
            mcts_m,
            rounds,
            solve_depth
        }
    }
}

impl Player for MctsPlayer {

    fn do_move(&mut self, b : Board, mut ms_left : u64) -> Move {
        let pieces = b.count_pieces();
        let total = pieces.0 + pieces.1;
        let empty = (64 - total) as usize;

        let ttime = Instant::now();
        let mut start = Instant::now();

        let mut moves = empty_movelist();
        let n = b.get_moves(&mut moves) as usize;

        eprintln!("[COIN] {} nodes in the tree.", self.mcts_m.count_sims());

        eprintln!("[COIN] {} ms remaining.", ms_left);

        let pruned = self.mcts_m.prune_board(b.clone());

        eprintln!("[COIN] Saved {} Nodes.", pruned);

        let mut timeout = false;

        let mut out_move = Move::null();

        if empty < self.solve_depth {
            eprintln!("[COIN] Attempting to solve the game.");
            let (m,s) = self.mcts_m.solve_endgame(start, Duration::from_millis(ms_left/4), &mut timeout);

            ms_left *= 3;
            ms_left /= 4;

            if timeout {
                eprintln!("[COIN] Timeout on endgame solver, researching with MCTS.");
                start = Instant::now();
            } else {
                out_move = m;
                eprintln!("[COIN] Solved game! Result: {} {}", s, m);
            }
        }

        let alloc_time = (ms_left as f32 * TIME_ALLOC[total as usize]) as u64;

        if timeout || empty >= self.solve_depth {
            eprintln!("[COIN] Searching... ({} rounds)", self.rounds);
            let expansions = if self.rounds > 0 {
                self.mcts_m.n_rounds(self.rounds as usize);
                self.rounds as usize
            } else {
                self.mcts_m.time_rounds(alloc_time)
            };

            let elapsed_time = {
                let e = start.elapsed();
                let s = e.as_secs();
                let ns = e.subsec_nanos();

                s * 1000 + ns as u64 / 1_000_000
            };

            eprintln!("[COIN] Done!");
            eprintln!("[COIN] Generated {} Nodes. ({} n/s)", expansions, expansions as f32 * 1000.0 / elapsed_time as f32);

            let EvalOutput(output, score) = self.mcts_m.evaluate(&Board::new());
            eprintln!("[COIN] Score={:.3}", score);

            eprint!("[COIN] Main Line:");
            let main_line = self.mcts_m.scan(4);

            for mv in main_line {
                eprint!(" {}", mv);
            }
            eprintln!("...");

            let mut mi = 0;
            let mut mx = output[moves[0].offset() as usize];

            for i in 1..n {
                let tmp = output[moves[i].offset() as usize];
                if mx < tmp {
                    mx = tmp;
                    mi = i;
                }
            }

            out_move = moves[mi];
        }

        if out_move.is_null() {
            eprintln!("[COIN] Something went wrong. Choosing random move.");
            let mut ml = empty_movelist();
            let n = b.get_moves(&mut ml) as usize;

            use rand;
            use rand::Rng;
            out_move = ml[rand::thread_rng().gen::<usize>()%n];
        }

        let self_pruned = self.mcts_m.prune(out_move);

        eprintln!("[COIN] Playing {} ({} nodes remaining)", out_move, self_pruned);

        let elapsed_time = {
            let e = ttime.elapsed();
            let s = e.as_secs();
            let ns = e.subsec_nanos();

            s * 1000 + ns as u64 / 1_000_000
        };

        eprintln!("[COIN] Turn took {} ms.", elapsed_time);

        out_move
    }
}
<commit_msg>added provisions to re-search with mcts when WLD solver thinks we've lost<commit_after>use players::*;
use std::path::Path;
use std::thread;

use std::time::*;
use std::fs;

use glob::*;

pub struct MctsPlayer {
    mcts_m  : mcts::MctsTree<CoinNet>,
    rounds  : isize,
    solve_depth : usize,
}

impl MctsPlayer {
    pub fn new(_s : Turn, model_path : &Path, params_path : &Path, rounds : isize, solve_depth : usize) -> MctsPlayer {

        let tmp_dir = &Path::new("/tmp/coin_othello/");
        fs::create_dir_all(tmp_dir).unwrap();

        let model_filename = model_path.file_name().expect("Error getting graph file name.");
        let params_filename = params_path.file_name().expect("Error getting graph file name.");

        eprintln!("[COIN] Copying {:?} to {:?}", model_path, tmp_dir);
        while let Err(_) = fs::copy(model_path, tmp_dir.join(model_filename)) {
            thread::sleep(Duration::from_millis(100));
        }
        let glob_path = format!("{}*", params_path.display());
        for path in glob(&glob_path).expect("Failed to find parameter files.") {
            if let Ok(path) = path {
                eprintln!("[COIN] Copying {:?} to {:?}", path, tmp_dir);
                while let Err(_) = fs::copy(path.clone(), tmp_dir.join(path.file_name().unwrap())) {
                    thread::sleep(Duration::from_millis(100));
                }
            }
        }

        let model_path = &tmp_dir.join(model_filename);
        let params_path = &tmp_dir.join(params_filename);

        let mut net = CoinNet::new(model_path).unwrap();
        net.load(params_path).unwrap();

        let mut mcts_m = MctsTree::new(net);
        mcts_m.set_temp(1.0);
        MctsPlayer {
            mcts_m,
            rounds,
            solve_depth
        }
    }
}

impl Player for MctsPlayer {

    fn do_move(&mut self, b : Board, mut ms_left : u64) -> Move {
        let pieces = b.count_pieces();
        let total = pieces.0 + pieces.1;
        let empty = (64 - total) as usize;

        let ttime = Instant::now();
        let mut start = Instant::now();

        let mut moves = empty_movelist();
        let n = b.get_moves(&mut moves) as usize;

        eprintln!("[COIN] {} nodes in the tree.", self.mcts_m.count_sims());

        eprintln!("[COIN] {} ms remaining.", ms_left);

        let pruned = self.mcts_m.prune_board(b.clone());

        eprintln!("[COIN] Saved {} Nodes.", pruned);

        let mut timeout = false;

        let mut research = false;

        let mut out_move = Move::null();

        if empty < self.solve_depth {
            eprintln!("[COIN] Attempting to solve the game.");
            let (m,s) = self.mcts_m.solve_endgame(start, Duration::from_millis(ms_left/4), &mut timeout);

            ms_left *= 3;
            ms_left /= 4;

            if timeout {
                eprintln!("[COIN] Timeout on endgame solver, researching with MCTS.");
                start = Instant::now();
            } else if s == -1 {
                eprintln!("[COIN] No win or draw, researching with MCTS.");
                start = Instant::now();
                research = true;
            }else {
                out_move = m;
                eprintln!("[COIN] Solved game! Result: {} {}", s, m);
            }
        }

        let alloc_time = (ms_left as f32 * TIME_ALLOC[total as usize]) as u64;

        if timeout || research || empty >= self.solve_depth {
            eprintln!("[COIN] Searching... ({} rounds)", self.rounds);
            let expansions = if self.rounds > 0 {
                self.mcts_m.n_rounds(self.rounds as usize);
                self.rounds as usize
            } else {
                self.mcts_m.time_rounds(alloc_time)
            };

            let elapsed_time = {
                let e = start.elapsed();
                let s = e.as_secs();
                let ns = e.subsec_nanos();

                s * 1000 + ns as u64 / 1_000_000
            };

            eprintln!("[COIN] Done!");
            eprintln!("[COIN] Generated {} Nodes. ({} n/s)", expansions, expansions as f32 * 1000.0 / elapsed_time as f32);

            let EvalOutput(output, score) = self.mcts_m.evaluate(&Board::new());
            eprintln!("[COIN] Score={:.3}", score);

            eprint!("[COIN] Main Line:");
            let main_line = self.mcts_m.scan(4);

            for mv in main_line {
                eprint!(" {}", mv);
            }
            eprintln!("...");

            let mut mi = 0;
            let mut mx = output[moves[0].offset() as usize];

            for i in 1..n {
                let tmp = output[moves[i].offset() as usize];
                if mx < tmp {
                    mx = tmp;
                    mi = i;
                }
            }

            out_move = moves[mi];
        }

        if out_move.is_null() {
            eprintln!("[COIN] Something went wrong. Choosing random move.");
            let mut ml = empty_movelist();
            let n = b.get_moves(&mut ml) as usize;

            use rand;
            use rand::Rng;
            out_move = ml[rand::thread_rng().gen::<usize>()%n];
        }

        let self_pruned = self.mcts_m.prune(out_move);

        eprintln!("[COIN] Playing {} ({} nodes remaining)", out_move, self_pruned);

        let elapsed_time = {
            let e = ttime.elapsed();
            let s = e.as_secs();
            let ns = e.subsec_nanos();

            s * 1000 + ns as u64 / 1_000_000
        };

        eprintln!("[COIN] Turn took {} ms.", elapsed_time);

        out_move
    }
}
<|endoftext|>


<commit_before>use crate::config::Config;
use crate::errors::*;
use crate::filter::{
    contains_markdown_file, contains_markdown_subdir, is_directory, is_hidden, is_image,
    is_markdown,
};
use crate::template;
use image;
use liquid;
use pulldown_cmark::{html, Options, Parser};
use rayon::prelude::*;
use std::fmt::{self, Debug};
use std::fs;
use std::fs::DirBuilder;
use std::fs::File;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::vec::Vec;
use walkdir::{DirEntry, WalkDir};

#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum MenuCmd {
    OpenLevel,
    CloseLevel,
    CloseOpenLevel,
    None,
}

impl fmt::Display for MenuCmd {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        Debug::fmt(self, f)
    }
}

pub fn build(path: &Path, conf: &Config) -> Result<()> {
    let path_comps = path.components().collect::<Vec<_>>();
    let output_dir = PathBuf::from(conf.output_dir.as_ref().unwrap());
    let nav_items = prepare_site_structure(path, output_dir.as_path());
    // let last_gen_timestmp = set_and_determine_last_generation(output_dir.as_path());
    let entries = WalkDir::new(path)
        .min_depth(1)
        .into_iter()
        .filter_entry(|e| is_hidden(e))
        .filter(|e| e.is_ok() && is_markdown(e.as_ref().unwrap()))
        .collect::<Vec<_>>();
    entries.par_iter().for_each(|e| {
        let src = e.as_ref().unwrap();
        let mut target_dir = output_dir.clone();
        if src.path().parent().is_some() {
            for comp in src
                .path()
                .parent()
                .unwrap()
                .components()
                .skip(path_comps.len())
            {
                target_dir.push(comp.as_os_str());
            }
        }
        build_page(nav_items.clone(), src, target_dir.as_path(), conf);
    });
    copy_dirs(path, output_dir.as_path(), conf);
    Ok(())
}

fn build_page(
    nav_items: Vec<liquid::model::Value>,
    entry: &DirEntry,
    target_dir: &Path,
    conf: &Config,
) {
    let page_content = convert_markdown_to_html(entry.path());
    let html = if entry.file_name() == "gallery.md" {
        let images = prepare_gallery(entry, target_dir, conf);
        apply_gallery_template(&page_content, nav_items, images, entry.depth(), conf)
    } else {
        apply_page_template(&page_content, nav_items, entry.depth(), conf)
    };
    write_html_file(&html, target_dir, entry);
    copy_images(entry.path().parent().unwrap(), target_dir);
}

fn copy_dirs(path: &Path, target_path: &Path, conf: &Config) {
    if conf.copy_dirs.is_some() {
        let path_comps = path.components().collect::<Vec<_>>();
        for copy_dir in conf.copy_dirs.as_ref().unwrap() {
            let walker = WalkDir::new(path.join(copy_dir).as_path())
                .min_depth(1)
                .into_iter();
            for entry in walker.filter(|e| e.is_ok() && !is_directory(e.as_ref().unwrap())) {
                let entry = entry.unwrap();
                let mut target_file = PathBuf::from(target_path);
                for comp in entry.path().components().skip(path_comps.len()) {
                    target_file.push(comp.as_os_str());
                }
                match DirBuilder::new()
                    .recursive(true)
                    .create(target_file.parent().expect("Missing parent folder!"))
                {
                    Ok(_) => {}
                    Err(e) => println!("{}", e),
                }
                if !target_file.exists() {
                    fs::copy(entry.path(), target_file)
                        .expect(format!("error during copy of {:?}", copy_dir).as_ref());
                }
            }
        }
    }
}

fn copy_images(source: &Path, target: &Path) {
    let walker = WalkDir::new(source)
        .min_depth(1)
        .max_depth(1)
        .follow_links(true)
        .into_iter();
    for entry in walker.filter(|e| e.is_ok() && is_image(e.as_ref().unwrap())) {
        let mut target_file = PathBuf::from(target);
        let entry = entry.unwrap();
        target_file.push(entry.path().file_name().unwrap());
        if !target_file.exists() {
            fs::copy(entry.path(), target_file.as_path())
                .expect(format!("Error during copy of {:?}", entry.path().display()).as_ref());
        }
    }
}

fn prepare_site_structure(path: &Path, target_path: &Path) -> Vec<liquid::model::Value> {
    let mut nav_entries = Vec::<liquid::model::Value>::new();
    let walker = WalkDir::new(path)
        .min_depth(1)
        .sort_by(|a, b| a.file_name().cmp(b.file_name()))
        .into_iter();
    let mut prev_depth = 1;
    let path_comps = path.components().collect::<Vec<_>>();
    for entry in
        walker.filter_entry(|e| is_hidden(e) && is_directory(e) && contains_markdown_file(e))
    {
        let entry = entry.expect("Reading directory entry failed");
        let name = String::from(
            entry
                .file_name()
                .to_str()
                .expect("Failed to read navigation entries"),
        );
        let mut url = PathBuf::new();
        for comp in entry.path().components().skip(path_comps.len()) {
            url.push(comp.as_os_str());
        }
        let target_dir = target_path.join(url.as_path());
        match DirBuilder::new().recursive(true).create(target_dir) {
            Ok(_) => {}
            Err(e) => println!("{}", e),
        }
        url.push("index.html");
        let (menu_cmd, level_depth) =
            match (contains_markdown_subdir(&entry), prev_depth > entry.depth()) {
                (true, true) => (MenuCmd::CloseOpenLevel, prev_depth - entry.depth()),
                (true, false) => (MenuCmd::OpenLevel, 0),
                (false, true) => (MenuCmd::CloseLevel, prev_depth - entry.depth()),
                _ => (MenuCmd::None, 0),
            };
        let nav_entry = liquid::object!({
            "name": name,
            "url" : url.as_os_str().to_str().unwrap().to_owned(),
            "menu_cmd" : menu_cmd.to_string().to_owned(),
            "level_depth" : level_depth as i32,
        });
        nav_entries.push(liquid::model::Value::Object(nav_entry));
        prev_depth = entry.depth();
    }
    nav_entries
}

fn prepare_gallery(
    source_entry: &DirEntry,
    target_path: &Path,
    conf: &Config,
) -> Vec<liquid::model::Value> {
    let gallery_settings = conf.gallery.as_ref().unwrap();
    let mut images = Vec::<liquid::model::Value>::new();
    let img_dir = gallery_settings.img_dir.as_ref().unwrap();
    let target_dir = target_path.join(img_dir.as_str());
    match DirBuilder::new().recursive(true).create(&target_dir) {
        Ok(_) => {}
        Err(e) => println!("{}", e),
    }
    let entries = WalkDir::new(
        source_entry
            .path()
            .parent()
            .unwrap()
            .join(img_dir.as_str())
            .as_path(),
    )
    .min_depth(1)
    .follow_links(true)
    .into_iter()
    .filter(|e| e.is_ok() && !is_directory(e.as_ref().unwrap()))
    .collect::<Vec<_>>();
    for entry in entries {
        let entry = entry.unwrap();
        let mut img = image::open(entry.path()).expect(
            format!(
                "Resize of '{}' failed: The gallery folder should only contain \
                 images!",
                entry.path().display()
            )
            .as_ref(),
        );

        let mut image_path = PathBuf::from(&target_dir);
        let mut rel_image_path = PathBuf::from(img_dir.as_str());
        image_path.push(entry.file_name());
        image_path.set_extension("png");
        rel_image_path.push(entry.file_name());
        rel_image_path.set_extension("png");
        if !image_path.exists() {
            let _ = &mut File::create(&image_path).unwrap();
            img = img.resize(
                gallery_settings.img_width,
                gallery_settings.img_height,
                image::imageops::FilterType::Nearest,
            );
            img.save_with_format(&image_path, image::ImageFormat::Png)
                .expect(format!("Saving image '{}' failed", image_path.display()).as_ref());
        }

        let mut thumb_path = PathBuf::from(&target_dir);
        let mut rel_thumb_path = PathBuf::from(img_dir.as_str());
        let mut thumb_file_name = String::from(
            entry
                .path()
                .file_stem()
                .map(|s| s.to_str().unwrap())
                .unwrap(),
        );
        thumb_file_name.push_str("_thumb.png");
        thumb_path.push(thumb_file_name.clone());
        rel_thumb_path.push(thumb_file_name);
        if !thumb_path.exists() {
            let _ = &mut File::create(&thumb_path).unwrap();
            img = img.resize(
                gallery_settings.thumb_width,
                gallery_settings.thumb_height,
                image::imageops::FilterType::Nearest,
            );
            img.save_with_format(&thumb_path, image::ImageFormat::Png)
                .expect(format!("Saving thumb image '{}' failed", thumb_path.display()).as_ref());
        }

        let image_entry = liquid::object!({
            "name"  : rel_image_path.to_str().unwrap().to_owned(),
            "thumb" : rel_thumb_path.to_str().unwrap().to_owned(),
        });
        images.push(liquid::model::Value::Object(image_entry));
    }
    images
}

fn apply_gallery_template(
    content: &str,
    nav_items: Vec<liquid::model::Value>,
    images: Vec<liquid::model::Value>,
    depth: usize,
    conf: &Config,
) -> String {
    let template = liquid::ParserBuilder::with_stdlib()
        .build()
        .unwrap()
        .parse(template::load_gallery_template(conf).as_str())
        .expect("Gallery template could not be parsed!");
    let mut root_dir = String::new();
    for _ in 1..depth {
        root_dir.push_str("../");
    }
    let mut context = liquid::model::Object::new();
    context.insert(
        "root_dir".into(),
        liquid::model::Value::scalar(root_dir),
    );
    context.insert(
        "title".into(),
        liquid::model::Value::scalar(if conf.title.is_some() {
            conf.title.as_ref().unwrap().clone()
        } else {
            "None".to_string()
        }),
    );
    context.insert(
        "nav_items".into(),
        liquid::model::Value::Array(nav_items),
    );
    context.insert(
        "content".into(),
        liquid::model::Value::scalar(content.to_owned()),
    );
    context.insert("images".into(), liquid::model::Value::Array(images));
    match template.render(&context) {
        Ok(output) => output,
        Err(error) => panic!("Could not render Page template: {}", error),
    }
}

fn apply_page_template(
    content: &str,
    nav_items: Vec<liquid::model::Value>,
    depth: usize,
    conf: &Config,
) -> String {
    let template = liquid::ParserBuilder::with_stdlib()
        .build()
        .unwrap()
        .parse(template::load_page_template(conf).as_str())
        .expect("Page template could not be parsed!");
    let mut root_dir = String::from("./");
    for _ in 1..depth {
        root_dir.push_str("../");
    }
    let context = liquid::object!({
       "root_dir" : root_dir,
       "title" : if conf.title.is_some() {
            conf.title.as_ref().unwrap().clone()
        } else {
            "None".to_string()
        },
        "nav_items" : liquid::model::Value::Array(nav_items),
        "content" : content.to_owned(),
    });
    match template.render(&context) {
        Ok(output) => output,
        Err(error) => panic!("Could not render Page template: {}", error),
    }
}

fn convert_markdown_to_html(entry: &Path) -> String {
    let mut markdown = String::new();
    let mut html_output = String::new();
    match File::open(entry).and_then(|mut f| f.read_to_string(&mut markdown)) {
        Err(error) => panic!("failed to open {}: {}", entry.display(), error),
        Ok(_) => html::push_html(
            &mut html_output,
            Parser::new_ext(markdown.as_str(), Options::empty()),
        ),
    }
    html_output
}

fn write_html_file(html: &str, target_dir: &Path, entry: &DirEntry) {
    let html_file = match entry.file_name().to_str() {
        Some(fname) => {
            let mut tmp_path = PathBuf::from(fname);
            tmp_path.set_file_name("index.html");
            tmp_path
        }
        None => panic!("Could not write html file {}", entry.path().display()),
    };
    let file_path = target_dir.join(html_file.as_path());
    let result = File::create(file_path.as_path()).and_then(|mut f| f.write_all(html.as_bytes()));
    if result.is_err() {
        panic!(
            "Could not write html file {}: {:?}",
            file_path.as_path().display(),
            result.err()
        );
    }
    println!("written file {}", file_path.display());
}
<commit_msg>cargo fmt<commit_after>use crate::config::Config;
use crate::errors::*;
use crate::filter::{
    contains_markdown_file, contains_markdown_subdir, is_directory, is_hidden, is_image,
    is_markdown,
};
use crate::template;
use image;
use liquid;
use pulldown_cmark::{html, Options, Parser};
use rayon::prelude::*;
use std::fmt::{self, Debug};
use std::fs;
use std::fs::DirBuilder;
use std::fs::File;
use std::io::prelude::*;
use std::path::{Path, PathBuf};
use std::vec::Vec;
use walkdir::{DirEntry, WalkDir};

#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum MenuCmd {
    OpenLevel,
    CloseLevel,
    CloseOpenLevel,
    None,
}

impl fmt::Display for MenuCmd {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        Debug::fmt(self, f)
    }
}

pub fn build(path: &Path, conf: &Config) -> Result<()> {
    let path_comps = path.components().collect::<Vec<_>>();
    let output_dir = PathBuf::from(conf.output_dir.as_ref().unwrap());
    let nav_items = prepare_site_structure(path, output_dir.as_path());
    // let last_gen_timestmp = set_and_determine_last_generation(output_dir.as_path());
    let entries = WalkDir::new(path)
        .min_depth(1)
        .into_iter()
        .filter_entry(|e| is_hidden(e))
        .filter(|e| e.is_ok() && is_markdown(e.as_ref().unwrap()))
        .collect::<Vec<_>>();
    entries.par_iter().for_each(|e| {
        let src = e.as_ref().unwrap();
        let mut target_dir = output_dir.clone();
        if src.path().parent().is_some() {
            for comp in src
                .path()
                .parent()
                .unwrap()
                .components()
                .skip(path_comps.len())
            {
                target_dir.push(comp.as_os_str());
            }
        }
        build_page(nav_items.clone(), src, target_dir.as_path(), conf);
    });
    copy_dirs(path, output_dir.as_path(), conf);
    Ok(())
}

fn build_page(
    nav_items: Vec<liquid::model::Value>,
    entry: &DirEntry,
    target_dir: &Path,
    conf: &Config,
) {
    let page_content = convert_markdown_to_html(entry.path());
    let html = if entry.file_name() == "gallery.md" {
        let images = prepare_gallery(entry, target_dir, conf);
        apply_gallery_template(&page_content, nav_items, images, entry.depth(), conf)
    } else {
        apply_page_template(&page_content, nav_items, entry.depth(), conf)
    };
    write_html_file(&html, target_dir, entry);
    copy_images(entry.path().parent().unwrap(), target_dir);
}

fn copy_dirs(path: &Path, target_path: &Path, conf: &Config) {
    if conf.copy_dirs.is_some() {
        let path_comps = path.components().collect::<Vec<_>>();
        for copy_dir in conf.copy_dirs.as_ref().unwrap() {
            let walker = WalkDir::new(path.join(copy_dir).as_path())
                .min_depth(1)
                .into_iter();
            for entry in walker.filter(|e| e.is_ok() && !is_directory(e.as_ref().unwrap())) {
                let entry = entry.unwrap();
                let mut target_file = PathBuf::from(target_path);
                for comp in entry.path().components().skip(path_comps.len()) {
                    target_file.push(comp.as_os_str());
                }
                match DirBuilder::new()
                    .recursive(true)
                    .create(target_file.parent().expect("Missing parent folder!"))
                {
                    Ok(_) => {}
                    Err(e) => println!("{}", e),
                }
                if !target_file.exists() {
                    fs::copy(entry.path(), target_file)
                        .expect(format!("error during copy of {:?}", copy_dir).as_ref());
                }
            }
        }
    }
}

fn copy_images(source: &Path, target: &Path) {
    let walker = WalkDir::new(source)
        .min_depth(1)
        .max_depth(1)
        .follow_links(true)
        .into_iter();
    for entry in walker.filter(|e| e.is_ok() && is_image(e.as_ref().unwrap())) {
        let mut target_file = PathBuf::from(target);
        let entry = entry.unwrap();
        target_file.push(entry.path().file_name().unwrap());
        if !target_file.exists() {
            fs::copy(entry.path(), target_file.as_path())
                .expect(format!("Error during copy of {:?}", entry.path().display()).as_ref());
        }
    }
}

fn prepare_site_structure(path: &Path, target_path: &Path) -> Vec<liquid::model::Value> {
    let mut nav_entries = Vec::<liquid::model::Value>::new();
    let walker = WalkDir::new(path)
        .min_depth(1)
        .sort_by(|a, b| a.file_name().cmp(b.file_name()))
        .into_iter();
    let mut prev_depth = 1;
    let path_comps = path.components().collect::<Vec<_>>();
    for entry in
        walker.filter_entry(|e| is_hidden(e) && is_directory(e) && contains_markdown_file(e))
    {
        let entry = entry.expect("Reading directory entry failed");
        let name = String::from(
            entry
                .file_name()
                .to_str()
                .expect("Failed to read navigation entries"),
        );
        let mut url = PathBuf::new();
        for comp in entry.path().components().skip(path_comps.len()) {
            url.push(comp.as_os_str());
        }
        let target_dir = target_path.join(url.as_path());
        match DirBuilder::new().recursive(true).create(target_dir) {
            Ok(_) => {}
            Err(e) => println!("{}", e),
        }
        url.push("index.html");
        let (menu_cmd, level_depth) =
            match (contains_markdown_subdir(&entry), prev_depth > entry.depth()) {
                (true, true) => (MenuCmd::CloseOpenLevel, prev_depth - entry.depth()),
                (true, false) => (MenuCmd::OpenLevel, 0),
                (false, true) => (MenuCmd::CloseLevel, prev_depth - entry.depth()),
                _ => (MenuCmd::None, 0),
            };
        let nav_entry = liquid::object!({
            "name": name,
            "url" : url.as_os_str().to_str().unwrap().to_owned(),
            "menu_cmd" : menu_cmd.to_string().to_owned(),
            "level_depth" : level_depth as i32,
        });
        nav_entries.push(liquid::model::Value::Object(nav_entry));
        prev_depth = entry.depth();
    }
    nav_entries
}

fn prepare_gallery(
    source_entry: &DirEntry,
    target_path: &Path,
    conf: &Config,
) -> Vec<liquid::model::Value> {
    let gallery_settings = conf.gallery.as_ref().unwrap();
    let mut images = Vec::<liquid::model::Value>::new();
    let img_dir = gallery_settings.img_dir.as_ref().unwrap();
    let target_dir = target_path.join(img_dir.as_str());
    match DirBuilder::new().recursive(true).create(&target_dir) {
        Ok(_) => {}
        Err(e) => println!("{}", e),
    }
    let entries = WalkDir::new(
        source_entry
            .path()
            .parent()
            .unwrap()
            .join(img_dir.as_str())
            .as_path(),
    )
    .min_depth(1)
    .follow_links(true)
    .into_iter()
    .filter(|e| e.is_ok() && !is_directory(e.as_ref().unwrap()))
    .collect::<Vec<_>>();
    for entry in entries {
        let entry = entry.unwrap();
        let mut img = image::open(entry.path()).expect(
            format!(
                "Resize of '{}' failed: The gallery folder should only contain \
                 images!",
                entry.path().display()
            )
            .as_ref(),
        );

        let mut image_path = PathBuf::from(&target_dir);
        let mut rel_image_path = PathBuf::from(img_dir.as_str());
        image_path.push(entry.file_name());
        image_path.set_extension("png");
        rel_image_path.push(entry.file_name());
        rel_image_path.set_extension("png");
        if !image_path.exists() {
            let _ = &mut File::create(&image_path).unwrap();
            img = img.resize(
                gallery_settings.img_width,
                gallery_settings.img_height,
                image::imageops::FilterType::Nearest,
            );
            img.save_with_format(&image_path, image::ImageFormat::Png)
                .expect(format!("Saving image '{}' failed", image_path.display()).as_ref());
        }

        let mut thumb_path = PathBuf::from(&target_dir);
        let mut rel_thumb_path = PathBuf::from(img_dir.as_str());
        let mut thumb_file_name = String::from(
            entry
                .path()
                .file_stem()
                .map(|s| s.to_str().unwrap())
                .unwrap(),
        );
        thumb_file_name.push_str("_thumb.png");
        thumb_path.push(thumb_file_name.clone());
        rel_thumb_path.push(thumb_file_name);
        if !thumb_path.exists() {
            let _ = &mut File::create(&thumb_path).unwrap();
            img = img.resize(
                gallery_settings.thumb_width,
                gallery_settings.thumb_height,
                image::imageops::FilterType::Nearest,
            );
            img.save_with_format(&thumb_path, image::ImageFormat::Png)
                .expect(format!("Saving thumb image '{}' failed", thumb_path.display()).as_ref());
        }

        let image_entry = liquid::object!({
            "name"  : rel_image_path.to_str().unwrap().to_owned(),
            "thumb" : rel_thumb_path.to_str().unwrap().to_owned(),
        });
        images.push(liquid::model::Value::Object(image_entry));
    }
    images
}

fn apply_gallery_template(
    content: &str,
    nav_items: Vec<liquid::model::Value>,
    images: Vec<liquid::model::Value>,
    depth: usize,
    conf: &Config,
) -> String {
    let template = liquid::ParserBuilder::with_stdlib()
        .build()
        .unwrap()
        .parse(template::load_gallery_template(conf).as_str())
        .expect("Gallery template could not be parsed!");
    let mut root_dir = String::new();
    for _ in 1..depth {
        root_dir.push_str("../");
    }
    let mut context = liquid::model::Object::new();
    context.insert("root_dir".into(), liquid::model::Value::scalar(root_dir));
    context.insert(
        "title".into(),
        liquid::model::Value::scalar(if conf.title.is_some() {
            conf.title.as_ref().unwrap().clone()
        } else {
            "None".to_string()
        }),
    );
    context.insert("nav_items".into(), liquid::model::Value::Array(nav_items));
    context.insert(
        "content".into(),
        liquid::model::Value::scalar(content.to_owned()),
    );
    context.insert("images".into(), liquid::model::Value::Array(images));
    match template.render(&context) {
        Ok(output) => output,
        Err(error) => panic!("Could not render Page template: {}", error),
    }
}

fn apply_page_template(
    content: &str,
    nav_items: Vec<liquid::model::Value>,
    depth: usize,
    conf: &Config,
) -> String {
    let template = liquid::ParserBuilder::with_stdlib()
        .build()
        .unwrap()
        .parse(template::load_page_template(conf).as_str())
        .expect("Page template could not be parsed!");
    let mut root_dir = String::from("./");
    for _ in 1..depth {
        root_dir.push_str("../");
    }
    let context = liquid::object!({
       "root_dir" : root_dir,
       "title" : if conf.title.is_some() {
            conf.title.as_ref().unwrap().clone()
        } else {
            "None".to_string()
        },
        "nav_items" : liquid::model::Value::Array(nav_items),
        "content" : content.to_owned(),
    });
    match template.render(&context) {
        Ok(output) => output,
        Err(error) => panic!("Could not render Page template: {}", error),
    }
}

fn convert_markdown_to_html(entry: &Path) -> String {
    let mut markdown = String::new();
    let mut html_output = String::new();
    match File::open(entry).and_then(|mut f| f.read_to_string(&mut markdown)) {
        Err(error) => panic!("failed to open {}: {}", entry.display(), error),
        Ok(_) => html::push_html(
            &mut html_output,
            Parser::new_ext(markdown.as_str(), Options::empty()),
        ),
    }
    html_output
}

fn write_html_file(html: &str, target_dir: &Path, entry: &DirEntry) {
    let html_file = match entry.file_name().to_str() {
        Some(fname) => {
            let mut tmp_path = PathBuf::from(fname);
            tmp_path.set_file_name("index.html");
            tmp_path
        }
        None => panic!("Could not write html file {}", entry.path().display()),
    };
    let file_path = target_dir.join(html_file.as_path());
    let result = File::create(file_path.as_path()).and_then(|mut f| f.write_all(html.as_bytes()));
    if result.is_err() {
        panic!(
            "Could not write html file {}: {:?}",
            file_path.as_path().display(),
            result.err()
        );
    }
    println!("written file {}", file_path.display());
}
<|endoftext|>


<commit_before>use ammonia::Ammonia;
use comrak;
use htmlescape::encode_minimal;

use util::CargoResult;

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

impl<'a> MarkdownRenderer<'a> {
    /// Creates a new renderer instance.
    fn new() -> MarkdownRenderer<'a> {
        let tags = [
            "a",
            "b",
            "blockquote",
            "br",
            "code",
            "dd",
            "del",
            "dl",
            "dt",
            "em",
            "h1",
            "h2",
            "h3",
            "hr",
            "i",
            "img",
            "input",
            "kbd",
            "li",
            "ol",
            "p",
            "pre",
            "s",
            "strike",
            "strong",
            "sub",
            "sup",
            "table",
            "tbody",
            "td",
            "th",
            "thead",
            "tr",
            "ul",
            "hr",
            "span",
        ].iter()
            .cloned()
            .collect();
        let tag_attributes = [
            ("a", ["href", "target"].iter().cloned().collect()),
            ("code", ["class"].iter().cloned().collect()),
            (
                "img",
                ["width", "height", "src", "alt", "align"]
                    .iter()
                    .cloned()
                    .collect(),
            ),
            (
                "input",
                ["checked", "disabled", "type"].iter().cloned().collect(),
            ),
        ].iter()
            .cloned()
            .collect();
        let allowed_classes = [
            (
                "code",
                [
                    "language-bash",
                    "language-clike",
                    "language-glsl",
                    "language-go",
                    "language-ini",
                    "language-javascript",
                    "language-json",
                    "language-markup",
                    "language-protobuf",
                    "language-ruby",
                    "language-rust",
                    "language-scss",
                    "language-sql",
                    "yaml",
                ].iter()
                    .cloned()
                    .collect(),
            ),
        ].iter()
            .cloned()
            .collect();
        let html_sanitizer = Ammonia {
            link_rel: Some("nofollow noopener noreferrer"),
            keep_cleaned_elements: true,
            tags: tags,
            tag_attributes: tag_attributes,
            allowed_classes: allowed_classes,
            ..Ammonia::default()
        };
        MarkdownRenderer {
            html_sanitizer: html_sanitizer,
        }
    }

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

impl<'a> Default for MarkdownRenderer<'a> {
    fn default() -> Self {
        Self::new()
    }
}

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

/// Any readme with a filename ending in one of these extensions will be rendered as Markdown.
static MARKDOWN_EXTENSIONS: [&'static str; 7] = [
    ".md",
    ".markdown",
    ".mdown",
    ".mdwn",
    ".mkd",
    ".mkdn",
    ".mkdown",
];

/// Renders a readme to sanitized HTML.  An appropriate rendering method is chosen depending
/// on the extension of the supplied filename.
///
/// The returned text should not contain any harmful HTML tag or attribute (such as iframe,
/// onclick, onmouseover, etc.).
///
/// # Examples
///
/// ```
/// use render::render_to_html;
///
/// let text = "[Rust](https://rust-lang.org/) is an awesome *systems programming* language!";
/// let rendered = readme_to_html(text, "README.md")?;
/// ```
pub fn readme_to_html(text: &str, filename: &str) -> CargoResult<String> {
    for e in &MARKDOWN_EXTENSIONS {
        if filename.to_lowercase().ends_with(e) {
            return markdown_to_html(text);
        }
    }

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

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

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

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

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

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

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

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

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

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

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

    #[test]
    fn readme_to_html_renders_other_things() {
        for f in &["readme", "readem.org", "blah.adoc"] {
            assert_eq!(
                readme_to_html("\n\nis my friend\n", f).unwrap(),
                "<script>lobster</script>
\n
\nis my friend
\n"
            );
        }
    }
}
<commit_msg>No extension = assume Markdown.<commit_after>use ammonia::Ammonia;
use comrak;
use htmlescape::encode_minimal;

use util::CargoResult;

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

impl<'a> MarkdownRenderer<'a> {
    /// Creates a new renderer instance.
    fn new() -> MarkdownRenderer<'a> {
        let tags = [
            "a",
            "b",
            "blockquote",
            "br",
            "code",
            "dd",
            "del",
            "dl",
            "dt",
            "em",
            "h1",
            "h2",
            "h3",
            "hr",
            "i",
            "img",
            "input",
            "kbd",
            "li",
            "ol",
            "p",
            "pre",
            "s",
            "strike",
            "strong",
            "sub",
            "sup",
            "table",
            "tbody",
            "td",
            "th",
            "thead",
            "tr",
            "ul",
            "hr",
            "span",
        ].iter()
            .cloned()
            .collect();
        let tag_attributes = [
            ("a", ["href", "target"].iter().cloned().collect()),
            ("code", ["class"].iter().cloned().collect()),
            (
                "img",
                ["width", "height", "src", "alt", "align"]
                    .iter()
                    .cloned()
                    .collect(),
            ),
            (
                "input",
                ["checked", "disabled", "type"].iter().cloned().collect(),
            ),
        ].iter()
            .cloned()
            .collect();
        let allowed_classes = [
            (
                "code",
                [
                    "language-bash",
                    "language-clike",
                    "language-glsl",
                    "language-go",
                    "language-ini",
                    "language-javascript",
                    "language-json",
                    "language-markup",
                    "language-protobuf",
                    "language-ruby",
                    "language-rust",
                    "language-scss",
                    "language-sql",
                    "yaml",
                ].iter()
                    .cloned()
                    .collect(),
            ),
        ].iter()
            .cloned()
            .collect();
        let html_sanitizer = Ammonia {
            link_rel: Some("nofollow noopener noreferrer"),
            keep_cleaned_elements: true,
            tags: tags,
            tag_attributes: tag_attributes,
            allowed_classes: allowed_classes,
            ..Ammonia::default()
        };
        MarkdownRenderer {
            html_sanitizer: html_sanitizer,
        }
    }

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

impl<'a> Default for MarkdownRenderer<'a> {
    fn default() -> Self {
        Self::new()
    }
}

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

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

/// Renders a readme to sanitized HTML.  An appropriate rendering method is chosen depending
/// on the extension of the supplied filename.
///
/// The returned text should not contain any harmful HTML tag or attribute (such as iframe,
/// onclick, onmouseover, etc.).
///
/// # Examples
///
/// ```
/// use render::render_to_html;
///
/// let text = "[Rust](https://rust-lang.org/) is an awesome *systems programming* language!";
/// let rendered = readme_to_html(text, "README.md")?;
/// ```
pub fn readme_to_html(text: &str, filename: &str) -> CargoResult<String> {
    let filename = filename.to_lowercase();

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

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

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

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

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

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

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

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

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

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

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

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

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


<commit_before>extern crate gfx;
extern crate gfx_window_glutin;
extern crate glutin;
extern crate lazy_static;
extern crate nalgebra;
extern crate rand;

use nalgebra::ToHomogeneous;

use cube;
use cube::ComputedCube;
use math::{IntoDomain, RealSpace};

pub type Point3 = nalgebra::Point3<RealSpace>;
pub type Vector3 = nalgebra::Vector3<RealSpace>;
pub type Vector4 = nalgebra::Vector4<RealSpace>;
pub type Matrix4 = nalgebra::Matrix4<RealSpace>;
pub type Index = u32;

#[cfg_attr(rustfmt, rustfmt_skip)]
const UNIT_CUBE_INDECES: [Index; 36] = [
    0,  1,  2,  2,  3,  0,
    4,  5,  6,  6,  7,  4,
    8,  9,  10, 10, 11, 8,
    12, 13, 14, 14, 15, 12,
    16, 17, 18, 18, 19, 16,
    20, 21, 22, 22, 23, 20
];
lazy_static! {
    // TODO: Remove redundant points and adjust the indeces accordingly. This
    //       will use less data and make it easier to apply transformations
    //       based on leaf geometry.
    static ref UNIT_CUBE_POINTS: [Point3; 24] = [
        // Top.
        Point3::new(0.0, 0.0, 1.0),
        Point3::new(1.0, 0.0, 1.0),
        Point3::new(1.0, 1.0, 1.0),
        Point3::new(0.0, 1.0, 1.0),
        // Bottom.
        Point3::new(0.0, 1.0, 0.0),
        Point3::new(1.0, 1.0, 0.0),
        Point3::new(1.0, 0.0, 0.0),
        Point3::new(0.0, 0.0, 0.0),
        // Right.
        Point3::new(1.0, 0.0, 0.0),
        Point3::new(1.0, 1.0, 0.0),
        Point3::new(1.0, 1.0, 1.0),
        Point3::new(1.0, 0.0, 1.0),
        // Left.
        Point3::new(0.0, 0.0, 1.0),
        Point3::new(0.0, 1.0, 1.0),
        Point3::new(0.0, 1.0, 0.0),
        Point3::new(0.0, 0.0, 0.0),
        // Front.
        Point3::new(1.0, 1.0, 0.0),
        Point3::new(0.0, 1.0, 0.0),
        Point3::new(0.0, 1.0, 1.0),
        Point3::new(1.0, 1.0, 1.0),
        // Back.
        Point3::new(1.0, 0.0, 1.0),
        Point3::new(0.0, 0.0, 1.0),
        Point3::new(0.0, 0.0, 0.0),
        Point3::new(1.0, 0.0, 0.0),
    ];
}

gfx_pipeline!{
    pipeline {
        vertex_buffer: gfx::VertexBuffer<RawVertex> = (),
        transform: gfx::Global<[[f32; 4]; 4]> = "u_transform",
        output: gfx::RenderTarget<gfx::format::Rgba8> = "f_target0",
    }
}

gfx_vertex_struct!{
    RawVertex {
        position: [f32; 3] = "a_position",
        color: [f32; 4] = "a_color",
    }
}

trait GeometricEdge {
    fn front_unit_transform(&self) -> f32;
    fn back_unit_transform(&self) -> f32;
}

impl GeometricEdge for cube::Edge {
    fn front_unit_transform(&self) -> f32 {
        ((self.front() - cube::MIN_EDGE) as f32) / ((cube::MAX_EDGE - cube::MIN_EDGE) as f32)
    }

    fn back_unit_transform(&self) -> f32 {
        let range = cube::MAX_EDGE - cube::MIN_EDGE;
        -((range - (self.back() - cube::MIN_EDGE)) as f32) / (range as f32)
    }
}

pub trait GeometricCube: cube::ComputedCube {
    fn points(&self) -> Vec<Point3>;
}

impl<T: cube::ComputedCube> GeometricCube for T {
    fn points(&self) -> Vec<Point3> {
        // TODO: Compute the points of this cube.
        unimplemented!()
    }
}

impl From<Vertex> for RawVertex {
    fn from(vertex: Vertex) -> Self {
        RawVertex {
            position: *vertex.position.as_ref(),
            color: *vertex.color.as_ref(),
        }
    }
}

struct Vertex {
    pub position: Point3,
    pub color: Vector4,
}

impl Vertex {
    pub fn new(position: Point3, color: Vector4) -> Self {
        Vertex {
            position: position,
            color: color,
        }
    }
}

pub fn vertex_buffer_from_cube<R, F>(cube: &cube::Traversal,
                                     factory: &mut F)
                                     -> (gfx::handle::Buffer<R, RawVertex>, gfx::Slice<R>)
    where R: gfx::Resources,
          F: gfx::traits::FactoryExt<R>
{
    let mut points = Vec::new();
    let mut indeces = Vec::new();
    for (i, cube) in cube.iter().filter(|cube| cube.is_leaf()).enumerate() {
        let width = cube.partition().width();
        let origin: Vector3 = cube.partition().origin().to_vector().into_domain();
        let color = Vector4::new(rand::random::<f32>(),
                                 rand::random::<f32>(),
                                 rand::random::<f32>(),
                                 1.0);
        points.extend(UNIT_CUBE_POINTS.iter()
            .map(|point| (point * cube::exp(width) as RealSpace) + origin)
            .map(|point| RawVertex::from(Vertex::new(point, color))));
        indeces.extend(UNIT_CUBE_INDECES.iter()
            .map(|j| ((UNIT_CUBE_POINTS.len() * i) as Index + *j)));
    }
    factory.create_vertex_buffer_with_slice(points.as_slice(), indeces.as_slice())
}

pub fn projection_from_window(window: &glutin::Window) -> Matrix4 {
    let aspect = {
        let (width, height) = window.get_inner_size_pixels().unwrap();
        width as f32 / height as f32
    };
    nalgebra::PerspectiveMatrix3::new(aspect, 1.0, -1.0, 1.0).to_matrix()
}

pub fn look_at_cube<C>(cube: &C, from: &Point3) -> Matrix4
    where C: cube::ComputedCube
{
    nalgebra::Isometry3::look_at_rh(from,
                                    &cube.partition().midpoint().into_domain(),
                                    &Vector3::new(0.0, 0.0, 1.0))
        .to_homogeneous()
}

#[cfg(test)]
mod tests {
    use super::*;
}
<commit_msg>Consolidate points in unit cube.<commit_after>extern crate gfx;
extern crate gfx_window_glutin;
extern crate glutin;
extern crate lazy_static;
extern crate nalgebra;
extern crate rand;

use nalgebra::ToHomogeneous;

use cube;
use cube::ComputedCube;
use math::{IntoDomain, RealSpace};

pub type Point3 = nalgebra::Point3<RealSpace>;
pub type Vector3 = nalgebra::Vector3<RealSpace>;
pub type Vector4 = nalgebra::Vector4<RealSpace>;
pub type Matrix4 = nalgebra::Matrix4<RealSpace>;
pub type Index = u32;

#[cfg_attr(rustfmt, rustfmt_skip)]
const UNIT_CUBE_INDECES: [Index; 36] = [
    0, 1, 2, 2, 3, 0,
    4, 5, 6, 6, 7, 4,
    6, 5, 2, 2, 1, 6,
    0, 3, 4, 4, 7, 0,
    5, 4, 3, 3, 2, 5,
    1, 0, 7, 7, 6, 1,
];
lazy_static! {
    static ref UNIT_CUBE_POINTS: [Point3; 8] = [
        // Bottom.
        Point3::new(0.0, 0.0, 1.0), // 0
        Point3::new(1.0, 0.0, 1.0), // 1
        Point3::new(1.0, 1.0, 1.0), // 2
        Point3::new(0.0, 1.0, 1.0), // 3
        // Top.
        Point3::new(0.0, 1.0, 0.0), // 4
        Point3::new(1.0, 1.0, 0.0), // 5
        Point3::new(1.0, 0.0, 0.0), // 6
        Point3::new(0.0, 0.0, 0.0), // 7
    ];
}

gfx_pipeline!{
    pipeline {
        vertex_buffer: gfx::VertexBuffer<RawVertex> = (),
        transform: gfx::Global<[[f32; 4]; 4]> = "u_transform",
        output: gfx::RenderTarget<gfx::format::Rgba8> = "f_target0",
    }
}

gfx_vertex_struct!{
    RawVertex {
        position: [f32; 3] = "a_position",
        color: [f32; 4] = "a_color",
    }
}

trait GeometricEdge {
    fn front_unit_transform(&self) -> f32;
    fn back_unit_transform(&self) -> f32;
}

impl GeometricEdge for cube::Edge {
    fn front_unit_transform(&self) -> f32 {
        ((self.front() - cube::MIN_EDGE) as f32) / ((cube::MAX_EDGE - cube::MIN_EDGE) as f32)
    }

    fn back_unit_transform(&self) -> f32 {
        let range = cube::MAX_EDGE - cube::MIN_EDGE;
        -((range - (self.back() - cube::MIN_EDGE)) as f32) / (range as f32)
    }
}

pub trait GeometricCube: cube::ComputedCube {
    fn points(&self) -> Vec<Point3>;
}

impl<T: cube::ComputedCube> GeometricCube for T {
    fn points(&self) -> Vec<Point3> {
        // TODO: Compute the points of this cube.
        unimplemented!()
    }
}

impl From<Vertex> for RawVertex {
    fn from(vertex: Vertex) -> Self {
        RawVertex {
            position: *vertex.position.as_ref(),
            color: *vertex.color.as_ref(),
        }
    }
}

struct Vertex {
    pub position: Point3,
    pub color: Vector4,
}

impl Vertex {
    pub fn new(position: Point3, color: Vector4) -> Self {
        Vertex {
            position: position,
            color: color,
        }
    }
}

pub fn vertex_buffer_from_cube<R, F>(cube: &cube::Traversal,
                                     factory: &mut F)
                                     -> (gfx::handle::Buffer<R, RawVertex>, gfx::Slice<R>)
    where R: gfx::Resources,
          F: gfx::traits::FactoryExt<R>
{
    let mut points = Vec::new();
    let mut indeces = Vec::new();
    for (i, cube) in cube.iter().filter(|cube| cube.is_leaf()).enumerate() {
        let width = cube.partition().width();
        let origin: Vector3 = cube.partition().origin().to_vector().into_domain();
        let color = Vector4::new(rand::random::<f32>(),
                                 rand::random::<f32>(),
                                 rand::random::<f32>(),
                                 1.0);
        points.extend(UNIT_CUBE_POINTS.iter()
            .map(|point| (point * cube::exp(width) as RealSpace) + origin)
            .map(|point| RawVertex::from(Vertex::new(point, color))));
        indeces.extend(UNIT_CUBE_INDECES.iter()
            .map(|j| ((UNIT_CUBE_POINTS.len() * i) as Index + *j)));
    }
    factory.create_vertex_buffer_with_slice(points.as_slice(), indeces.as_slice())
}

pub fn projection_from_window(window: &glutin::Window) -> Matrix4 {
    let aspect = {
        let (width, height) = window.get_inner_size_pixels().unwrap();
        width as f32 / height as f32
    };
    nalgebra::PerspectiveMatrix3::new(aspect, 1.0, -1.0, 1.0).to_matrix()
}

pub fn look_at_cube<C>(cube: &C, from: &Point3) -> Matrix4
    where C: cube::ComputedCube
{
    nalgebra::Isometry3::look_at_rh(from,
                                    &cube.partition().midpoint().into_domain(),
                                    &Vector3::new(0.0, 0.0, 1.0))
        .to_homogeneous()
}

#[cfg(test)]
mod tests {
    use super::*;
}
<|endoftext|>


<commit_before>use ammonia::Ammonia;
use pulldown_cmark::Parser;
use pulldown_cmark::html;

use util::CargoResult;

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

impl<'a> MarkdownRenderer<'a> {
    /// Creates a new renderer instance.
    pub fn new() -> MarkdownRenderer<'a> {
        let tags = [
            "a",
            "b",
            "blockquote",
            "br",
            "code",
            "dd",
            "del",
            "dl",
            "dt",
            "em",
            "i",
            "h1",
            "h2",
            "h3",
            "hr",
            "img",
            "kbd",
            "li",
            "ol",
            "p",
            "pre",
            "s",
            "strike",
            "strong",
            "sub",
            "sup",
            "table",
            "tbody",
            "td",
            "th",
            "thead",
            "tr",
            "ul",
            "hr",
            "span",
        ].iter()
            .cloned()
            .collect();
        let tag_attributes = [
            ("a", ["href", "target"].iter().cloned().collect()),
            (
                "img",
                ["width", "height", "src", "alt", "align", "width"]
                    .iter()
                    .cloned()
                    .collect(),
            ),
            ("code", ["class"].iter().cloned().collect()),
            ("span", ["style"].iter().cloned().collect()),
        ].iter()
            .cloned()
            .collect();
        let html_sanitizer = Ammonia {
            keep_cleaned_elements: true,
            tags: tags,
            tag_attributes: tag_attributes,
            ..Ammonia::default()
        };
        MarkdownRenderer { html_sanitizer: html_sanitizer }
    }

    /// Renders the given markdown to HTML using the current settings.
    pub fn to_html(&self, text: &str) -> CargoResult<String> {
        let mut rendered = String::with_capacity(text.len() * 3 / 2);
        let parser = Parser::new(text);
        html::push_html(&mut rendered, parser);
        Ok(self.html_sanitizer.clean(&rendered))
    }
}

impl<'a> Default for MarkdownRenderer<'a> {
    fn default() -> Self {
        Self::new()
    }
}

/// Renders a markdown text to sanitized HTML.
///
/// The returned text should not contain any harmful HTML tag or attribute (such as iframe,
/// onclick, onmouseover, etc.).
///
/// # Examples
///
/// ```
/// use render::markdown_to_html;
///
/// let text = "[Rust](https://rust-lang.org/) is an awesome *systems programming* language!";
/// let rendered = markdown_to_html(text)?;
/// ```
pub fn markdown_to_html(text: &str) -> CargoResult<String> {
    let renderer = MarkdownRenderer::new();
    renderer.to_html(text)
}

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

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

    #[test]
    fn text_with_script_tag() {
        let text = "foo_readme\n\n";
        let result = markdown_to_html(text);
        assert_eq!(result.is_ok(), true);
        let rendered = result.unwrap();
        assert_eq!(rendered.contains("foo_readme"), true);
        assert_eq!(rendered.contains("script"), false);
        assert_eq!(rendered.contains("alert('Hello World')"), true);
    }

    #[test]
    fn text_with_iframe_tag() {
        let text = "foo_readme\n\nalert('Hello World')";
        let result = markdown_to_html(text);
        assert_eq!(result.is_ok(), true);
        let rendered = result.unwrap();
        assert_eq!(rendered.contains("foo_readme"), true);
        assert_eq!(rendered.contains("iframe"), false);
        assert_eq!(rendered.contains("alert('Hello World')"), true);
    }

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

    #[test]
    fn text_with_inline_javascript() {
        let text = r#"foo_readme\n\nCrate page"#;
        let result = markdown_to_html(text);
        assert_eq!(result.is_ok(), true);
        let rendered = result.unwrap();
        assert_eq!(rendered.contains("foo_readme"), true);
        assert_eq!(rendered.contains("<a"), true);
        assert_eq!(rendered.contains("href="), true);
        assert_eq!(rendered.contains("onclick"), false);
        assert_eq!(rendered.contains("window.alert"), false);
        assert_eq!(rendered.contains("Crate page"), true);
    }
}
<commit_msg>Remove a few allowed attributes<commit_after>use ammonia::Ammonia;
use pulldown_cmark::Parser;
use pulldown_cmark::html;

use util::CargoResult;

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

impl<'a> MarkdownRenderer<'a> {
    /// Creates a new renderer instance.
    pub fn new() -> MarkdownRenderer<'a> {
        let tags = [
            "a",
            "b",
            "blockquote",
            "br",
            "code",
            "dd",
            "del",
            "dl",
            "dt",
            "em",
            "i",
            "h1",
            "h2",
            "h3",
            "hr",
            "img",
            "kbd",
            "li",
            "ol",
            "p",
            "pre",
            "s",
            "strike",
            "strong",
            "sub",
            "sup",
            "table",
            "tbody",
            "td",
            "th",
            "thead",
            "tr",
            "ul",
            "hr",
            "span",
        ].iter()
            .cloned()
            .collect();
        let tag_attributes = [
            ("a", ["href", "target"].iter().cloned().collect()),
            (
                "img",
                ["width", "height", "src", "alt", "align", "width"]
                    .iter()
                    .cloned()
                    .collect(),
            ),
        ].iter()
            .cloned()
            .collect();
        let html_sanitizer = Ammonia {
            keep_cleaned_elements: true,
            tags: tags,
            tag_attributes: tag_attributes,
            ..Ammonia::default()
        };
        MarkdownRenderer { html_sanitizer: html_sanitizer }
    }

    /// Renders the given markdown to HTML using the current settings.
    pub fn to_html(&self, text: &str) -> CargoResult<String> {
        let mut rendered = String::with_capacity(text.len() * 3 / 2);
        let parser = Parser::new(text);
        html::push_html(&mut rendered, parser);
        Ok(self.html_sanitizer.clean(&rendered))
    }
}

impl<'a> Default for MarkdownRenderer<'a> {
    fn default() -> Self {
        Self::new()
    }
}

/// Renders a markdown text to sanitized HTML.
///
/// The returned text should not contain any harmful HTML tag or attribute (such as iframe,
/// onclick, onmouseover, etc.).
///
/// # Examples
///
/// ```
/// use render::markdown_to_html;
///
/// let text = "[Rust](https://rust-lang.org/) is an awesome *systems programming* language!";
/// let rendered = markdown_to_html(text)?;
/// ```
pub fn markdown_to_html(text: &str) -> CargoResult<String> {
    let renderer = MarkdownRenderer::new();
    renderer.to_html(text)
}

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

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

    #[test]
    fn text_with_script_tag() {
        let text = "foo_readme\n\n";
        let result = markdown_to_html(text);
        assert_eq!(result.is_ok(), true);
        let rendered = result.unwrap();
        assert_eq!(rendered.contains("foo_readme"), true);
        assert_eq!(rendered.contains("script"), false);
        assert_eq!(rendered.contains("alert('Hello World')"), true);
    }

    #[test]
    fn text_with_iframe_tag() {
        let text = "foo_readme\n\nalert('Hello World')";
        let result = markdown_to_html(text);
        assert_eq!(result.is_ok(), true);
        let rendered = result.unwrap();
        assert_eq!(rendered.contains("foo_readme"), true);
        assert_eq!(rendered.contains("iframe"), false);
        assert_eq!(rendered.contains("alert('Hello World')"), true);
    }

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

    #[test]
    fn text_with_inline_javascript() {
        let text = r#"foo_readme\n\nCrate page"#;
        let result = markdown_to_html(text);
        assert_eq!(result.is_ok(), true);
        let rendered = result.unwrap();
        assert_eq!(rendered.contains("foo_readme"), true);
        assert_eq!(rendered.contains("<a"), true);
        assert_eq!(rendered.contains("href="), true);
        assert_eq!(rendered.contains("onclick"), false);
        assert_eq!(rendered.contains("window.alert"), false);
        assert_eq!(rendered.contains("Crate page"), true);
    }
}
<|endoftext|>


<commit_before>use argvalues::ArgValues;
use download;
use error;
use gfx;
use loader;

use old_school_gfx_glutin_ext::*;

use gfx::{traits::FactoryExt, Device};
use glutin::{
    event::{Event, KeyboardInput, VirtualKeyCode, WindowEvent},
    event_loop::{ControlFlow, EventLoop},
    window::WindowBuilder,
};

use glutin::event::{ElementState, MouseButton};

use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use std::path::Path;
use std::sync::mpsc::{channel, TryRecvError};

use std::time::{Duration, Instant};

pub enum TextureId {
    ZERO,
    ONE,
    TWO,
    THREE,
}

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

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

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

        // Uniforms
        i_global_time: gfx::Global<f32> = "iGlobalTime",
        i_time: gfx::Global<f32> = "iTime",
        i_resolution: gfx::Global<[f32; 3]> = "iResolution",
        i_mouse: gfx::Global<[f32; 4]> = "iMouse",
        i_frame: gfx::Global<i32> = "iFrame",
        i_channel0: gfx::TextureSampler<[f32; 4]> = "iChannel0",
        i_channel1: gfx::TextureSampler<[f32; 4]> = "iChannel1",
        i_channel2: gfx::TextureSampler<[f32; 4]> = "iChannel2",
        i_channel3: gfx::TextureSampler<[f32; 4]> = "iChannel3",

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

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

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

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

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

    // Load vertex and fragment shaders into byte buffers
    let vert_src_buf = loader::load_vertex_shader();
    let frag_src_buf = match av.getid {
        Some(ref id) => {
            let (_, shadercode) = download::download(id)?;

            // Don't run default shader if downloading (with no --run flag)
            if av.getid.is_some() && !av.andrun {
                return Ok(());
            }

            if av.andrun {
                loader::format_shader_src(&shadercode)
            } else {
                loader::load_fragment_shader(&av)?
            }
        }
        None => loader::load_fragment_shader(&av)?,
    };

    let (tx, rx) = channel();
    let mut watcher = watcher(tx, Duration::from_millis(250)).expect("couldn't initialise notify");
    let avsp = match av.shaderpath {
      None => String::new(),
      Some(_) => {
        av.shaderpath.clone().unwrap()
      }
    };
    let shader_basename =
    if avsp != "" {
      let path = Path::new(&avsp);
      watcher
          .watch(path.parent().unwrap(), RecursiveMode::NonRecursive)
          .expect("couldn't register inotify watch");
      Some(path.clone().file_name().unwrap())
    } else {None};

    let event_loop = EventLoop::new();
    let window_config = WindowBuilder::new()
        .with_title("shadertoy-rs")
        .with_inner_size(glutin::dpi::PhysicalSize::new(width, height));

    let (window, mut device, mut factory, main_color, mut main_depth) =
        glutin::ContextBuilder::new()
            .with_gfx_color_depth::<ColorFormat, DepthFormat>()
            .build_windowed(window_config, &event_loop)
            .unwrap()
            .init_gfx::<ColorFormat, DepthFormat>();

    let mut encoder = gfx::Encoder::from(factory.create_command_buffer());

    let mut pso = factory
        .create_pipeline_simple(&vert_src_buf, &frag_src_buf, pipe::new())
        .unwrap();

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

    // Load textures
    let texture0 = loader::load_texture(&TextureId::ZERO, &av.texture0path, &mut factory)?;
    let texture1 = loader::load_texture(&TextureId::ONE, &av.texture1path, &mut factory)?;
    let texture2 = loader::load_texture(&TextureId::TWO, &av.texture2path, &mut factory)?;
    let texture3 = loader::load_texture(&TextureId::THREE, &av.texture3path, &mut factory)?;

    let sampler = factory.create_sampler_linear();

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

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

        i_channel0: (texture0, sampler.clone()),
        i_channel1: (texture1, sampler.clone()),
        i_channel2: (texture2, sampler.clone()),
        i_channel3: (texture3, sampler.clone()),

        frag_color: main_color,
    };

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

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

    let mut xyzw = [0.0; 4];

    let mut start_time = Instant::now();
    let mut running = true;

    event_loop.run(move |event, encoder, control_flow| {
        *control_flow = ControlFlow::Poll;
        let mut shader_modified = false;

        if let Event::WindowEvent { event, .. } = event {
            match event {
                WindowEvent::CloseRequested
                | WindowEvent::KeyboardInput {
                    input:
                        KeyboardInput {
                            virtual_keycode: Some(VirtualKeyCode::Escape),
                            ..
                        },
                    ..
                } => running = false,

                WindowEvent::KeyboardInput {
                    input:
                        KeyboardInput {
                            virtual_keycode: Some(VirtualKeyCode::F5),
                            ..
                        },
                    ..
                } => shader_modified = true,

                WindowEvent::Resized(size) => {
                    window.update_gfx(&mut data.frag_color, &mut main_depth);
                    window.resize(size);

                    width = size.width as f32;
                    height = size.height as f32;
                }

                WindowEvent::CursorMoved {
                    position: cursor_position,
                    ..
                } => {
                    mx = cursor_position.x as f32;
                    my = height - cursor_position.y as f32; // Flip y-axis
                }

                WindowEvent::MouseInput { state, button, .. } => {
                    last_mouse = current_mouse;
                    if state == ElementState::Pressed && button == MouseButton::Left {
                        current_mouse = ElementState::Pressed;
                    } else {
                        current_mouse = ElementState::Released;
                    }
                }

                _ => (),
            }
        }
        // notify handling
        shader_modified = shader_modified
            | match shader_basename {
                None => false,
                Some(_) => {
                    let mut have_events = false;
                    let basename = shader_basename.unwrap();

                    loop {
                        match rx.try_recv() {
                            Err(TryRecvError::Empty) => break,

                            // we handle both create and write here because some text editors write
                            // the modified file to a tmpfile then move it
                            Ok(DebouncedEvent::Create(ref path))
                            | Ok(DebouncedEvent::Write(ref path))
                            | Ok(DebouncedEvent::Rename(_, ref path))
                                if path.ends_with(basename) =>
                            {
                                have_events = true
                            }

                            Ok(_ev) => {
                                // println!(" >> unhandled notify event: {:?}", _ev);
                            }

                            Err(TryRecvError::Disconnected) => {
                                println!(" !! watch disconnected");
                                break;
                            }
                        }
                    }

                    have_events
                }
            };

        // this is a while loop so we can "break;" out of it prematurely
        // in the event that we can't recompile the shader, this means that the
        // old shader just continues running, and then we dump the error to stdout.
        while shader_modified {
            // Reload fragment shader into byte buffer
            let frag_src_res = loader::load_fragment_shader(&av);
            if frag_src_res.is_err() {
                println!("failed to load fragment shader");
                break;
            }
            let frag_src_res = frag_src_res.unwrap();
            let frag_src_buf = frag_src_res.as_slice();

            // Recreate pipeline
            let pso_res = factory.create_pipeline_simple(&vert_src_buf, &frag_src_buf, pipe::new());

            pso = match pso_res {
                Ok(pso) => pso,
                Err(e) => {
                    println!("failed to create pipeline: {:?}", e);
                    break;
                }
            };

            // Reset uniforms
            data.i_global_time = 0.0;
            data.i_time = 0.0;
            data.i_resolution = [width, height, width / height];
            data.i_mouse = [0.0; 4];
            data.i_frame = -1;

            start_time = Instant::now();

            break;
        }

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

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

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

        // Frame
        data.i_frame += 1;

        // Draw
        encoder.clear(&data.frag_color, CLEAR_COLOR);
        encoder.draw(&slice, &pso, &data);
        encoder.flush(&mut device);
        window.swap_buffers().unwrap();
        device.cleanup();
    });
}
<commit_msg>Issue fixed. Doesn't compile<commit_after>use argvalues::ArgValues;
use download;
use error;
use gfx;
use loader;

use old_school_gfx_glutin_ext::*;

use gfx::{traits::FactoryExt, Device};
use glutin::{
    event::{Event, KeyboardInput, VirtualKeyCode, WindowEvent},
    event_loop::{ControlFlow, EventLoop},
    window::WindowBuilder,
};

use glutin::event::{ElementState, MouseButton};

use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
use std::path::Path;
use std::sync::mpsc::{channel, TryRecvError};

use std::time::{Duration, Instant};

pub enum TextureId {
    ZERO,
    ONE,
    TWO,
    THREE,
}

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

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

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

        // Uniforms
        i_global_time: gfx::Global<f32> = "iGlobalTime",
        i_time: gfx::Global<f32> = "iTime",
        i_resolution: gfx::Global<[f32; 3]> = "iResolution",
        i_mouse: gfx::Global<[f32; 4]> = "iMouse",
        i_frame: gfx::Global<i32> = "iFrame",
        i_channel0: gfx::TextureSampler<[f32; 4]> = "iChannel0",
        i_channel1: gfx::TextureSampler<[f32; 4]> = "iChannel1",
        i_channel2: gfx::TextureSampler<[f32; 4]> = "iChannel2",
        i_channel3: gfx::TextureSampler<[f32; 4]> = "iChannel3",

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

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

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

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

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

    // Load vertex and fragment shaders into byte buffers
    let vert_src_buf = loader::load_vertex_shader();
    let frag_src_buf = match av.getid {
        Some(ref id) => {
            let (_, shadercode) = download::download(id)?;

            // Don't run default shader if downloading (with no --run flag)
            if av.getid.is_some() && !av.andrun {
                return Ok(());
            }

            if av.andrun {
                loader::format_shader_src(&shadercode)
            } else {
                loader::load_fragment_shader(&av)?
            }
        }
        None => loader::load_fragment_shader(&av)?,
    };

    let (tx, rx) = channel();
    let mut watcher = watcher(tx, Duration::from_millis(250)).expect("couldn't initialise notify");
    let avsp = match av.shaderpath {
      None => String::new(),
      Some(_) => {
        av.shaderpath.clone().unwrap()
      }
    };
    let shader_basename =
    if avsp != "" {
      let path = Path::new(&avsp);
      watcher
          .watch(path.parent().unwrap(), RecursiveMode::NonRecursive)
          .expect("couldn't register inotify watch");
      Some(path.clone().file_name().unwrap())
    } else {None};

    let event_loop = EventLoop::new();
    let window_config = WindowBuilder::new()
        .with_title("shadertoy-rs")
        .with_inner_size(glutin::dpi::PhysicalSize::new(width, height));

    let (window, mut device, mut factory, main_color, mut main_depth) =
        glutin::ContextBuilder::new()
            .with_gfx_color_depth::<ColorFormat, DepthFormat>()
            .build_windowed(window_config, &event_loop)
            .unwrap()
            .init_gfx::<ColorFormat, DepthFormat>();

    let mut encoder = gfx::Encoder::from(factory.create_command_buffer());

    let mut pso = factory
        .create_pipeline_simple(&vert_src_buf, &frag_src_buf, pipe::new())
        .unwrap();

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

    // Load textures
    let texture0 = loader::load_texture(&TextureId::ZERO, &av.texture0path, &mut factory)?;
    let texture1 = loader::load_texture(&TextureId::ONE, &av.texture1path, &mut factory)?;
    let texture2 = loader::load_texture(&TextureId::TWO, &av.texture2path, &mut factory)?;
    let texture3 = loader::load_texture(&TextureId::THREE, &av.texture3path, &mut factory)?;

    let sampler = factory.create_sampler_linear();

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

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

        i_channel0: (texture0, sampler.clone()),
        i_channel1: (texture1, sampler.clone()),
        i_channel2: (texture2, sampler.clone()),
        i_channel3: (texture3, sampler.clone()),

        frag_color: main_color,
    };

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

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

    let mut xyzw = [0.0; 4];

    let mut start_time = Instant::now();
    let mut running = true;

    event_loop.run(move |event, _, control_flow| {
        *control_flow = ControlFlow::Poll;
        let mut shader_modified = false;

        if let Event::WindowEvent { event, .. } = event {
            match event {
                WindowEvent::CloseRequested
                | WindowEvent::KeyboardInput {
                    input:
                        KeyboardInput {
                            virtual_keycode: Some(VirtualKeyCode::Escape),
                            ..
                        },
                    ..
                } => running = false,

                WindowEvent::KeyboardInput {
                    input:
                        KeyboardInput {
                            virtual_keycode: Some(VirtualKeyCode::F5),
                            ..
                        },
                    ..
                } => shader_modified = true,

                WindowEvent::Resized(size) => {
                    window.update_gfx(&mut data.frag_color, &mut main_depth);
                    window.resize(size);

                    width = size.width as f32;
                    height = size.height as f32;
                }

                WindowEvent::CursorMoved {
                    position: cursor_position,
                    ..
                } => {
                    mx = cursor_position.x as f32;
                    my = height - cursor_position.y as f32; // Flip y-axis
                }

                WindowEvent::MouseInput { state, button, .. } => {
                    last_mouse = current_mouse;
                    if state == ElementState::Pressed && button == MouseButton::Left {
                        current_mouse = ElementState::Pressed;
                    } else {
                        current_mouse = ElementState::Released;
                    }
                }

                _ => (),
            }
        }
        // notify handling
        shader_modified = shader_modified
            | match shader_basename {
                None => false,
                Some(_) => {
                    let mut have_events = false;
                    let basename = shader_basename.unwrap();

                    loop {
                        match rx.try_recv() {
                            Err(TryRecvError::Empty) => break,

                            // we handle both create and write here because some text editors write
                            // the modified file to a tmpfile then move it
                            Ok(DebouncedEvent::Create(ref path))
                            | Ok(DebouncedEvent::Write(ref path))
                            | Ok(DebouncedEvent::Rename(_, ref path))
                                if path.ends_with(basename) =>
                            {
                                have_events = true
                            }

                            Ok(_ev) => {
                                // println!(" >> unhandled notify event: {:?}", _ev);
                            }

                            Err(TryRecvError::Disconnected) => {
                                println!(" !! watch disconnected");
                                break;
                            }
                        }
                    }

                    have_events
                }
            };

        // this is a while loop so we can "break;" out of it prematurely
        // in the event that we can't recompile the shader, this means that the
        // old shader just continues running, and then we dump the error to stdout.
        while shader_modified {
            // Reload fragment shader into byte buffer
            let frag_src_res = loader::load_fragment_shader(&av);
            if frag_src_res.is_err() {
                println!("failed to load fragment shader");
                break;
            }
            let frag_src_res = frag_src_res.unwrap();
            let frag_src_buf = frag_src_res.as_slice();

            // Recreate pipeline
            let pso_res = factory.create_pipeline_simple(&vert_src_buf, &frag_src_buf, pipe::new());

            pso = match pso_res {
                Ok(pso) => pso,
                Err(e) => {
                    println!("failed to create pipeline: {:?}", e);
                    break;
                }
            };

            // Reset uniforms
            data.i_global_time = 0.0;
            data.i_time = 0.0;
            data.i_resolution = [width, height, width / height];
            data.i_mouse = [0.0; 4];
            data.i_frame = -1;

            start_time = Instant::now();

            break;
        }

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

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

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

        // Frame
        data.i_frame += 1;

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