<commit_before>extern crate image;
extern crate dungenon;
extern crate cast;

use std::io;
use std::path::PathBuf;

use dungenon::level::Level;
use dungenon::tile::Tile;
use dungenon::generator::{MazeGen, RoomGen, DungeonGen};
use dungenon::util::Error;

use image::RgbImage;
use image::Rgb;

use std::collections::HashMap;

fn main() {
    Drawer::new().start();
}

struct Drawer {
    pub colors: HashMap<Tile, Rgb<u8>>,
}

impl Drawer {
    pub fn new() -> Drawer {
        let mut colors = HashMap::new();
        colors.insert(Tile::Wall(0), Rgb([127u8, 127u8, 127u8]));
        colors.insert(Tile::Floor(0), Rgb([200u8, 200u8, 200u8]));
        colors.insert(Tile::Void(0), Rgb([0u8, 0u8, 0u8]));
        Drawer{
            colors: colors,
        }
    }

    pub fn start(&mut self) {
        println!("Choose level type(faction, tile):");
        let t = Self::string_from_cmd();
        match t.trim() {
            "faction" => self.start_faction(),
            "tile" => self.start_tile(),
        }
    }

    pub fn start_faction(&mut self) {

    }

    pub fn start_tile(&mut self) {
        println!("Initializing level...");
        let mut level = self.init_tile_level();

        println!("This is the dungenon-drawer. \nType Help for list of commands.");
        loop {
            println!("Enter command:");
            let command = Self::string_from_cmd();
            match command.trim() {
                "dungeon" => Self::carve_dungeon(&mut level),
                "maze" => Self::carve_maze(&mut level),
                "room" => Self::carve_rooms(&mut level),
                "reset" => level = self.init_level(),
                "colors" => self.change_colors(),
                "export" => {
                    self.png_export(&mut level);
                },
                "print" => Self::print_level(&level),
                "help" => {
                    println!("Options: dungeon, maze, room, reset, colors, export, print, help, and exit");
                }
                "exit" => break,
                _ => println!("Invalid command."),
            }
        }
    }

    fn change_colors(&mut self) {
        use cast;
        use std::u8;
        loop {
            println!("Which color would you like to change? (floor, wall, void), Type exit if you're done with modifying colors.");
            let command = Self::string_from_cmd();
            let key = match command.trim() {
                "floor" => Tile::Floor(0),
                "wall" => Tile::Wall(0),
                "void" => Tile::Void(0),
                "exit" => break,
                _ => {
                    println!("Invalid command.");
                    continue;
                },
            };
            println!("Input red value for the color:");
            let r = cast::u8(Self::usize_from_cmd()).unwrap_or(u8::MAX);

            println!("Input green value for the color:");
            let g = cast::u8(Self::usize_from_cmd()).unwrap_or(u8::MAX);

            println!("Input blue value for the color:");
            let b = cast::u8(Self::usize_from_cmd()).unwrap_or(u8::MAX);
            self.colors.insert(key, Rgb([r,g,b]));
        }
    }

    fn init_tile_level(&self) -> Level<Tile> {
        println!("Input level width: ");
        let x = Self::usize_from_cmd();

        println!("Input level height: ");
        let y = Self::usize_from_cmd();

        Level::new_filled_with(Tile::Wall(0), x, y)
    }

    fn carve_dungeon(level: &mut Level<Tile>) {
        println!("Creating DungeonGen...");
        let mazegen = Self::create_mazegen();
        let roomgen = Self::create_roomgen();
        let mut dungeongen = DungeonGen::new(mazegen, roomgen);
        level.apply(|m| dungeongen.generate(m));
    }

    fn create_mazegen() -> MazeGen {
        println!("Input MazeGen startpos x coordinate: ");
        let x = Self::usize_from_cmd();

        println!("Input MazeGen startpos y coordinate: ");
        let y = Self::usize_from_cmd();
        MazeGen::new(x,y)
    }

    fn carve_maze(level: &mut Level<Tile>) {
        let mut mazegen = Self::create_mazegen();
        level.apply(|m| mazegen.generate(m));
    }

    fn create_roomgen() -> RoomGen {
        println!("Input min room size:");
        let min_room_size = Self::usize_from_cmd();

        println!("Input max room size:");
        let max_room_size = Self::usize_from_cmd();

        println!("Input min room distance:");
        let room_distance = Self::usize_from_cmd();

        println!("Input room placement amount (Something high preferably):");
        let attempts = Self::u64_from_cmd();

        RoomGen::new(min_room_size, max_room_size, room_distance, attempts)
    }

    fn carve_rooms(level: &mut Level<Tile>) {
        let mut roomgen = Self::create_roomgen();
        level.apply(|m| roomgen.generate(m));
    }

    fn png_export(&self, level: &mut Level<Tile>) {
        let mut level_image = RgbImage::new(level.get_width() as u32, level.get_height() as u32);
        for x in 0 .. level.get_width() {
            for y in 0 .. level.get_height() {
                level_image.put_pixel(x as u32, y as u32, self.tile_to_color(&level.get_tile_with_tuple((x,y))));
            }
        }
        let mut p = PathBuf::new();
        println!("Enter png name:");
        p.push(Self::string_from_cmd().trim());
        p.set_extension("png");
        level_image.save(p.as_path()).expect("Something went wrong when saving the png.");
    }

    fn tile_to_color(&self, tile: &Result<&Tile, Error>) -> Rgb<u8> {
        match *tile {
            Ok(ref tile) => {
                    match self.colors.get(tile) {
                        Some(color) => color.clone(),
                        None => Rgb([255u8, 0u8, 255u8]),
                    }
            },
            Err(Error::IndexOutOfBounds) => panic!("Tile png import failed. Level indexing went out of bounds."),
        }
    }

    fn print_level(level: &Level<Tile>) {
        use dungenon::util::Error;
        let mut string = String::new();
        for y in 0 .. level.get_width() {
            for x in 0 .. level.get_height() {
                match level.get_tile_with_tuple((x,y)) {
                    Ok(tile) => {
                        match tile {
                            &Tile::Wall(_) => string.push('#'),
                            &Tile::Floor(_) => string.push(' '),
                            &Tile::Void(_) => string.push('*'),
                        }
                    },
                    Err(Error::IndexOutOfBounds) => panic!("IndexOutOfBounds occurred. Level printing wasn't implemented properly."),
                }
            }
            string.push('\n');
        }
        println!("{}", &string);
    }

    fn usize_from_cmd() -> usize {
        let mut num = String::new();

        io::stdin().read_line(&mut num)
            .expect("failed to read line");

        let num: usize = num.trim().parse()
            .expect("Please type a number!");
        num
    }

    fn u64_from_cmd() -> u64 {
        let mut num = String::new();

        io::stdin().read_line(&mut num)
            .expect("failed to read line");

        let num: u64 = num.trim().parse()
            .expect("Please type a number!");
        num
    }

    fn string_from_cmd() -> String {
        let mut string = String::new();
        io::stdin().read_line(&mut string)
        .expect("Failed to read line");
        string
    }
}
<commit_msg>implementing faction stuff almost done.<commit_after>extern crate image;
extern crate dungenon;
extern crate cast;

use std::io;
use std::path::PathBuf;

use dungenon::level::Level;
use dungenon::tile::{Tile, Faction};
use dungenon::generator::{MazeGen, RoomGen, DungeonGen, FactionGen};
use dungenon::util::Error;

use image::RgbImage;
use image::Rgb;

use std::collections::HashMap;

fn main() {
    Drawer::new().start();
}

struct Drawer {
    pub dungeon_colors: HashMap<Tile, Rgb<u8>>,
    pub faction_colors: HashMap<Faction, Rgb<u8><,
}

impl Drawer {
    pub fn new() -> Drawer {
        let mut dungeon_colors = HashMap::new();
        dungeon_colors.insert(Tile::Wall(0), Rgb([127u8, 127u8, 127u8]));
        dungeon_colors.insert(Tile::Floor(0), Rgb([200u8, 200u8, 200u8]));
        dungeon_colors.insert(Tile::Void(0), Rgb([0u8, 0u8, 0u8]));

        let mut faction_colors = HashMap::new();
        faction_colors.insert(Faction::Faction(0), Rgb([200u8, 0u8, 0u8]));
        faction_colors.insert(Faction::Faction(1), Rgb([0u8, 0u8, 127u8]));
        faction_colors.insert(Faction::Neutral, Rgb([127u8, 127u8, 127u8]));
        faction_colors.insert(Faction::Void, Rgb([0u8, 0u8, 0u8]));

        Drawer{
            dungeon_colors: dungeon_colors,
            faction_colors: faction_colors,
        }
    }

    pub fn start(&mut self) {
        println!("Choose level type(faction, tile):");
        let t = Self::string_from_cmd();
        match t.trim() {
            "faction" => self.start_faction(),
            "tile" => self.start_tile(),
        }
    }

    pub fn start_faction(&mut self) {
        let mut level = self.init_faction_level();
        let simulator = FactionGen::new();

        println!("This is the faction mode of dungenon-drawer.");
        println!("Type Help for list of possible commands.")
        loop {
            println!("Enter command:");
            let command = Self::string_from_cmd();
            match command.trim() {
                "simulate" => Self::simulate_factions(&mut level, &mut simulator),
                "factions" => Self::edit_level(&mut level),
                "colors" => self.change_faction_colors(),
                "export" => self.png_export("&mut Level"),
                "help" => println!("Options: help, simulate, factions, colors, export and exit"),
                "exit" => break,
                _ => println!("Invalid command.")
            }
        }
    }

    fn init_faction_level(&self) -> Level<Faction> {
        println!("Initializing level...");
        println!("Input level width: ");
        let x = Self::usize_from_cmd();

        println!("Input level height: ");
        let y = Self::usize_from_cmd();

        Level::new_filled_with(Faction::Neutral, x, y)
    }

    fn simulate_factions(level: &mut Level<Faction>, simulator: &mut FactionGen) {
        println!("How many simulation steps are iterated?");
        let iterations = Self::u64_from_cmd();
        for 0..iterations {
            level.apply(|m| simulator.generate(m));
        }
    }

    fn edit_level(level: &mut Level<Faction>) {
        loop {
            println!("Input x coordinate:");
            let x = Self::usize_from_cmd();
            println!("Input y coordinate:");
            let y = Self::usize_from_cmd();

            println!("Select type(faction, neutral, void):");
            let faction_type = Self::string_from_cmd();
            let faction = match faction.trim() {
                "faction" => {
                    println!("Input faction number:");
                    let faction_number = Self::usize_from_cmd();
                    Faction::Faction(faction_number)
                },
                "neutral" => Faction::Neutral,
                "void" => Faction::Void,
                _ => {
                    println!("Invalid type.");
                    break;
                }
            }
            match level.get_mut_tile(x,y) {
                Ok(tile) => {
                    *tile = faction;
                },
                Err(_) => println!("Index out of bounds"),
            }
        }
    }

    pub fn start_tile(&mut self) {
        let mut level = self.init_tile_level();

        println!("This is the tile mode of dungenon-drawer. \nType Help for list of commands.");
        loop {
            println!("Enter command:");
            let command = Self::string_from_cmd();
            match command.trim() {
                "dungeon" => Self::carve_dungeon(&mut level),
                "maze" => Self::carve_maze(&mut level),
                "room" => Self::carve_rooms(&mut level),
                "reset" => level = self.init_level(),
                "colors" => self.change_dungeon_colors(),
                "export" => {
                    self.png_export(&mut level);
                },
                "print" => Self::print_level(&level),
                "help" => {
                    println!("Options: dungeon, maze, room, reset, colors, export, print, help, and exit");
                }
                "exit" => break,
                _ => println!("Invalid command."),
            }
        }
    }

    fn change_dungeon_colors(&mut self) {
        use cast;
        use std::u8;
        loop {
            println!("Which color would you like to change? (floor, wall, void), Type exit if you're done with modifying colors.");
            let command = Self::string_from_cmd();
            let key = match command.trim() {
                "floor" => Tile::Floor(0),
                "wall" => Tile::Wall(0),
                "void" => Tile::Void(0),
                "exit" => break,
                _ => {
                    println!("Invalid command.");
                    continue;
                },
            };
            println!("Input red value for the color:");
            let r = cast::u8(Self::usize_from_cmd()).unwrap_or(u8::MAX);

            println!("Input green value for the color:");
            let g = cast::u8(Self::usize_from_cmd()).unwrap_or(u8::MAX);

            println!("Input blue value for the color:");
            let b = cast::u8(Self::usize_from_cmd()).unwrap_or(u8::MAX);
            self.colors.insert(key, Rgb([r,g,b]));
        }
    }

    fn init_tile_level(&self) -> Level<Tile> {
        println!("Initializing level...");
        println!("Input level width: ");
        let x = Self::usize_from_cmd();

        println!("Input level height: ");
        let y = Self::usize_from_cmd();

        Level::new_filled_with(Tile::Wall(0), x, y)
    }

    fn carve_dungeon(level: &mut Level<Tile>) {
        println!("Creating DungeonGen...");
        let mazegen = Self::create_mazegen();
        let roomgen = Self::create_roomgen();
        let mut dungeongen = DungeonGen::new(mazegen, roomgen);
        level.apply(|m| dungeongen.generate(m));
    }

    fn create_mazegen() -> MazeGen {
        println!("Input MazeGen startpos x coordinate: ");
        let x = Self::usize_from_cmd();

        println!("Input MazeGen startpos y coordinate: ");
        let y = Self::usize_from_cmd();
        MazeGen::new(x,y)
    }

    fn carve_maze(level: &mut Level<Tile>) {
        let mut mazegen = Self::create_mazegen();
        level.apply(|m| mazegen.generate(m));
    }

    fn create_roomgen() -> RoomGen {
        println!("Input min room size:");
        let min_room_size = Self::usize_from_cmd();

        println!("Input max room size:");
        let max_room_size = Self::usize_from_cmd();

        println!("Input min room distance:");
        let room_distance = Self::usize_from_cmd();

        println!("Input room placement amount (Something high preferably):");
        let attempts = Self::u64_from_cmd();

        RoomGen::new(min_room_size, max_room_size, room_distance, attempts)
    }

    fn carve_rooms(level: &mut Level<Tile>) {
        let mut roomgen = Self::create_roomgen();
        level.apply(|m| roomgen.generate(m));
    }

    fn png_export(&self, level: &mut Level<Tile>) {
        let mut level_image = RgbImage::new(level.get_width() as u32, level.get_height() as u32);
        for x in 0 .. level.get_width() {
            for y in 0 .. level.get_height() {
                level_image.put_pixel(x as u32, y as u32, self.tile_to_color(&level.get_tile_with_tuple((x,y))));
            }
        }
        let mut p = PathBuf::new();
        println!("Enter png name:");
        p.push(Self::string_from_cmd().trim());
        p.set_extension("png");
        level_image.save(p.as_path()).expect("Something went wrong when saving the png.");
    }

    fn tile_to_color(&self, tile: &Result<&Tile, Error>) -> Rgb<u8> {
        match *tile {
            Ok(ref tile) => {
                    match self.dungeon_colors.get(tile) {
                        Some(color) => color.clone(),
                        None => Rgb([255u8, 0u8, 255u8]),
                    }
            },
            Err(Error::IndexOutOfBounds) => panic!("Tile png import failed. Level indexing went out of bounds."),
        }
    }

    fn print_level(level: &Level<Tile>) {
        use dungenon::util::Error;
        let mut string = String::new();
        for y in 0 .. level.get_width() {
            for x in 0 .. level.get_height() {
                match level.get_tile_with_tuple((x,y)) {
                    Ok(tile) => {
                        match tile {
                            &Tile::Wall(_) => string.push('#'),
                            &Tile::Floor(_) => string.push(' '),
                            &Tile::Void(_) => string.push('*'),
                        }
                    },
                    Err(Error::IndexOutOfBounds) => panic!("IndexOutOfBounds occurred. Level printing wasn't implemented properly."),
                }
            }
            string.push('\n');
        }
        println!("{}", &string);
    }

    fn usize_from_cmd() -> usize {
        let mut num = String::new();

        io::stdin().read_line(&mut num)
            .expect("failed to read line");

        let num: usize = num.trim().parse()
            .expect("Please type a number!");
        num
    }

    fn u64_from_cmd() -> u64 {
        let mut num = String::new();

        io::stdin().read_line(&mut num)
            .expect("failed to read line");

        let num: u64 = num.trim().parse()
            .expect("Please type a number!");
        num
    }

    fn string_from_cmd() -> String {
        let mut string = String::new();
        io::stdin().read_line(&mut string)
        .expect("Failed to read line");
        string
    }
}
<|endoftext|>


<commit_before>use crate::exercise::{Exercise, ExerciseList};
use crate::run::run;
use crate::verify::verify;
use clap::{crate_version, App, Arg, SubCommand};
use notify::DebouncedEvent;
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
use std::ffi::OsStr;
use std::fs;
use std::path::Path;
use std::sync::mpsc::channel;
use std::time::Duration;

mod exercise;
mod run;
mod verify;

fn main() {
    let matches = App::new("rustlings")
        .version(crate_version!())
        .author("Olivia Hugger, Carol Nichols")
        .about("Rustlings is a collection of small exercises to get you used to writing and reading Rust code")
        .subcommand(SubCommand::with_name("verify").alias("v").about("Verifies all exercises according to the recommended order"))
        .subcommand(SubCommand::with_name("watch").alias("w").about("Reruns `verify` when files were edited"))
        .subcommand(
            SubCommand::with_name("run")
                .alias("r")
                .about("Runs/Tests a single exercise")
                .arg(Arg::with_name("file").required(true).index(1))
                .arg(Arg::with_name("test").short("t").long("test").help("Run the file as a test")),
        )
        .get_matches();

    if None == matches.subcommand_name() {
        println!();
        println!(r#"       welcome to...                      "#);
        println!(r#"                 _   _ _                  "#);
        println!(r#"  _ __ _   _ ___| |_| (_)_ __   __ _ ___  "#);
        println!(r#" | '__| | | / __| __| | | '_ \ / _` / __| "#);
        println!(r#" | |  | |_| \__ \ |_| | | | | | (_| \__ \ "#);
        println!(r#" |_|   \__,_|___/\__|_|_|_| |_|\__, |___/ "#);
        println!(r#"                               |___/      "#);
        println!();
    }

    if !Path::new("info.toml").exists() {
        println!(
            "{} must be run from the rustlings directory",
            std::env::current_exe().unwrap().to_str().unwrap()
        );
        println!("Try `cd rustlings/`!");
        std::process::exit(1);
    }

    let toml_str = &fs::read_to_string("info.toml").unwrap();
    let exercises = toml::from_str::<ExerciseList>(toml_str).unwrap().exercises;

    if let Some(ref matches) = matches.subcommand_matches("run") {
        let filename = matches.value_of("file").unwrap_or_else(|| {
            println!("Please supply a file name!");
            std::process::exit(1);
        });

        let matching_exercise = |e: &&Exercise| {
            Path::new(filename)
                .canonicalize()
                .map(|p| p.ends_with(&e.path))
                .unwrap_or(false)
        };

        let exercise = exercises.iter().find(matching_exercise).unwrap_or_else(|| {
            println!("No exercise found for your file name!");
            std::process::exit(1)
        });

        run(&exercise).unwrap_or_else(|_| std::process::exit(1));
    }

    if matches.subcommand_matches("verify").is_some() {
        verify(&exercises).unwrap_or_else(|_| std::process::exit(1));
    }

    if matches.subcommand_matches("watch").is_some() {
        watch(&exercises).unwrap();
    }

    if matches.subcommand_name().is_none() {
        let text = fs::read_to_string("default_out.txt").unwrap();
        println!("{}", text);
    }
}

fn watch(exercises: &[Exercise]) -> notify::Result<()> {
    let (tx, rx) = channel();

    let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(2))?;
    watcher.watch(Path::new("./exercises"), RecursiveMode::Recursive)?;

    let _ignored = verify(exercises.iter());

    loop {
        match rx.recv() {
            Ok(event) => match event {
                DebouncedEvent::Create(b) | DebouncedEvent::Chmod(b) | DebouncedEvent::Write(b) => {
                    if b.extension() == Some(OsStr::new("rs")) && b.exists() {
                        println!("----------**********----------\n");
                        let filepath = b.as_path().canonicalize().unwrap();
                        let exercise = exercises
                            .iter()
                            .skip_while(|e| !filepath.ends_with(&e.path));
                        let _ignored = verify(exercise);
                    }
                }
                _ => {}
            },
            Err(e) => println!("watch error: {:?}", e),
        }
    }
}
<commit_msg>Auto merge of #227 - workingjubilee:watch-clears-terminal, r=fmoko<commit_after>use crate::exercise::{Exercise, ExerciseList};
use crate::run::run;
use crate::verify::verify;
use clap::{crate_version, App, Arg, SubCommand};
use notify::DebouncedEvent;
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
use std::ffi::OsStr;
use std::fs;
use std::path::Path;
use std::sync::mpsc::channel;
use std::time::Duration;

mod exercise;
mod run;
mod verify;

fn main() {
    let matches = App::new("rustlings")
        .version(crate_version!())
        .author("Olivia Hugger, Carol Nichols")
        .about("Rustlings is a collection of small exercises to get you used to writing and reading Rust code")
        .subcommand(SubCommand::with_name("verify").alias("v").about("Verifies all exercises according to the recommended order"))
        .subcommand(SubCommand::with_name("watch").alias("w").about("Reruns `verify` when files were edited"))
        .subcommand(
            SubCommand::with_name("run")
                .alias("r")
                .about("Runs/Tests a single exercise")
                .arg(Arg::with_name("file").required(true).index(1))
                .arg(Arg::with_name("test").short("t").long("test").help("Run the file as a test")),
        )
        .get_matches();

    if None == matches.subcommand_name() {
        println!();
        println!(r#"       welcome to...                      "#);
        println!(r#"                 _   _ _                  "#);
        println!(r#"  _ __ _   _ ___| |_| (_)_ __   __ _ ___  "#);
        println!(r#" | '__| | | / __| __| | | '_ \ / _` / __| "#);
        println!(r#" | |  | |_| \__ \ |_| | | | | | (_| \__ \ "#);
        println!(r#" |_|   \__,_|___/\__|_|_|_| |_|\__, |___/ "#);
        println!(r#"                               |___/      "#);
        println!();
    }

    if !Path::new("info.toml").exists() {
        println!(
            "{} must be run from the rustlings directory",
            std::env::current_exe().unwrap().to_str().unwrap()
        );
        println!("Try `cd rustlings/`!");
        std::process::exit(1);
    }

    let toml_str = &fs::read_to_string("info.toml").unwrap();
    let exercises = toml::from_str::<ExerciseList>(toml_str).unwrap().exercises;

    if let Some(ref matches) = matches.subcommand_matches("run") {
        let filename = matches.value_of("file").unwrap_or_else(|| {
            println!("Please supply a file name!");
            std::process::exit(1);
        });

        let matching_exercise = |e: &&Exercise| {
            Path::new(filename)
                .canonicalize()
                .map(|p| p.ends_with(&e.path))
                .unwrap_or(false)
        };

        let exercise = exercises.iter().find(matching_exercise).unwrap_or_else(|| {
            println!("No exercise found for your file name!");
            std::process::exit(1)
        });

        run(&exercise).unwrap_or_else(|_| std::process::exit(1));
    }

    if matches.subcommand_matches("verify").is_some() {
        verify(&exercises).unwrap_or_else(|_| std::process::exit(1));
    }

    if matches.subcommand_matches("watch").is_some() {
        /* Clears the terminal with an ANSI escape code.
           Works in UNIX and newer Windows terminals. */
        println!("\x1Bc");
        watch(&exercises).unwrap();
    }

    if matches.subcommand_name().is_none() {
        let text = fs::read_to_string("default_out.txt").unwrap();
        println!("{}", text);
    }
}

fn watch(exercises: &[Exercise]) -> notify::Result<()> {
    let (tx, rx) = channel();

    let mut watcher: RecommendedWatcher = Watcher::new(tx, Duration::from_secs(2))?;
    watcher.watch(Path::new("./exercises"), RecursiveMode::Recursive)?;

    let _ignored = verify(exercises.iter());

    loop {
        match rx.recv() {
            Ok(event) => match event {
                DebouncedEvent::Create(b) | DebouncedEvent::Chmod(b) | DebouncedEvent::Write(b) => {
                    if b.extension() == Some(OsStr::new("rs")) && b.exists() {
                        println!("----------**********----------\n");
                        let filepath = b.as_path().canonicalize().unwrap();
                        let exercise = exercises
                            .iter()
                            .skip_while(|e| !filepath.ends_with(&e.path));
                        let _ignored = verify(exercise);
                    }
                }
                _ => {}
            },
            Err(e) => println!("watch error: {:?}", e),
        }
    }
}
<|endoftext|>


<commit_before>#![cfg_attr(feature = "dev", allow(unstable_features))]
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]

mod logger;
mod toml_file;
mod git;
mod changelog;
mod commit_analyzer;
mod cargo;
mod error;
mod github;
mod config;

extern crate rustc_serialize;
extern crate toml;
extern crate regex;
extern crate semver;
extern crate docopt;
extern crate git2;
extern crate clog;
extern crate hyper;
extern crate hubcaps;
extern crate url;
extern crate travis_after_all;
extern crate env_logger;

use docopt::Docopt;
use commit_analyzer::CommitType;
use config::ConfigBuilder;
use std::process;
use semver::Version;
use std::{env,fs};
use std::path::Path;
use std::error::Error;
use url::Url;
use travis_after_all::Build;

const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const USERAGENT: &'static str = concat!("semantic-rs/", env!("CARGO_PKG_VERSION"));
const USAGE: &'static str = "
semantic.rs 🚀

Usage:
  semantic-rs [options]
  semantic-rs --version

Options:
  -h --help              Show this screen.
  --version              Show version.
  -p PATH, --path=PATH   Specifies the repository path. [default: .]
  -w, --write            Run with writing the changes afterwards.
  -r <r>, --release=<r>  Create release on GitHub and publish on crates.io (only in write mode) [default: yes]
  -b <b>, --branch=<b>   The branch on which releases should happen. [default: master]
";

macro_rules! print_exit {
    ($fmt:expr) => {{
        logger::stderr(format!($fmt));
        process::exit(1);
    }};
    ($fmt:expr, $($arg:tt)*) => {{
        logger::stderr(format!($fmt, $($arg)*));
        process::exit(1);
    }};
}

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_path: String,
    flag_write: bool,
    flag_version: bool,
    flag_release: String,
    flag_branch: String,
}

fn string_to_bool(answer: &str) -> bool {
    match &answer.to_lowercase()[..] {
        "yes" | "true" | "1" => true,
        "no" | "false" | "0" => false,
        _ => false
    }
}

fn version_bump(version: &Version, bump: CommitType) -> Option<Version> {
    let mut version = version.clone();
    match bump {
        CommitType::Unknown => return None,
        CommitType::Patch => version.increment_patch(),
        CommitType::Minor => version.increment_minor(),
        CommitType::Major => version.increment_major(),
    }

    Some(version)
}

fn ci_env_set() -> bool {
    env::var("CI").is_ok()
}

fn current_branch(repo: &git2::Repository) -> Option<String> {
    if let Ok(branch) = env::var("TRAVIS_BRANCH") {
        return Some(branch)
    }

    let head = repo.head().expect("No HEAD found for repository");

    if head.is_branch() {
        let short = head.shorthand().expect("No branch name found");
        return Some(short.into());
    }

    None
}

fn is_release_branch(current: &str, release: &str) -> bool {
    if let Ok(pr) = env::var("TRAVIS_PULL_REQUEST") {
        if pr != "false" {
            return false;
        }
    }

    current == release
}

fn user_repo_from_url(url: Url) -> Result<(String, String), String> {
    let path = match url.path() {
        Some(path) => path,
        None => return Err("URL should contain user and repository".into()),
    };

    let user = path[0].clone();
    let repo = match path[1].rfind(".git") {
        None => path[1].clone(),
        Some(suffix_pos) => {
            let valid_pos = path[1].len() - 4;
            if valid_pos == suffix_pos {
                let path = &path[1][0..suffix_pos];
                path.into()
            } else {
                return Err("URL does not point to a git repository".into())
            }
        }
    };

    Ok((user, repo))
}

fn main() {
    env_logger::init().expect("Can't instantiate env logger");

    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    let mut cb = ConfigBuilder::new();

    if args.flag_version {
        println!("semantic.rs 🚀 -- v{}", VERSION);
        process::exit(0);
    }

    let is_dry_run = if ci_env_set() {
        false
    }
    else {
        !args.flag_write
    };

    let release_mode = string_to_bool(&args.flag_release);

    cb.write(args.flag_write);
    cb.release(release_mode);
    cb.branch(args.flag_branch);

    println!("semantic.rs 🚀");

    logger::stdout("Analyzing your repository");
    let path = Path::new(&args.flag_path);
    let path = fs::canonicalize(path)
        .unwrap_or_else(|_| print_exit!("Path does not exist or a component is not a directory"));
    let repository_path = path.to_str()
        .unwrap_or_else(|| print_exit!("Path is not valid unicode"));

    let repo = match git2::Repository::open(repository_path) {
        Ok(repo) => repo,
        Err(e) => {
            logger::stderr(format!("Could not open the git repository: {:?}", e));
            process::exit(1);
        }
    };

    cb.repository_path(repository_path.to_owned());

    // extra scope scope to make sure borrow of `repo` is dropped
    {
        let signature = match git::get_signature(&repo) {
            Ok(sig) => sig,
            Err(e) => {
                logger::stderr(format!("Failed to get the committer's name and email address: {}", e.description()));
                logger::stderr(r"
A release commit needs a committer name and email address.
We tried fetching it from different locations, but couldn't find one.

Committer information is taken from the following environment variables, if set:

GIT_COMMITTER_NAME
GIT_COMMITTER_EMAIL

If none is set the normal git config is tried in the following order:

Local repository config
User config
Global config");
                process::exit(1);
            }
        };

        cb.signature(signature.to_owned());
    }

    // In case we are in write-mode AND release mode,
    // we will make sure we got all configuration settings
    if !is_dry_run && release_mode {
        let remote_url = match repo.find_remote("origin") {
            Err(e) => print_exit!("Could not determine the origin remote url: {:?}", e),
            Ok(remote) => {
                let url = remote.url().expect("Remote URL is not valid UTF-8");
                Url::parse(&url).expect("Remote URL can't be parsed")
            }
        };

        let (user, repo_name) = user_repo_from_url(remote_url)
            .unwrap_or_else(|e| print_exit!("Could not extract user and repository name from URL: {:?}", e));
        cb.user(user);
        cb.repository_name(repo_name);

        let gh_token = env::var("GH_TOKEN")
            .unwrap_or_else(|err| print_exit!("GH_TOKEN not set: {:?}", err));

        let cargo_token = env::var("CARGO_TOKEN")
            .unwrap_or_else(|err| print_exit!("CARGO_TOKEN not set: {:?}", err));

        cb.gh_token(gh_token);
        cb.cargo_token(cargo_token);
    }

    cb.repository(repo);
    let config = cb.build();

    let branch = current_branch(&config.repository)
        .unwrap_or_else(|| print_exit!("Could not determine current branch."));

    if !is_release_branch(&branch, &config.branch) {
        println!("Current branch is '{}', releases are only done from branch '{}'", branch, config.branch);
        println!("No release done from a pull request either.");
        process::exit(0);
    }

    if ci_env_set() {
        let build_run = Build::from_env()
            .unwrap_or_else(|e| print_exit!("CI mode, but can't check other builds. Error: {:?}", e));

        if !build_run.is_leader() {
            println!("Not the build leader. Nothing to do. Bye.");
            process::exit(0);
        }

        println!("I am the build leader. Waiting for other jobs to finish.");
        match build_run.wait_for_others() {
            Ok(()) => println!("Other jobs finished and succeeded. Doing my work now."),
            Err(travis_after_all::Error::FailedBuilds) => {
                print_exit!("Some builds failed. Stopping here.");
            },
            Err(e) => print_exit!("Waiting for other builds failed Reason: {:?}", e),
        }
    }

    let version = toml_file::read_from_file(&config.repository_path)
        .unwrap_or_else(|err| print_exit!("Reading `Cargo.toml` failed: {:?}", err));

    let version = Version::parse(&version).expect("Not a valid version");
    logger::stdout(format!("Current version: {}", version.to_string()));

    logger::stdout("Analyzing commits");

    let bump = git::version_bump_since_latest(&config.repository);
    if is_dry_run {
        logger::stdout(format!("Commits analyzed. Bump would be {:?}", bump));
    }
    else {
        logger::stdout(format!("Commits analyzed. Bump will be {:?}", bump));
    }
    let new_version = match version_bump(&version, bump) {
        Some(new_version) => new_version.to_string(),
        None => {
            logger::stdout("No version bump. Nothing to do.");
            process::exit(0);
        }
    };

    if is_dry_run {
        logger::stdout(format!("New version would be: {}", new_version));
        logger::stdout("Would write the following Changelog:");
        let changelog = match changelog::generate(repository_path, &version.to_string(), &new_version) {
            Ok(log) => log,
            Err(err) => {
                logger::stderr(format!("Generating Changelog failed: {:?}", err));
                process::exit(1);
            }
        };
        logger::stdout("====================================");
        logger::stdout(changelog);
        logger::stdout("====================================");
        logger::stdout("Would create annotated git tag");
    }
    else {
        logger::stdout(format!("New version: {}", new_version));

        toml_file::write_new_version(repository_path, &new_version)
            .unwrap_or_else(|err| print_exit!("Writing `Cargo.toml` failed: {:?}", err));

        logger::stdout(format!("Writing Changelog"));
        changelog::write(repository_path, &version.to_string(), &new_version)
            .unwrap_or_else(|err| print_exit!("Writing Changelog failed: {:?}", err));

        if config.release_mode {
            logger::stdout("Updating lockfile");
            if !cargo::update_lockfile(repository_path) {
                print_exit!("`cargo fetch` failed. See above for the cargo error message.");
            }
        }

        logger::stdout("Package crate");
        if !cargo::package(repository_path) {
            print_exit!("`cargo package` failed. See above for the cargo error message.");
        }

        git::commit_files(&config, &new_version)
            .unwrap_or_else(|err| print_exit!("Committing files failed: {:?}", err));

        logger::stdout("Creating annotated git tag");
        let tag_message = changelog::generate(repository_path, &version.to_string(), &new_version)
            .unwrap_or_else(|err| print_exit!("Can't generate changelog: {:?}", err));

        let tag_name = format!("v{}", new_version);
        git::tag(&config, &tag_name, &tag_message)
            .unwrap_or_else(|err| print_exit!("Failed to create git tag: {:?}", err));

        if config.release_mode {
            logger::stdout("Pushing new commit and tag");
            git::push(&config, &tag_name)
                .unwrap_or_else(|err| print_exit!("Failed to push git: {:?}", err));

            logger::stdout("Creating GitHub release");
            github::release(&config, &tag_name, &tag_message)
                .unwrap_or_else(|err| print_exit!("Failed to create GitHub release: {:?}", err));

            logger::stdout("Publishing crate on crates.io");
            if !cargo::publish(&config.repository_path, &config.cargo_token.as_ref().unwrap()) {
                print_exit!("Failed to publish on crates.io");
            }

            println!("{} v{} is released. 🚀🚀🚀", config.repository_name, new_version);
        }
    }
}
<commit_msg>fix: Wait a short time before creating the release<commit_after>#![cfg_attr(feature = "dev", allow(unstable_features))]
#![cfg_attr(feature = "dev", feature(plugin))]
#![cfg_attr(feature = "dev", plugin(clippy))]

mod logger;
mod toml_file;
mod git;
mod changelog;
mod commit_analyzer;
mod cargo;
mod error;
mod github;
mod config;

extern crate rustc_serialize;
extern crate toml;
extern crate regex;
extern crate semver;
extern crate docopt;
extern crate git2;
extern crate clog;
extern crate hyper;
extern crate hubcaps;
extern crate url;
extern crate travis_after_all;
extern crate env_logger;

use docopt::Docopt;
use commit_analyzer::CommitType;
use config::ConfigBuilder;
use std::process;
use semver::Version;
use std::{env,fs};
use std::path::Path;
use std::error::Error;
use std::thread;
use std::time::Duration;
use url::Url;
use travis_after_all::Build;

const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const USERAGENT: &'static str = concat!("semantic-rs/", env!("CARGO_PKG_VERSION"));
const USAGE: &'static str = "
semantic.rs 🚀

Usage:
  semantic-rs [options]
  semantic-rs --version

Options:
  -h --help              Show this screen.
  --version              Show version.
  -p PATH, --path=PATH   Specifies the repository path. [default: .]
  -w, --write            Run with writing the changes afterwards.
  -r <r>, --release=<r>  Create release on GitHub and publish on crates.io (only in write mode) [default: yes]
  -b <b>, --branch=<b>   The branch on which releases should happen. [default: master]
";

macro_rules! print_exit {
    ($fmt:expr) => {{
        logger::stderr(format!($fmt));
        process::exit(1);
    }};
    ($fmt:expr, $($arg:tt)*) => {{
        logger::stderr(format!($fmt, $($arg)*));
        process::exit(1);
    }};
}

#[derive(Debug, RustcDecodable)]
struct Args {
    flag_path: String,
    flag_write: bool,
    flag_version: bool,
    flag_release: String,
    flag_branch: String,
}

fn string_to_bool(answer: &str) -> bool {
    match &answer.to_lowercase()[..] {
        "yes" | "true" | "1" => true,
        "no" | "false" | "0" => false,
        _ => false
    }
}

fn version_bump(version: &Version, bump: CommitType) -> Option<Version> {
    let mut version = version.clone();
    match bump {
        CommitType::Unknown => return None,
        CommitType::Patch => version.increment_patch(),
        CommitType::Minor => version.increment_minor(),
        CommitType::Major => version.increment_major(),
    }

    Some(version)
}

fn ci_env_set() -> bool {
    env::var("CI").is_ok()
}

fn current_branch(repo: &git2::Repository) -> Option<String> {
    if let Ok(branch) = env::var("TRAVIS_BRANCH") {
        return Some(branch)
    }

    let head = repo.head().expect("No HEAD found for repository");

    if head.is_branch() {
        let short = head.shorthand().expect("No branch name found");
        return Some(short.into());
    }

    None
}

fn is_release_branch(current: &str, release: &str) -> bool {
    if let Ok(pr) = env::var("TRAVIS_PULL_REQUEST") {
        if pr != "false" {
            return false;
        }
    }

    current == release
}

fn user_repo_from_url(url: Url) -> Result<(String, String), String> {
    let path = match url.path() {
        Some(path) => path,
        None => return Err("URL should contain user and repository".into()),
    };

    let user = path[0].clone();
    let repo = match path[1].rfind(".git") {
        None => path[1].clone(),
        Some(suffix_pos) => {
            let valid_pos = path[1].len() - 4;
            if valid_pos == suffix_pos {
                let path = &path[1][0..suffix_pos];
                path.into()
            } else {
                return Err("URL does not point to a git repository".into())
            }
        }
    };

    Ok((user, repo))
}

fn main() {
    env_logger::init().expect("Can't instantiate env logger");

    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());

    let mut cb = ConfigBuilder::new();

    if args.flag_version {
        println!("semantic.rs 🚀 -- v{}", VERSION);
        process::exit(0);
    }

    let is_dry_run = if ci_env_set() {
        false
    }
    else {
        !args.flag_write
    };

    let release_mode = string_to_bool(&args.flag_release);

    cb.write(args.flag_write);
    cb.release(release_mode);
    cb.branch(args.flag_branch);

    println!("semantic.rs 🚀");

    logger::stdout("Analyzing your repository");
    let path = Path::new(&args.flag_path);
    let path = fs::canonicalize(path)
        .unwrap_or_else(|_| print_exit!("Path does not exist or a component is not a directory"));
    let repository_path = path.to_str()
        .unwrap_or_else(|| print_exit!("Path is not valid unicode"));

    let repo = match git2::Repository::open(repository_path) {
        Ok(repo) => repo,
        Err(e) => {
            logger::stderr(format!("Could not open the git repository: {:?}", e));
            process::exit(1);
        }
    };

    cb.repository_path(repository_path.to_owned());

    // extra scope scope to make sure borrow of `repo` is dropped
    {
        let signature = match git::get_signature(&repo) {
            Ok(sig) => sig,
            Err(e) => {
                logger::stderr(format!("Failed to get the committer's name and email address: {}", e.description()));
                logger::stderr(r"
A release commit needs a committer name and email address.
We tried fetching it from different locations, but couldn't find one.

Committer information is taken from the following environment variables, if set:

GIT_COMMITTER_NAME
GIT_COMMITTER_EMAIL

If none is set the normal git config is tried in the following order:

Local repository config
User config
Global config");
                process::exit(1);
            }
        };

        cb.signature(signature.to_owned());
    }

    // In case we are in write-mode AND release mode,
    // we will make sure we got all configuration settings
    if !is_dry_run && release_mode {
        let remote_url = match repo.find_remote("origin") {
            Err(e) => print_exit!("Could not determine the origin remote url: {:?}", e),
            Ok(remote) => {
                let url = remote.url().expect("Remote URL is not valid UTF-8");
                Url::parse(&url).expect("Remote URL can't be parsed")
            }
        };

        let (user, repo_name) = user_repo_from_url(remote_url)
            .unwrap_or_else(|e| print_exit!("Could not extract user and repository name from URL: {:?}", e));
        cb.user(user);
        cb.repository_name(repo_name);

        let gh_token = env::var("GH_TOKEN")
            .unwrap_or_else(|err| print_exit!("GH_TOKEN not set: {:?}", err));

        let cargo_token = env::var("CARGO_TOKEN")
            .unwrap_or_else(|err| print_exit!("CARGO_TOKEN not set: {:?}", err));

        cb.gh_token(gh_token);
        cb.cargo_token(cargo_token);
    }

    cb.repository(repo);
    let config = cb.build();

    let branch = current_branch(&config.repository)
        .unwrap_or_else(|| print_exit!("Could not determine current branch."));

    if !is_release_branch(&branch, &config.branch) {
        println!("Current branch is '{}', releases are only done from branch '{}'", branch, config.branch);
        println!("No release done from a pull request either.");
        process::exit(0);
    }

    if ci_env_set() {
        let build_run = Build::from_env()
            .unwrap_or_else(|e| print_exit!("CI mode, but can't check other builds. Error: {:?}", e));

        if !build_run.is_leader() {
            println!("Not the build leader. Nothing to do. Bye.");
            process::exit(0);
        }

        println!("I am the build leader. Waiting for other jobs to finish.");
        match build_run.wait_for_others() {
            Ok(()) => println!("Other jobs finished and succeeded. Doing my work now."),
            Err(travis_after_all::Error::FailedBuilds) => {
                print_exit!("Some builds failed. Stopping here.");
            },
            Err(e) => print_exit!("Waiting for other builds failed Reason: {:?}", e),
        }
    }

    let version = toml_file::read_from_file(&config.repository_path)
        .unwrap_or_else(|err| print_exit!("Reading `Cargo.toml` failed: {:?}", err));

    let version = Version::parse(&version).expect("Not a valid version");
    logger::stdout(format!("Current version: {}", version.to_string()));

    logger::stdout("Analyzing commits");

    let bump = git::version_bump_since_latest(&config.repository);
    if is_dry_run {
        logger::stdout(format!("Commits analyzed. Bump would be {:?}", bump));
    }
    else {
        logger::stdout(format!("Commits analyzed. Bump will be {:?}", bump));
    }
    let new_version = match version_bump(&version, bump) {
        Some(new_version) => new_version.to_string(),
        None => {
            logger::stdout("No version bump. Nothing to do.");
            process::exit(0);
        }
    };

    if is_dry_run {
        logger::stdout(format!("New version would be: {}", new_version));
        logger::stdout("Would write the following Changelog:");
        let changelog = match changelog::generate(repository_path, &version.to_string(), &new_version) {
            Ok(log) => log,
            Err(err) => {
                logger::stderr(format!("Generating Changelog failed: {:?}", err));
                process::exit(1);
            }
        };
        logger::stdout("====================================");
        logger::stdout(changelog);
        logger::stdout("====================================");
        logger::stdout("Would create annotated git tag");
    }
    else {
        logger::stdout(format!("New version: {}", new_version));

        toml_file::write_new_version(repository_path, &new_version)
            .unwrap_or_else(|err| print_exit!("Writing `Cargo.toml` failed: {:?}", err));

        logger::stdout(format!("Writing Changelog"));
        changelog::write(repository_path, &version.to_string(), &new_version)
            .unwrap_or_else(|err| print_exit!("Writing Changelog failed: {:?}", err));

        if config.release_mode {
            logger::stdout("Updating lockfile");
            if !cargo::update_lockfile(repository_path) {
                print_exit!("`cargo fetch` failed. See above for the cargo error message.");
            }
        }

        logger::stdout("Package crate");
        if !cargo::package(repository_path) {
            print_exit!("`cargo package` failed. See above for the cargo error message.");
        }

        git::commit_files(&config, &new_version)
            .unwrap_or_else(|err| print_exit!("Committing files failed: {:?}", err));

        logger::stdout("Creating annotated git tag");
        let tag_message = changelog::generate(repository_path, &version.to_string(), &new_version)
            .unwrap_or_else(|err| print_exit!("Can't generate changelog: {:?}", err));

        let tag_name = format!("v{}", new_version);
        git::tag(&config, &tag_name, &tag_message)
            .unwrap_or_else(|err| print_exit!("Failed to create git tag: {:?}", err));

        if config.release_mode {
            logger::stdout("Pushing new commit and tag");
            git::push(&config, &tag_name)
                .unwrap_or_else(|err| print_exit!("Failed to push git: {:?}", err));

            logger::stdout("Waiting a tiny bit, so GitHub can store the git tag");
            thread::sleep(Duration::from_secs(1));

            logger::stdout("Creating GitHub release");
            github::release(&config, &tag_name, &tag_message)
                .unwrap_or_else(|err| print_exit!("Failed to create GitHub release: {:?}", err));

            logger::stdout("Publishing crate on crates.io");
            if !cargo::publish(&config.repository_path, &config.cargo_token.as_ref().unwrap()) {
                print_exit!("Failed to publish on crates.io");
            }

            println!("{} v{} is released. 🚀🚀🚀", config.repository_name, new_version);
        }
    }
}
<|endoftext|>


<commit_before>mod config;
mod filter;
mod ftp;
mod macros;
mod render;
mod sha1dir;
mod sync;
mod template;

use crate::config::Config;
use crate::sync::Synchronizer;
use anyhow::Result;
use clap::{Parser, Subcommand};
use config::LogKind;
use flexi_logger::{FileSpec, Logger, LoggerHandle, WriteMode};
use std::fs;
use std::path::{Path, PathBuf};

fn sync(path: &Path, conf: &Config, scratch: bool, overwrite: bool) -> Result<()> {
    if scratch {
        render::build(path, conf, true)?
    }
    let mut synchronizer = Synchronizer::new(conf)?;
    if overwrite {
        synchronizer.push_all_files()
    } else {
        synchronizer.execute()
    }
}

#[derive(Parser)]
struct Build {
    /// Generate output from scratch?
    #[clap(short, long)]
    clean: bool,
}

#[derive(Parser)]
struct Sync {
    /// Overwrites all remote files?
    #[clap(short, long)]
    overwrite: bool,
    /// Do scratch build before sync?
    #[clap(short, long)]
    scratch: bool,
}

#[derive(Subcommand)]
enum Command {
    /// Print the configuration of the config.toml file
    #[clap(alias = "print-config")]
    PrintConfig,
    /// Generate the website
    Build(Build),
    /// Synchronize the website with an sftp or ftp server
    Sync(Sync),
}

#[derive(Parser)]
#[clap(about = "Simple static website generator")]
#[clap(author, version)]
struct Arguments {
    /// Specify the path to the project. By default current directory is used.
    #[clap(short, long, value_name = "FILE")]
    project_path: Option<PathBuf>,

    #[clap(subcommand)]
    command: Command,
}

fn main() -> Result<()> {
    let arguments = Arguments::parse();

    let path = if let Some(path) = arguments.project_path {
        fs::canonicalize(path).expect("could not determine path")
    } else {
        std::env::current_dir()
            .unwrap_or_else(|_| fs::canonicalize(".").expect("could not determine path"))
    };

    // load configuration from config.toml if present
    let conf = Config::load(path.as_path())?;

    let _logger_handle: Option<LoggerHandle> = if let Some(log_kind) = conf.logging {
        match log_kind {
            LogKind::Stdout => Some(Logger::try_with_str("warn, neptungen=info")?.start()?),
            LogKind::File => Some(
                Logger::try_with_str("warn, neptungen=info")?
                    .log_to_file(FileSpec::default().directory(path.join(".logs")))
                    .write_mode(WriteMode::BufferAndFlush)
                    .start()?,
            ),
        }
    } else {
        None
    };

    match arguments.command {
        Command::PrintConfig => {
            conf.print();
        }
        Command::Build(build_args) => render::build(path.as_path(), &conf, build_args.clean)?,
        Command::Sync(sync_args) => sync(
            path.as_path(),
            &conf,
            sync_args.scratch,
            sync_args.overwrite,
        )?,
    };

    Ok(())
}

mod test {
    #[test]
    fn verify_app() {
        use clap::IntoApp;
        super::Arguments::into_app().debug_assert()
    }
}
<commit_msg>fix unitttests<commit_after>mod config;
mod filter;
mod ftp;
mod macros;
mod render;
mod sha1dir;
mod sync;
mod template;

use crate::config::Config;
use crate::sync::Synchronizer;
use anyhow::Result;
use clap::{Parser, Subcommand};
use config::LogKind;
use flexi_logger::{FileSpec, Logger, LoggerHandle, WriteMode};
use std::fs;
use std::path::{Path, PathBuf};

fn sync(path: &Path, conf: &Config, scratch: bool, overwrite: bool) -> Result<()> {
    if scratch {
        render::build(path, conf, true)?
    }
    let mut synchronizer = Synchronizer::new(conf)?;
    if overwrite {
        synchronizer.push_all_files()
    } else {
        synchronizer.execute()
    }
}

#[derive(Parser)]
struct Build {
    /// Generate output from scratch?
    #[clap(short, long)]
    clean: bool,
}

#[derive(Parser)]
struct Sync {
    /// Overwrites all remote files?
    #[clap(short, long)]
    overwrite: bool,
    /// Do scratch build before sync?
    #[clap(short, long)]
    scratch: bool,
}

#[derive(Subcommand)]
enum Command {
    /// Print the configuration of the config.toml file
    PrintConfig,
    /// Generate the website
    Build(Build),
    /// Synchronize the website with an sftp or ftp server
    Sync(Sync),
}

#[derive(Parser)]
#[clap(about = "Simple static website generator")]
#[clap(author, version)]
struct Arguments {
    /// Specify the path to the project. By default current directory is used.
    #[clap(short, long, value_name = "FILE")]
    project_path: Option<PathBuf>,

    #[clap(subcommand)]
    command: Command,
}

fn main() -> Result<()> {
    let arguments = Arguments::parse();

    let path = if let Some(path) = arguments.project_path {
        fs::canonicalize(path).expect("could not determine path")
    } else {
        std::env::current_dir()
            .unwrap_or_else(|_| fs::canonicalize(".").expect("could not determine path"))
    };

    // load configuration from config.toml if present
    let conf = Config::load(path.as_path())?;

    let _logger_handle: Option<LoggerHandle> = if let Some(log_kind) = conf.logging {
        match log_kind {
            LogKind::Stdout => Some(Logger::try_with_str("warn, neptungen=info")?.start()?),
            LogKind::File => Some(
                Logger::try_with_str("warn, neptungen=info")?
                    .log_to_file(FileSpec::default().directory(path.join(".logs")))
                    .write_mode(WriteMode::BufferAndFlush)
                    .start()?,
            ),
        }
    } else {
        None
    };

    match arguments.command {
        Command::PrintConfig => {
            conf.print();
        }
        Command::Build(build_args) => render::build(path.as_path(), &conf, build_args.clean)?,
        Command::Sync(sync_args) => sync(
            path.as_path(),
            &conf,
            sync_args.scratch,
            sync_args.overwrite,
        )?,
    };

    Ok(())
}

mod test {
    #[test]
    fn verify_app() {
        use clap::CommandFactory;
        super::Arguments::command().debug_assert()
    }
}
<|endoftext|>


<commit_before>
extern crate clap;
extern crate rand;
extern crate serde;
#[macro_use]
extern crate serde_derive;

use clap::{App, Arg, SubCommand};

mod types;
mod log;
mod descriptor;
mod command;
mod installer;
mod runner;

fn main() {
    let name = "make";
    let version = env!("CARGO_PKG_VERSION");
    let author = env!("CARGO_PKG_AUTHORS");
    let description = env!("CARGO_PKG_DESCRIPTION");

    let default_toml = "Makefile.toml";

    let mut build_file_arg = "-b, --buildFile=[FILE] 'Build toml file containing the build descriptor (default: ".to_string();
    build_file_arg.push_str(&default_toml);
    build_file_arg.push_str(" if exists)'");

    let matches = App::new("cargo")
        .subcommand(
            SubCommand::with_name(name)
                .version(version)
                .author(author)
                .about(description)
                .arg_from_usage(&build_file_arg)
                .arg_from_usage("-t, --task=[TASK NAME] 'The task name to execute (default: default)'")
                .arg(Arg::from_usage("-l, --loglevel=[LOG LEVEL] 'The log level (default: info)'").possible_values(&["verbose", "info", "error"]))
        )
        .get_matches();

    let (build_file, task, log_level) = if let Some(cmd_matches) = matches.subcommand_matches(name) {
        (cmd_matches.value_of("buildFile").unwrap_or(&default_toml), cmd_matches.value_of("task").unwrap_or("default"), cmd_matches.value_of("loglevel").unwrap_or("info"))
    } else {
        panic!("cargo-{} not invoked via cargo.", name);
    };

    let logger = log::create(log_level);

    logger.info::<()>("Using Build File: ", &[build_file], None);
    logger.info::<()>("Task: ", &[task], None);

    let config = descriptor::load(&build_file, &logger);

    runner::run(&logger, &config, &task);
}
<commit_msg>enable tests in ci<commit_after>
extern crate clap;
extern crate rand;
extern crate serde;
#[macro_use]
extern crate serde_derive;

use clap::{App, Arg, SubCommand};

mod types;
mod log;
mod descriptor;
mod command;
mod installer;
mod runner;

fn main() {
    let name = "make";
    let version = env!("CARGO_PKG_VERSION");
    let author = env!("CARGO_PKG_AUTHORS");
    let description = env!("CARGO_PKG_DESCRIPTION");

    let default_toml = "Makefile.toml";

    let mut build_file_arg = "-b, --buildFile=[FILE] 'Build toml file containing the build descriptor (default: ".to_string();
    build_file_arg.push_str(&default_toml);
    build_file_arg.push_str(" if exists)'");

    let matches = App::new("cargo")
        .subcommand(
            SubCommand::with_name(name)
                .version(version)
                .author(author)
                .about(description)
                .arg_from_usage(&build_file_arg)
                .arg_from_usage("-t, --task=[TASK NAME] 'The task name to execute (default: default)'")
                .arg(Arg::from_usage("-l, --loglevel=[LOG LEVEL] 'The log level (default: info)'").possible_values(&["verbose", "info", "error"]))
        )
        .get_matches();

    let (build_file, task, log_level) = if let Some(cmd_matches) = (cmd_matches.value_of("buildFile").unwrap_or(&default_toml), cmd_matches.value_of("task").unwrap_or("default"), cmd_matches.value_of("loglevel").unwrap_or("info"));

    let logger = log::create(log_level);

    logger.info::<()>("Using Build File: ", &[build_file], None);
    logger.info::<()>("Task: ", &[task], None);

    let config = descriptor::load(&build_file, &logger);

    runner::run(&logger, &config, &task);
}
<|endoftext|>


<commit_before>#![feature(core)]
#![feature(path)]

#![feature(plugin)]
#![plugin(regex_macros)]
extern crate regex;
extern crate git2;
extern crate "rustc-serialize" as rustc_serialize;
extern crate docopt;

use git2::{Repository, Branch, BranchType, DiffLine,
    Commit, Diff, DiffFormat, DiffDelta, DiffHunk, Error, Oid};
use docopt::Docopt;
use std::old_io as io;
use std::old_io::BufferedReader;
use std::old_io::File;
use std::str;
use regex::Regex;

// Write the Docopt usage string.
static USAGE: &'static str = "
Usage: transit <repo> [<old> <new>]

If no commits are given, transit will revwalk from latest to oldest.
";

#[derive(RustcDecodable, Debug)]
struct Args {
    arg_repo: String,
    arg_old: Option<String>,
    arg_new: Option<String>,
}

fn main() {
    // Parse the args above or die.
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());
    // Pull up the revwalk.
    let path = Path::new(args.arg_repo);
    let repo = Repository::discover(&path)
        .ok().expect("Unable to find repo.");
    // If we can destructures the two optional args into real things.
    if let (Some(old_string), Some(new_string)) = (args.arg_old, args.arg_new) {
        // Compare a specific commit pair.
        // Fist, get the commits. (Error checked)
        let old = Oid::from_str(&old_string[]).and_then(|oid| repo.find_commit(oid));
        let new = Oid::from_str(&new_string[]).and_then(|oid| repo.find_commit(oid));
        if old.is_ok() && new.is_ok() {
            let output = find_moves(&repo, &old.unwrap(), &new.unwrap()).unwrap();
            make_output(output);
        } else {
            panic!("Commit ids were not valid.");
        }
    } else {
        // Revwalk.
        let mut revwalk = repo.revwalk()
            .ok().expect("Unable to get revwalk.");
        // Setup some options.
        revwalk.simplify_first_parent(); // TODO: Maybe remove?
        let mut flags = git2::Sort::empty();
        flags.insert(git2::SORT_TIME);
        flags.insert(git2::SORT_TOPOLOGICAL);
        revwalk.set_sorting(flags);
        // Push HEAD to the revwalk.
        revwalk.push_head()
            .ok().expect("Unable to push HEAD.");
        // We sadly must collect here to use `.windows()`
        let history = revwalk.filter_map(|id| repo.find_commit(id).ok())
            .collect::<Vec<Commit>>();
        // Walk through each pair of commits.
        for pair in history.windows(2) {let (old, new) = (&pair[1], &pair[0]);
			let output = find_moves(&repo, old.clone(), new.clone()).unwrap();
			// mangle the vectors here to make pretty output
			make_output(output);
		}
	}
}

fn make_output(output: Vec<Output>) {
	println!("make_output:");
    println!("\toutput.len()={}", output.len());
    for i in range(0, output.len()) {
            println!("\told_commit={}",         output[i].old_commit);
            println!("\tnew_commit={}",         output[i].new_commit);
            println!("\torigin_line={}",        output[i].origin_line);
            println!("\tdestintation_line={}",  output[i].destination_line);
            println!("\tnum_lines={}",          output[i].num_lines);
    }
}

#[derive(Debug)]
enum State {
    Other, Addition, Deletion
}

#[derive(Debug)]
enum FoundState {
    Added, Deleted
}

fn dump_diffline(line: &DiffLine) {
    // 'origin' is wrapped in pipes to ease displaying space characters.
    print!("line: old={:?} new={:?} offset={} |origin|=|{}|\n      content={}",
             line.old_lineno(), line.new_lineno(), line.content_offset(),
             line.origin(), str::from_utf8(line.content()).unwrap());
}

fn dump_diffdelta(delta: &DiffDelta) {
    println!("delta: nfiles={} status={:?} old_file=(id={} path_bytes={:?} path={:?} tsize={}) new_file=(id={} path_bytes={:?} path={:?} tsize={})",
            delta.nfiles(), delta.status(),
            delta.old_file().id(), delta.old_file().path_bytes(), delta.old_file().path(), delta.old_file().size(),
            delta.new_file().id(), delta.new_file().path_bytes(), delta.new_file().path(), delta.new_file().size());
}

fn format_key(key: String) -> String {
    let removeWhiteSpace = regex!(r"\s{2,}"); // 2 or more whitespaces    // TODO Removes whitespace from a string.
    removeWhiteSpace.replace_all(key.as_slice(), "")
}

#[derive(Debug)]
struct Found {
    filename: Path,
    key: String,
    state: FoundState,
}

fn find_moves(repo: &Repository, old: &Commit, new: &Commit) -> Result<Vec<Output>, Error> {

    println!("\nFIND MOVES---------------------");

	let old_tree = try!(old.tree());
	let new_tree = try!(new.tree());
	// Build up a diff of the two trees.
	let diff = try!(Diff::tree_to_tree(repo, Some(&old_tree), Some(&new_tree), None));

    let mut state = State::Other;
    let mut added = String::new();
    let mut deleted = String::new();
    let mut old_path: Path = Path::new("");
    let mut new_path: Path = Path::new("");

    let mut founds: Vec<Found> = Vec::new();

	// Read about this function in http://alexcrichton.com/git2-rs/git2/struct.Diff.html#method.print
	// It's a bit weird, but I think it will provide the necessary information.
	diff.print(DiffFormat::Patch, |delta, maybe_hunk, line| -> bool {

        assert!(delta.nfiles() == 2, "This only works on diffs between exactly 2 files.");

		// Thinking:
		//  * If is not a hunk, keep going.
		//  * If it's a hunk, do regex magic.
		//  * Stick regex output into a hashmap as a hash.
		//  * Later, we will iterate through and output pased on the values.
		// Filter out all the boring context lines.
		// If we're not interested in this line just return since it will iterate to the next.
		if maybe_hunk.is_none() { return true }; // Return early.

        println!("top of loop: founds={:?}", founds);
        println!("top of loop: added={:?} deleted={:?}", added, deleted);

        dump_diffline(&line);
        //dump_diffdelta(&delta);

        old_path = delta.old_file().path().unwrap().clone();
        new_path = delta.new_file().path().unwrap().clone();

		match line.origin() {
			// Additions
			'+' | '>' => {
                println!("In additions. state={:?}", state);

                added.push_str(str::from_utf8(line.content()).unwrap());

                match state {
                    State::Deletion => {
                        founds.push(Found {
                            filename: old_path.clone(),
                            key: format_key(deleted.clone()),
                            state: FoundState::Deleted,
                        });
                        deleted = String::new();
                    },
                    _ => (),
                }

                state = State::Addition;
				true
			},
			// Deletions
			'-' | '<' => {
                println!("In deletion. state={:?}", state);

                deleted.push_str(str::from_utf8(line.content()).unwrap());

                match state {
                    State::Addition => {
                        founds.push(Found {
                            filename: new_path.clone(),
                            key: format_key(added.clone()),
                            state: FoundState::Added,
                        });
                        added = String::new();
                    },
                    _ => (),
                }

                state = State::Deletion;
				true
			},
			// Other
			_         => {
                println!("in _. state={:?}", state);

                match state {
                    State::Addition => {
                        founds.push(Found {
                            filename: new_path.clone(),
                            key: format_key(added.clone()),
                            state: FoundState::Added,
                        });
                        added = String::new();
                    },
                    State::Deletion => {
                        founds.push(Found {
                            filename: old_path.clone(),
                            key: format_key(deleted.clone()),
                            state: FoundState::Deleted,
                        });
                        deleted = String::new();
                    },
                    _ => (),
                }

                state = State::Other;
                true
            }
		}
	});

    // Grab one.
    match state {
        State::Addition => {
            if added.len() > 0 {
                founds.push(Found {
                    filename: new_path,
                    key: format_key(added.clone()),
                    state: FoundState::Added,
                });
            }
        },
        State::Deletion => {
            if deleted.len() > 0 {
                founds.push(Found {
                    filename: old_path,
                    key: format_key(deleted.clone()),
                    state: FoundState::Deleted,
                });
            }
        },
        _ => (),
    }

    println!("FOUNDS={:?}", founds);

    Ok(vec![Output {
	    old_commit: old.id(),
	    new_commit: new.id(),
        old_filename: String::from_str("oldfilename"),
        new_filename: String::from_str("newfilename"),
	    origin_line: 0,
	    destination_line: 0,
	    num_lines: 0
    }])
}

#[derive(Debug)]
struct Output {
    old_commit: Oid,
    new_commit: Oid,
    old_filename: String,
    new_filename: String,
    origin_line: u32,
    destination_line: u32,
    num_lines: u32,
}
<commit_msg>Use snakecase for variable name.<commit_after>#![feature(core)]
#![feature(path)]

#![feature(plugin)]
#![plugin(regex_macros)]
extern crate regex;
extern crate git2;
extern crate "rustc-serialize" as rustc_serialize;
extern crate docopt;

use git2::{Repository, Branch, BranchType, DiffLine,
    Commit, Diff, DiffFormat, DiffDelta, DiffHunk, Error, Oid};
use docopt::Docopt;
use std::old_io as io;
use std::old_io::BufferedReader;
use std::old_io::File;
use std::str;
use regex::Regex;

// Write the Docopt usage string.
static USAGE: &'static str = "
Usage: transit <repo> [<old> <new>]

If no commits are given, transit will revwalk from latest to oldest.
";

#[derive(RustcDecodable, Debug)]
struct Args {
    arg_repo: String,
    arg_old: Option<String>,
    arg_new: Option<String>,
}

fn main() {
    // Parse the args above or die.
    let args: Args = Docopt::new(USAGE)
        .and_then(|d| d.decode())
        .unwrap_or_else(|e| e.exit());
    // Pull up the revwalk.
    let path = Path::new(args.arg_repo);
    let repo = Repository::discover(&path)
        .ok().expect("Unable to find repo.");
    // If we can destructures the two optional args into real things.
    if let (Some(old_string), Some(new_string)) = (args.arg_old, args.arg_new) {
        // Compare a specific commit pair.
        // Fist, get the commits. (Error checked)
        let old = Oid::from_str(&old_string[]).and_then(|oid| repo.find_commit(oid));
        let new = Oid::from_str(&new_string[]).and_then(|oid| repo.find_commit(oid));
        if old.is_ok() && new.is_ok() {
            let output = find_moves(&repo, &old.unwrap(), &new.unwrap()).unwrap();
            make_output(output);
        } else {
            panic!("Commit ids were not valid.");
        }
    } else {
        // Revwalk.
        let mut revwalk = repo.revwalk()
            .ok().expect("Unable to get revwalk.");
        // Setup some options.
        revwalk.simplify_first_parent(); // TODO: Maybe remove?
        let mut flags = git2::Sort::empty();
        flags.insert(git2::SORT_TIME);
        flags.insert(git2::SORT_TOPOLOGICAL);
        revwalk.set_sorting(flags);
        // Push HEAD to the revwalk.
        revwalk.push_head()
            .ok().expect("Unable to push HEAD.");
        // We sadly must collect here to use `.windows()`
        let history = revwalk.filter_map(|id| repo.find_commit(id).ok())
            .collect::<Vec<Commit>>();
        // Walk through each pair of commits.
        for pair in history.windows(2) {let (old, new) = (&pair[1], &pair[0]);
			let output = find_moves(&repo, old.clone(), new.clone()).unwrap();
			// mangle the vectors here to make pretty output
			make_output(output);
		}
	}
}

fn make_output(output: Vec<Output>) {
	println!("make_output:");
    println!("\toutput.len()={}", output.len());
    for i in range(0, output.len()) {
            println!("\told_commit={}",         output[i].old_commit);
            println!("\tnew_commit={}",         output[i].new_commit);
            println!("\torigin_line={}",        output[i].origin_line);
            println!("\tdestintation_line={}",  output[i].destination_line);
            println!("\tnum_lines={}",          output[i].num_lines);
    }
}

#[derive(Debug)]
enum State {
    Other, Addition, Deletion
}

#[derive(Debug)]
enum FoundState {
    Added, Deleted
}

fn dump_diffline(line: &DiffLine) {
    // 'origin' is wrapped in pipes to ease displaying space characters.
    print!("line: old={:?} new={:?} offset={} |origin|=|{}|\n      content={}",
             line.old_lineno(), line.new_lineno(), line.content_offset(),
             line.origin(), str::from_utf8(line.content()).unwrap());
}

fn dump_diffdelta(delta: &DiffDelta) {
    println!("delta: nfiles={} status={:?} old_file=(id={} path_bytes={:?} path={:?} tsize={}) new_file=(id={} path_bytes={:?} path={:?} tsize={})",
            delta.nfiles(), delta.status(),
            delta.old_file().id(), delta.old_file().path_bytes(), delta.old_file().path(), delta.old_file().size(),
            delta.new_file().id(), delta.new_file().path_bytes(), delta.new_file().path(), delta.new_file().size());
}

fn format_key(key: String) -> String {
    let remove_whitespace = regex!(r"\s{2,}"); // 2 or more whitespaces    // TODO Removes whitespace from a string.
    remove_whitespace.replace_all(key.as_slice(), "")
}

#[derive(Debug)]
struct Found {
    filename: Path,
    key: String,
    state: FoundState,
}

fn find_moves(repo: &Repository, old: &Commit, new: &Commit) -> Result<Vec<Output>, Error> {

    println!("\nFIND MOVES---------------------");

	let old_tree = try!(old.tree());
	let new_tree = try!(new.tree());
	// Build up a diff of the two trees.
	let diff = try!(Diff::tree_to_tree(repo, Some(&old_tree), Some(&new_tree), None));

    let mut state = State::Other;
    let mut added = String::new();
    let mut deleted = String::new();
    let mut old_path: Path = Path::new("");
    let mut new_path: Path = Path::new("");

    let mut founds: Vec<Found> = Vec::new();

	// Read about this function in http://alexcrichton.com/git2-rs/git2/struct.Diff.html#method.print
	// It's a bit weird, but I think it will provide the necessary information.
	diff.print(DiffFormat::Patch, |delta, maybe_hunk, line| -> bool {

        assert!(delta.nfiles() == 2, "This only works on diffs between exactly 2 files.");

		// Thinking:
		//  * If is not a hunk, keep going.
		//  * If it's a hunk, do regex magic.
		//  * Stick regex output into a hashmap as a hash.
		//  * Later, we will iterate through and output pased on the values.
		// Filter out all the boring context lines.
		// If we're not interested in this line just return since it will iterate to the next.
		if maybe_hunk.is_none() { return true }; // Return early.

        println!("top of loop: founds={:?}", founds);
        println!("top of loop: added={:?} deleted={:?}", added, deleted);

        dump_diffline(&line);
        //dump_diffdelta(&delta);

        old_path = delta.old_file().path().unwrap().clone();
        new_path = delta.new_file().path().unwrap().clone();

		match line.origin() {
			// Additions
			'+' | '>' => {
                println!("In additions. state={:?}", state);

                added.push_str(str::from_utf8(line.content()).unwrap());

                match state {
                    State::Deletion => {
                        founds.push(Found {
                            filename: old_path.clone(),
                            key: format_key(deleted.clone()),
                            state: FoundState::Deleted,
                        });
                        deleted = String::new();
                    },
                    _ => (),
                }

                state = State::Addition;
				true
			},
			// Deletions
			'-' | '<' => {
                println!("In deletion. state={:?}", state);

                deleted.push_str(str::from_utf8(line.content()).unwrap());

                match state {
                    State::Addition => {
                        founds.push(Found {
                            filename: new_path.clone(),
                            key: format_key(added.clone()),
                            state: FoundState::Added,
                        });
                        added = String::new();
                    },
                    _ => (),
                }

                state = State::Deletion;
				true
			},
			// Other
			_         => {
                println!("in _. state={:?}", state);

                match state {
                    State::Addition => {
                        founds.push(Found {
                            filename: new_path.clone(),
                            key: format_key(added.clone()),
                            state: FoundState::Added,
                        });
                        added = String::new();
                    },
                    State::Deletion => {
                        founds.push(Found {
                            filename: old_path.clone(),
                            key: format_key(deleted.clone()),
                            state: FoundState::Deleted,
                        });
                        deleted = String::new();
                    },
                    _ => (),
                }

                state = State::Other;
                true
            }
		}
	});

    // Grab one.
    match state {
        State::Addition => {
            if added.len() > 0 {
                founds.push(Found {
                    filename: new_path,
                    key: format_key(added.clone()),
                    state: FoundState::Added,
                });
            }
        },
        State::Deletion => {
            if deleted.len() > 0 {
                founds.push(Found {
                    filename: old_path,
                    key: format_key(deleted.clone()),
                    state: FoundState::Deleted,
                });
            }
        },
        _ => (),
    }

    println!("FOUNDS={:?}", founds);

    Ok(vec![Output {
	    old_commit: old.id(),
	    new_commit: new.id(),
        old_filename: String::from_str("oldfilename"),
        new_filename: String::from_str("newfilename"),
	    origin_line: 0,
	    destination_line: 0,
	    num_lines: 0
    }])
}

#[derive(Debug)]
struct Output {
    old_commit: Oid,
    new_commit: Oid,
    old_filename: String,
    new_filename: String,
    origin_line: u32,
    destination_line: u32,
    num_lines: u32,
}
<|endoftext|>


<commit_before>#![feature(convert)]
#![feature(core)]
#![feature(split_off)]
#![feature(iter_arith)]

extern crate getopts;
extern crate core;

use getopts::Options;
use std::env;
use std::thread::{JoinHandle, Thread};
use std::thread;
use std::sync::{Arc, Mutex};
use std::collections::{HashMap, BinaryHeap};
use std::sync::mpsc::{channel, Receiver, Sender};

extern crate bounded_spsc_queue;
use bounded_spsc_queue::{Producer, Consumer};

extern crate kissfft;
use kissfft::KissFFT;
use kissfft::binding::kiss_fft_cpx;

extern crate sound_stream;
//use sound_stream::{CallbackFlags, CallbackResult, SoundStream, Settings, StreamParams};
use sound_stream::*;
extern crate osc;
use osc::osc_sender::*;
use osc::osc_data::*;
use osc::osc_data::OscPacket::*;
use osc::osc_data::OscArg::*;
use std::net::{Ipv4Addr,SocketAddrV4};
use core::str::FromStr;

type ChannelBuffers = Arc<Mutex<HashMap<i32, Vec<f32>>>>;

const Fs : usize = 44100usize;
const osc_interval : usize = Fs / 30usize;

#[derive(Clone)]
struct IndexedMagnitude {
    index : usize,
    magnitude : f32
}

use std::cmp::Ordering;
impl PartialOrd for IndexedMagnitude {
     fn partial_cmp(&self, other: &IndexedMagnitude) -> Option<Ordering> {
        return self.magnitude.partial_cmp(&other.magnitude);
     }
}
impl PartialEq for IndexedMagnitude {
    fn eq(&self, other: &IndexedMagnitude) -> bool {
        return self.magnitude.eq(&other.magnitude);
    }
}
extern crate num;
use num::integer::Integer;
fn pitch_detect(buckets : &Vec<f32>) -> f32{
    //  gcd of top 5 bands
    let mut indexed_buckets = vec!();
    let mut i = 0usize;
    for bucket in buckets {
        indexed_buckets.push(IndexedMagnitude{index : i, magnitude: bucket.clone()});
        i += 1usize;
    }
    let mut harmonics = indexed_buckets.clone();
    for indexed_bucket in indexed_buckets.iter() {
        for i in 1..4 {
            if indexed_bucket.index / i > 0usize {
                harmonics[indexed_bucket.index / i].magnitude += indexed_bucket.magnitude;
            }
        }
    }
    harmonics.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
    let max_idx = harmonics.pop().unwrap();
    //  convert index to freq
    let freq = (max_idx.index * Fs) as f32 / buckets.len() as f32;
    return freq;
}

fn pitch_centroid(buckets : &Vec<f32>) -> f32 {
    let mut centroid = 0f32;
    let totalMag : f32 = buckets.iter().sum();
    let mut i = 1usize;
    for bucket in buckets {
        centroid += i as f32 * bucket / totalMag;
        i += 1usize;
    }
    centroid / buckets.len() as f32
}

use std::iter::FromIterator;
fn meter_fft(
    num_channels : i32,
    c : Consumer<f32>,
    osc_sender : Arc<Mutex<OscSender>>,
    osc_prefix : String
    ) -> JoinHandle<()> {
        let mut bufs_by_channel = HashMap::new();
        let mut chan_index = 0;
        let fft_buckets = 2048*4;
        thread::spawn(move || {
            loop {
                if let Some(s) = c.try_pop() {
                    let mut buf = bufs_by_channel.entry(chan_index).or_insert(vec!());
                    buf.push(kiss_fft_cpx{r : s, i : 0f32});
                    if buf.len() == fft_buckets {
                        let mut fft = KissFFT::new(fft_buckets, false);
                        let mut fft_out = fft.transform_to_vec(buf);
                        //  only use first half
                        fft_out.split_off(fft_buckets / 2usize);
                        let fft_norm = Vec::from_iter(fft_out.iter().map(|c| (c.r * c.r + c.i * c.i).sqrt()));
                        let pitch = pitch_detect(&fft_norm);
                        let centroid = pitch_centroid(&fft_norm);
                        let mut sender = osc_sender.lock().unwrap();
                        sender.send(
                            OscMessage{
                            addr : format!("/opera/meter/id-{}/track{}/pitch_centroid", osc_prefix, chan_index).to_string(),
                            args : vec!(OscFloat(centroid))
                        });
                        buf.truncate(fft_buckets * 7 / 8 as usize);
                    }

                    chan_index = (chan_index + 1) % num_channels;
                } else {
                    thread::sleep_ms(12u32);
                }
            }
        })

    }
fn meter_rms(
    num_channels : i32,
    c : Consumer<f32>,
    p : Producer<f32>,
    osc_sender : Arc<Mutex<OscSender>>,
    osc_prefix : String
    ) -> JoinHandle<()> {
    let mut rms_map : HashMap<i32, f32> = HashMap::new();
    let mut chan_idx = 0;
    let mut samples = 0usize;
    let mut last_sent_time = 0usize;
    // TODO: use sample rate
    let alpha = 30f32 / 44100f32;
    thread::spawn(move || {
        loop {
            if let Some(s) = c.try_pop() {
                let old_rms = *rms_map.entry(chan_idx).or_insert(0f32);
                let new_rms = ((old_rms.powi(2) + s.powi(2)) / 2f32).sqrt();
                rms_map.insert(chan_idx, (1f32 - alpha) * old_rms + new_rms * alpha);
                if samples > last_sent_time + osc_interval {
                    let mut sender = osc_sender.lock().unwrap();
                    let mut msg_vec = vec!();
                    for (chan, rms) in rms_map.iter() {
                        let rms_msg = OscMessage{
                            addr : format!("/opera/meter/id-{}/track{}/rms", osc_prefix, chan).to_string(),
                            args : vec!(OscFloat(*rms as f32))
                        };
                        msg_vec.push(rms_msg);
                    }
                    let bundle = OscBundle {
                        time_tag : (0, 1),
                        conts : msg_vec
                    };
                    if let Err(e) = sender.send(bundle) {
                        println!("Error sending OSC: {:?}", e);
                    }
                    last_sent_time = samples;
                }
                chan_idx = (chan_idx + 1) % num_channels;
                samples += 1usize;
                p.push(s);
            } else {
                thread::sleep_ms(1u32);
            }
        }
    })
}

fn setup_stream(
    frames_per_buffer : u16,
    active_channels : i32,
    total_channels : i32,
    p : Producer<f32>) {
        let mut channel_idx = 0usize;
        let f = Box::new(move |input: &[f32], _: Settings, o: &mut[f32], _: Settings, dt: f64, _: CallbackFlags| {
            for s in input.iter() {
                if (channel_idx as i32) < active_channels {
                    p.push(*s);
                }
                channel_idx = (channel_idx + 1usize) % total_channels as usize;
            }
            return CallbackResult::Continue;
        });

        let stream = SoundStream::new().frames_per_buffer(frames_per_buffer).duplex(StreamParams::new().channels(total_channels), StreamParams::new()).run_callback(f).unwrap();
        while let Ok(true) = stream.is_active() {
            thread::sleep_ms(500);
        }
}

fn main() {
     let args: Vec<String> = env::args().collect();
     let active_channels : i32 = i32::from_str_radix(args[1].as_str(), 10).unwrap();
     let total_channels : i32 = i32::from_str_radix(args[2].as_str(), 10).unwrap();
     let frames_per_buffer : usize = usize::from_str_radix(args[3].as_str(), 10).unwrap();
     let send_ip = args[4].as_str();
     let send_port = u16::from_str_radix(args[5].as_str(), 10).unwrap();
     let meter_id = args[6].as_str();

     let (p_pa, c_rms) = bounded_spsc_queue::make(16 * frames_per_buffer * (total_channels as usize));
     let (p_rms, c_fft) = bounded_spsc_queue::make(16 * frames_per_buffer * (total_channels as usize));

     let mut sender;
     match OscSender::new(
         SocketAddrV4::new(Ipv4Addr::from_str("0.0.0.0").unwrap(), 0),
         SocketAddrV4::new(Ipv4Addr::from_str(send_ip).unwrap(), send_port)
         ) {
         Ok(s) => { sender = s; },
         Err(e) => { panic!(e); }
     }
     let sender_arc = Arc::new(Mutex::new(sender));
     meter_rms(active_channels, c_rms, p_rms, sender_arc.clone(), String::from(meter_id));
     meter_fft(active_channels, c_fft, sender_arc.clone(), String::from(meter_id));
     setup_stream(frames_per_buffer as u16, active_channels, total_channels, p_pa);
}
<commit_msg>dissonance, maxAmp, 44.1k Fs<commit_after>#![feature(convert)]
#![feature(core)]
#![feature(split_off)]
#![feature(iter_arith)]

extern crate getopts;
extern crate core;

use getopts::Options;
use std::env;
use std::thread::{JoinHandle, Thread};
use std::thread;
use std::sync::{Arc, Mutex};
use std::collections::{HashMap, BinaryHeap};
use std::sync::mpsc::{channel, Receiver, Sender};

extern crate bounded_spsc_queue;
use bounded_spsc_queue::{Producer, Consumer};

extern crate kissfft;
use kissfft::KissFFT;
use kissfft::binding::kiss_fft_cpx;

extern crate sound_stream;
//use sound_stream::{CallbackFlags, CallbackResult, SoundStream, Settings, StreamParams};
use sound_stream::*;
extern crate osc;
use osc::osc_sender::*;
use osc::osc_data::*;
use osc::osc_data::OscPacket::*;
use osc::osc_data::OscArg::*;
use std::net::{Ipv4Addr,SocketAddrV4};
use core::str::FromStr;

type ChannelBuffers = Arc<Mutex<HashMap<i32, Vec<f32>>>>;

const Fs : usize = 44100usize;
const osc_interval : usize = Fs / 30usize;

#[derive(Clone)]
struct IndexedMagnitude {
    index : usize,
    magnitude : f32
}

use std::cmp::Ordering;
impl PartialOrd for IndexedMagnitude {
     fn partial_cmp(&self, other: &IndexedMagnitude) -> Option<Ordering> {
        return self.magnitude.partial_cmp(&other.magnitude);
     }
}
impl PartialEq for IndexedMagnitude {
    fn eq(&self, other: &IndexedMagnitude) -> bool {
        return self.magnitude.eq(&other.magnitude);
    }
}
extern crate num;
use num::integer::Integer;
fn pitch_detect(buckets : &Vec<f32>) -> f32{
    //  gcd of top 5 bands
    let mut indexed_buckets = vec!();
    let mut i = 0usize;
    for bucket in buckets {
        indexed_buckets.push(IndexedMagnitude{index : i, magnitude: bucket.clone()});
        i += 1usize;
    }
    let mut harmonics = indexed_buckets.clone();
    for indexed_bucket in indexed_buckets.iter() {
        for i in 1..4 {
            if indexed_bucket.index / i > 0usize {
                harmonics[indexed_bucket.index / i].magnitude += indexed_bucket.magnitude;
            }
        }
    }
    harmonics.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
    let max_idx = harmonics.pop().unwrap();
    //  convert index to freq
    let freq = (max_idx.index * Fs) as f32 / buckets.len() as f32;
    return freq;
}

fn idx_to_freq(idx : usize, buckets : usize) -> f32 {
    (idx * Fs) as f32 / buckets as f32
}

fn bkt_to_freq(bkt : &IndexedMagnitude, buckets : usize) -> f32 {
    idx_to_freq(bkt.index, buckets)
}

fn dissonance(buckets : &Vec<f32>) -> f32{
    let mut indexed_buckets = vec!();
    let mut i = 0usize;
    for bucket in buckets {
        indexed_buckets.push(IndexedMagnitude{index : i, magnitude: bucket.clone()});
        i += 1usize;
    }
    //  desc sort by magnitude
    indexed_buckets.sort_by(|a, b| b.partial_cmp(a).unwrap_or(Ordering::Equal));
    let f0 = bkt_to_freq(&indexed_buckets[0], buckets.len() * 2);
    let f1 = bkt_to_freq(&indexed_buckets[1], buckets.len() * 2);
    let f2 = bkt_to_freq(&indexed_buckets[2], buckets.len() * 2);
    let mut d1 = f1 / f0;
    let mut d2 = f2 / f0;
    d1 = (d1 - d1.floor() - 0.5f32) * 2f32;
    d2 = (d2 - d2.floor() - 0.5f32) * 2f32;
    (d1 - d2).abs()
}

fn pitch_centroid(buckets : &Vec<f32>) -> f32 {
    let mut centroid = 0f32;
    let totalMag : f32 = buckets.iter().sum();
    let mut i = 1usize;
    for bucket in buckets {
        centroid += idx_to_freq(i, buckets.len() * 2) *  bucket / totalMag;
        i += 1usize;
    }
    centroid
}

use std::iter::FromIterator;
fn meter_fft(
    num_channels : i32,
    c : Consumer<f32>,
    osc_sender : Arc<Mutex<OscSender>>,
    osc_prefix : String
    ) -> JoinHandle<()> {
        let mut bufs_by_channel = HashMap::new();
        let mut chan_index = 0;
        let fft_buckets = 2048*4;
        thread::spawn(move || {
            loop {
                if let Some(s) = c.try_pop() {
                    let mut buf = bufs_by_channel.entry(chan_index).or_insert(vec!());
                    buf.push(kiss_fft_cpx{r : s, i : 0f32});
                    if buf.len() == fft_buckets {
                        let mut fft = KissFFT::new(fft_buckets, false);
                        let mut fft_out = fft.transform_to_vec(buf);
                        //  only use first half
                        fft_out.split_off(fft_buckets / 2usize);
                        let fft_norm = Vec::from_iter(fft_out.iter().map(|c| (c.r * c.r + c.i * c.i).sqrt()));
                        let dissonance = dissonance(&fft_norm);
                        let centroid = pitch_centroid(&fft_norm);
                        let mut sender = osc_sender.lock().unwrap();
                        sender.send(
                            OscBundle{
                                time_tag : (0, 1),
                                conts: vec!(
                                    OscMessage{
                                        addr : format!("/opera/meter/{}/track{}/pitchCentroid", osc_prefix, chan_index).to_string(),
                                        args : vec!(OscFloat(centroid))
                                    },
                                    OscMessage{
                                        addr : format!("/opera/meter/{}/track{}/dissonance", osc_prefix, chan_index).to_string(),
                                        args : vec!(OscFloat(dissonance))
                                    },
                                    )
                            });
                        buf.truncate(fft_buckets * 7 / 8 as usize);
                    }

                    chan_index = (chan_index + 1) % num_channels;
                } else {
                    thread::sleep_ms(12u32);
                }
            }
        })

    }
fn meter_rms(
    num_channels : i32,
    c : Consumer<f32>,
    p : Producer<f32>,
    osc_sender : Arc<Mutex<OscSender>>,
    osc_prefix : String
    ) -> JoinHandle<()> {
    let mut rms_map : HashMap<i32, f32> = HashMap::new();
    let mut max_amp_map : HashMap<i32, f32> = HashMap::new();
    let mut chan_idx = 0;
    let mut samples = 0usize;
    let mut last_sent_time = 0usize;
    // TODO: use sample rate
    let alpha = 30f32 / 44100f32;
    thread::spawn(move || {
        loop {
            if let Some(s) = c.try_pop() {
                let old_rms = *rms_map.entry(chan_idx).or_insert(0f32);
                let new_rms = ((old_rms.powi(2) + s.powi(2)) / 2f32).sqrt();
                rms_map.insert(chan_idx, (1f32 - alpha) * old_rms + new_rms * alpha);

                let old_max_amp = *max_amp_map.entry(chan_idx).or_insert(0f32);
                max_amp_map.insert(chan_idx, f32::max(old_max_amp, f32::abs(s)));
                if samples > last_sent_time + osc_interval * (num_channels as usize) {
                    let mut sender = osc_sender.lock().unwrap();
                    let mut msg_vec = vec!();
                    for (chan, rms) in rms_map.iter() {
                        let rms_msg = OscMessage{
                            addr : format!("/opera/meter/{}/track{}/rms", osc_prefix, chan).to_string(),
                            args : vec!(OscFloat(*rms as f32))
                        };
                        msg_vec.push(rms_msg);
                    }
                    for (chan, max_amp) in max_amp_map.iter() {
                        let amp_msg = OscMessage{
                            addr : format!("/opera/meter/{}/track{}/maxAmp", osc_prefix, chan).to_string(),
                            args : vec!(OscFloat(*max_amp))
                        };
                        msg_vec.push(amp_msg);
                    }
                    max_amp_map.clear();
                    let bundle = OscBundle {
                        time_tag : (0, 1),
                        conts : msg_vec
                    };
                    if let Err(e) = sender.send(bundle) {
                        println!("Error sending OSC: {:?}", e);
                    }
                    last_sent_time = samples;
                }
                chan_idx = (chan_idx + 1) % num_channels;
                samples += 1usize;
                p.push(s);
            } else {
                thread::sleep_ms(1u32);
            }
        }
    })
}

fn setup_stream(
    frames_per_buffer : u16,
    active_channels : i32,
    total_channels : i32,
    p : Producer<f32>) {
        let mut channel_idx = 0usize;
        let f = Box::new(move |input: &[f32], _: Settings, o: &mut[f32], _: Settings, dt: f64, _: CallbackFlags| {
            for s in input.iter() {
                if (channel_idx as i32) < active_channels {
                    p.push(*s);
                }
                channel_idx = (channel_idx + 1usize) % total_channels as usize;
            }
            return CallbackResult::Continue;
        });

        let stream = SoundStream::new().sample_hz(Fs as f64).frames_per_buffer(frames_per_buffer).duplex(StreamParams::new().channels(total_channels), StreamParams::new()).run_callback(f).unwrap();
        while let Ok(true) = stream.is_active() {
            thread::sleep_ms(500);
        }
}

fn main() {
     let args: Vec<String> = env::args().collect();
     let active_channels : i32 = i32::from_str_radix(args[1].as_str(), 10).unwrap();
     let total_channels : i32 = i32::from_str_radix(args[2].as_str(), 10).unwrap();
     let frames_per_buffer : usize = usize::from_str_radix(args[3].as_str(), 10).unwrap();
     let send_ip = args[4].as_str();
     let send_port = u16::from_str_radix(args[5].as_str(), 10).unwrap();
     let meter_id = args[6].as_str();

     let (p_pa, c_rms) = bounded_spsc_queue::make(16 * frames_per_buffer * (total_channels as usize));
     let (p_rms, c_fft) = bounded_spsc_queue::make(16 * frames_per_buffer * (total_channels as usize));

     let mut sender;
     match OscSender::new(
         SocketAddrV4::new(Ipv4Addr::from_str("0.0.0.0").unwrap(), 0),
         SocketAddrV4::new(Ipv4Addr::from_str(send_ip).unwrap(), send_port)
         ) {
         Ok(s) => { sender = s; },
         Err(e) => { panic!(e); }
     }
     let sender_arc = Arc::new(Mutex::new(sender));
     meter_rms(active_channels, c_rms, p_rms, sender_arc.clone(), String::from(meter_id));
     meter_fft(active_channels, c_fft, sender_arc.clone(), String::from(meter_id));
     setup_stream(frames_per_buffer as u16, active_channels, total_channels, p_pa);
}
<|endoftext|>