<commit_before>use crate::enums::{Color, Severity, Status};
use alpm::{Alpm, Db, Version};
use atty::Stream;
use clap::{load_yaml, App};
use curl::easy::Easy;
use log::{debug, info};
use serde::Deserialize;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::default::Default;
use std::io;
use std::process::exit;
use std::str;
use term::terminfo::TermInfo;
use term::{color, Attr};
use term::{StdoutTerminal, TerminfoTerminal};

mod enums;

const WEBSITE: &str = "https://security.archlinux.org";

#[derive(Default)]
struct Options {
    color: enums::Color,
    format: Option<String>,
    quiet: u64,
    recursive: u64,
    upgradable_only: bool,
    show_testing: bool,
    show_cve: bool,
}

#[derive(Deserialize)]
#[serde(transparent)]
struct Avgs {
    avgs: Vec<Avg>,
}

#[derive(Deserialize, Clone, Default)]
struct Avg {
    packages: Vec<String>,
    status: Status,
    #[serde(rename = "type")]
    kind: String,
    severity: Severity,
    fixed: Option<String>,
    issues: Vec<String>,
}

#[derive(PartialOrd, Ord, PartialEq, Eq)]
struct Affected {
    package: String,
    cves: Vec<String>,
    severity: Severity,
    status: Status,
    fixed: Option<String>,
    kind: Vec<String>,
}

fn main() {
    env_logger::init();

    let yaml = load_yaml!("cli.yml");
    let args = App::from_yaml(yaml).get_matches();

    let options = Options {
        color: args
            .value_of("color")
            .unwrap()
            .parse()
            .expect("parse::<Color> failed"),
        format: {
            match args.value_of("format") {
                Some(f) => Some(f.to_string()),
                None => None,
            }
        },
        quiet: args.occurrences_of("quiet"),
        recursive: args.occurrences_of("recursive"),
        upgradable_only: args.is_present("upgradable"),
        show_testing: args.is_present("testing"),
        show_cve: args.is_present("show-cve"),
    };

    let avgs = get_avg_json();
    let avgs: Avgs = serde_json::from_str(&avgs).expect("failed to parse json");

    let dbpath = args.value_of("dbpath").unwrap();
    let pacman = Alpm::new("/", dbpath).expect("Alpm::new() failed");
    let db = pacman.localdb();

    let mut affected = BTreeMap::new();

    for avg in &avgs.avgs {
        if avg.status == Status::NotAffected {
            continue;
        }

        for pkg in &avg.packages {
            if !system_is_affected(db, pkg, &avg) {
                continue;
            }

            let aff = Affected {
                package: pkg.to_string(),
                cves: Vec::new(),
                kind: Vec::new(),
                severity: avg.severity,
                status: Status::Unknown,
                fixed: None,
            };

            let aff = affected.entry(pkg.as_str()).or_insert(aff);
            aff.severity = aff.severity.max(avg.severity);
            aff.cves.extend(avg.issues.clone());
            aff.kind.push(avg.kind.clone());
            aff.status = avg.status;
            if aff.fixed.as_ref().map(|f| Version::new(f.as_str()))
                < avg.fixed.as_ref().map(|f| Version::new(f.as_str()))
            {
                aff.fixed = avg.fixed.clone();
            }

            aff.kind.sort_unstable();
            aff.kind.dedup()
        }
    }

    print_all_affected(&options, &affected, db);
}

fn get_avg_json() -> String {
    let mut avgs = String::new();
    info!("Downloading AVGs...");
    let avgs_url = format!("{}/issues/all.json", WEBSITE);

    let mut easy = Easy::new();
    easy.fail_on_error(true)
        .expect("curl::Easy::fail_on_error failed");
    easy.follow_location(true)
        .expect("curl::Easy::follow_location failed");
    easy.url(&avgs_url).expect("curl::Easy::url failed");
    let mut transfer = easy.transfer();
    transfer
        .write_function(|data| {
            avgs.push_str(str::from_utf8(data).expect("str conversion failed"));
            Ok(data.len())
        })
        .expect("write_function failed");
    if transfer.perform().is_err() {
        println!(
            "Cannot fetch data from {}, please check your network connection!",
            WEBSITE
        );
        exit(1)
    }
    drop(transfer);
    avgs
}

fn get_required_by(db: Db, pkg: &str) -> Vec<String> {
    db.pkg(pkg)
        .unwrap()
        .required_by()
        .iter()
        .map(|s| s.to_string())
        .collect()
}

fn get_required_by_recursive(db: Db, pkg: &str) -> Vec<String> {
    let mut pkgs = Vec::new();
    let mut seen = HashSet::new();
    _get_required_by_recursive(db, pkg, &mut pkgs, &mut seen);
    pkgs
}

fn _get_required_by_recursive(
    db: alpm::Db,
    pkg: &str,
    pkgs: &mut Vec<String>,
    seen: &mut HashSet<String>,
) {
    let new_pkgs = db.pkg(pkg).unwrap().required_by();
    seen.insert(pkg.to_string());

    for pkg in &new_pkgs {
        if !seen.contains(pkg) {
            _get_required_by_recursive(db, pkg, pkgs, seen)
        }
    }

    pkgs.extend(new_pkgs);
}

/// Given a package and an `avg::AVG`, returns true if the system is affected
fn system_is_affected(db: Db, pkg: &str, avg: &Avg) -> bool {
    let pkg = if let Ok(pkg) = db.pkg(pkg) {
        info!(
            "Found installed version {} for package {}",
            pkg.version(),
            pkg.name(),
        );
        pkg
    } else {
        debug!("Package {} not installed", pkg);
        return false;
    };

    if let Some(ref fixed) = avg.fixed {
        info!("Comparing with fixed version {}", fixed);
        pkg.version() < fixed
    } else {
        true
    }
}

/// Print a single Affected
fn print_affected(options: &Options, t: &mut term::StdoutTerminal, aff: &Affected, db: Db) {
    match aff.fixed {
        Some(ref v) if aff.status != Status::Vulnerable => {
            // Quiet option
            if options.quiet >= 1 {
                write_with_colours(
                    t,
                    &aff.package,
                    options,
                    Some(aff.severity.to_color()),
                    None,
                );

                if options.quiet == 1 {
                    write!(t, ">=").expect("term::write failed");
                    write_with_colours(t, v, options, Some(term::color::GREEN), None);
                }
            } else {
                match options.format {
                    Some(ref f) => {
                        print_affected_formatted(t, aff, options, f, db);
                    }
                    None => {
                        print_affected_colored(t, aff, options, db);
                    }
                }
            }

            writeln!(t).expect("term::writeln failed");
        }

        _ if !options.upgradable_only => {
            if options.quiet > 0 {
                write_with_colours(
                    t,
                    &aff.package,
                    options,
                    Some(aff.severity.to_color()),
                    None,
                );
            } else if let Some(ref f) = options.format {
                print_affected_formatted(t, aff, options, f, db);
            } else {
                print_affected_colored(t, aff, options, db);
            }

            writeln!(t).expect("term::writeln failed");
        }
        _ => (),
    }
}

// Print a list of Affected
fn print_all_affected(options: &Options, affected: &BTreeMap<&str, Affected>, db: Db) {
    let fake_term = TermInfo {
        names: vec![],
        bools: HashMap::new(),
        numbers: HashMap::new(),
        strings: HashMap::new(),
    };

    let mut t = match term::stdout() {
        Some(x) => x,
        None => Box::new(TerminfoTerminal::new_with_terminfo(io::stdout(), fake_term))
            as Box<StdoutTerminal>,
    };

    for aff in affected.values() {
        print_affected(options, t.as_mut(), aff, db);
    }
}

/// Prints "Package {pkg} is affected by {issues}. {severity}!" colored
fn print_affected_colored(t: &mut term::StdoutTerminal, aff: &Affected, options: &Options, db: Db) {
    // Bold package
    write!(t, "Package ").expect("term::write failed");
    write_with_colours(t, &aff.package, options, None, Some(term::Attr::Bold));
    // Normal "is affected by {issues}"
    write!(t, " is affected by {}. ", aff.kind.join(", ")).expect("term::write failed");
    if options.show_cve {
        write!(t, "({}). ", aff.cves.join(",")).expect("term::write failed");
    }

    if options.recursive != 0 {
        let required_by = if options.recursive == 1 {
            get_required_by(db, &aff.package)
        } else {
            get_required_by_recursive(db, &aff.package)
        };

        if !required_by.is_empty() {
            write!(t, "It's required by {}. ", required_by.join(", ")).expect("term::write failed");
        }
    }

    // Colored severit
    write_with_colours(
        t,
        &aff.severity.to_string(),
        options,
        Some(aff.severity.to_color()),
        None,
    );
    write!(t, "!").expect("term::write failed");

    if let Some(ref version) = aff.fixed {
        if aff.status == Status::Fixed {
            // Print: Update to {}!
            write!(t, " Update to at least ").expect("term::write failed");
            write_with_colours(t, version, options, Some(color::GREEN), Some(Attr::Bold));
            write!(t, "!").expect("term::write failed");
        } else if aff.status == Status::Testing && options.show_testing {
            // Print: Update to {} from the testing repos!"
            write!(t, " Update to at least").expect("term::write failed");
            write_with_colours(t, version, options, Some(color::GREEN), Some(Attr::Bold));
            write!(t, " from the testing repos!").expect("term::write failed");
        }
    }
}

/// Prints output formatted as the user wants
fn print_affected_formatted(
    t: &mut term::StdoutTerminal,
    aff: &Affected,
    options: &Options,
    f: &str,
    db: Db,
) {
    let mut chars = f.chars().peekable();

    while let Some(c) = chars.next() {
        match c {
            '%' => match chars.peek() {
                Some('%') => {
                    write!(t, "%").expect("term::write failed");
                    chars.next();
                }
                Some('r') => {
                    write!(
                        t,
                        "{}",
                        get_required_by(db, &aff.package).join(",").as_str()
                    )
                    .expect("term::write failed");
                    chars.next();
                }
                Some('n') => {
                    write_with_colours(
                        t,
                        &aff.package,
                        options,
                        Some(aff.severity.to_color()),
                        None,
                    );
                    chars.next();
                }
                Some('c') => {
                    write!(t, "{}", aff.cves.join(",")).expect("term::write failed");
                    chars.next();
                }
                Some('v') => {
                    if let Some(ref version) = aff.fixed {
                        if aff.status == Status::Fixed
                            || (aff.status == Status::Testing && options.show_testing)
                        {
                            write_with_colours(
                                t,
                                version,
                                options,
                                Some(color::GREEN),
                                Some(Attr::Bold),
                            );
                        }
                    }
                    chars.next();
                }
                Some('t') => {
                    if !aff.kind.is_empty() {
                        write!(t, "{}", aff.kind.join(", ")).expect("term::write failed");
                    }
                    chars.next();
                }
                Some(x) => {
                    debug!("Unknown placeholder {}", x);
                    chars.next();
                }
                None => {}
            },
            x => {
                write!(t, "{}", x).expect("term::write failed");
            }
        }
    }
}

fn write_with_colours(
    t: &mut term::StdoutTerminal,
    text: &str,
    options: &Options,
    color: Option<term::color::Color>,
    attribute: Option<term::Attr>,
) {
    let show_colors = match options.color {
        Color::Always => true,
        Color::Never => false,
        Color::Auto => t.supports_color() && atty::is(Stream::Stdout),
    };

    if show_colors {
        if let Some(c) = color {
            t.fg(c).expect("term::fg failed");
        }
        if let Some(a) = attribute {
            t.attr(a).expect("term::attr failed");
        }
    }

    write!(t, "{}", text).expect("term::write failed");

    if show_colors {
        t.reset().expect("term::stdout failed");
    }
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_to_avg() {
        let json: Value = serde_json::from_str(
            "{\"issues\": [\"CVE-1\", \"CVE-2\"], \"fixed\": \"1.0\", \
         \"severity\": \"High\", \"status\": \"Not affected\"}",
        )
        .expect("serde_json::from_str failed");

        let avg1 = to_avg(&json);
        assert_eq!(2, avg1.issues.len());
        assert_eq!(Some("1.0".to_string()), avg1.fixed);
        assert_eq!(enums::Severity::High, avg1.severity);
        assert_eq!(enums::Status::NotAffected, avg1.status);

        let json: Value = serde_json::from_str(
            "{\"issues\": [\"CVE-1\"], \"fixed\": null, \
         \"severity\": \"Low\", \"status\": \"Vulnerable\"}",
        )
        .expect("serde_json::from_str failed");

        let avg2 = to_avg(&json);
        assert_eq!(1, avg2.issues.len());
        assert_eq!(None, avg2.fixed);
        assert_eq!(enums::Severity::Low, avg2.severity);
        assert_eq!(enums::Status::Vulnerable, avg2.status);
    }

    #[test]
    fn test_system_is_affected() {
        let pacman = alpm::Alpm::new(ROOT_DIR, DB_PATH).expect("Alpm::new failed");
        let db = pacman.localdb();

        let avg1 = avg::AVG {
            issues: vec!["CVE-1".to_string(), "CVE-2".to_string()],
            fixed: Some("1.0.0".to_string()),
            severity: enums::Severity::Unknown,
            status: enums::Status::Unknown,
            required_by: Vec::new(),
        };

        assert_eq!(false, system_is_affected(&db, &"pacman".to_string(), &avg1));

        let avg2 = avg::AVG {
            issues: vec!["CVE-1".to_string(), "CVE-2".to_string()],
            fixed: Some("7.0.0".to_string()),
            severity: enums::Severity::Unknown,
            status: enums::Status::Unknown,
            required_by: Vec::new(),
        };

        assert!(system_is_affected(&db, &"pacman".to_string(), &avg2));
    }

    #[test]
    fn test_package_is_installed() {
        let pacman = alpm::Alpm::new(ROOT_DIR, DB_PATH).expect("Alpm::new failed");
        let db = pacman.localdb();

        let packages = vec!["pacman".to_string(), "pac".to_string()];
        assert!(package_is_installed(&db, &packages));

        let packages = vec!["pac".to_string()];
        assert_eq!(false, package_is_installed(&db, &packages));
    }

    #[test]
    fn test_merge_avgs() {
        let mut avgs: BTreeMap<String, Vec<_>> = BTreeMap::new();

        let avg1 = avg::AVG {
            issues: vec!["CVE-1".to_string(), "CVE-2".to_string()],
            fixed: Some("1.0.0".to_string()),
            severity: enums::Severity::Unknown,
            status: enums::Status::Fixed,
            required_by: Vec::new(),
        };

        let avg2 = avg::AVG {
            issues: vec!["CVE-4".to_string(), "CVE-10".to_string()],
            fixed: Some("0.9.8".to_string()),
            severity: enums::Severity::High,
            status: enums::Status::Testing,
            required_by: Vec::new(),
        };

        assert!(enums::Severity::Critical > enums::Severity::High);

        avgs.insert("package".to_string(), vec![avg1.clone(), avg2.clone()]);

        avgs.insert("package2".to_string(), vec![avg1, avg2]);

        let pacman = alpm::Alpm::new(ROOT_DIR, DB_PATH).expect("Alpm::new failed");
        let db = pacman.localdb();

        let merged = merge_avgs(&avgs, &db, &Options::default());

        assert_eq!(2, merged.len());
        assert_eq!(
            4,
            merged
                .get(&"package".to_string())
                .expect("'package' key not found")
                .issues
                .len()
        );
        assert_eq!(
            enums::Severity::High,
            merged
                .get(&"package".to_string())
                .expect("'package' key not found")
                .severity
        );
        assert_eq!(
            enums::Status::Testing,
            merged
                .get(&"package".to_string())
                .expect("'package' key not found")
                .status
        );
    }
}
<commit_msg>Fix tests<commit_after>use crate::enums::{Color, Severity, Status};
use alpm::{Alpm, Db, Version};
use atty::Stream;
use clap::{load_yaml, App};
use curl::easy::Easy;
use log::{debug, info};
use serde::Deserialize;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::default::Default;
use std::io;
use std::process::exit;
use std::str;
use term::terminfo::TermInfo;
use term::{color, Attr};
use term::{StdoutTerminal, TerminfoTerminal};

mod enums;

const WEBSITE: &str = "https://security.archlinux.org";

#[derive(Default)]
struct Options {
    color: enums::Color,
    format: Option<String>,
    quiet: u64,
    recursive: u64,
    upgradable_only: bool,
    show_testing: bool,
    show_cve: bool,
}

#[derive(Deserialize)]
#[serde(transparent)]
struct Avgs {
    avgs: Vec<Avg>,
}

#[derive(Deserialize, Clone, Default)]
struct Avg {
    packages: Vec<String>,
    status: Status,
    #[serde(rename = "type")]
    kind: String,
    severity: Severity,
    fixed: Option<String>,
    issues: Vec<String>,
}

#[derive(PartialOrd, Ord, PartialEq, Eq)]
struct Affected {
    package: String,
    cves: Vec<String>,
    severity: Severity,
    status: Status,
    fixed: Option<String>,
    kind: Vec<String>,
}

fn main() {
    env_logger::init();

    let yaml = load_yaml!("cli.yml");
    let args = App::from_yaml(yaml).get_matches();

    let options = Options {
        color: args
            .value_of("color")
            .unwrap()
            .parse()
            .expect("parse::<Color> failed"),
        format: {
            match args.value_of("format") {
                Some(f) => Some(f.to_string()),
                None => None,
            }
        },
        quiet: args.occurrences_of("quiet"),
        recursive: args.occurrences_of("recursive"),
        upgradable_only: args.is_present("upgradable"),
        show_testing: args.is_present("testing"),
        show_cve: args.is_present("show-cve"),
    };

    let avgs = get_avg_json();
    let avgs: Avgs = serde_json::from_str(&avgs).expect("failed to parse json");

    let dbpath = args.value_of("dbpath").unwrap();
    let pacman = Alpm::new("/", dbpath).expect("Alpm::new() failed");
    let db = pacman.localdb();

    let mut affected = BTreeMap::new();

    for avg in &avgs.avgs {
        if avg.status == Status::NotAffected {
            continue;
        }

        for pkg in &avg.packages {
            if !system_is_affected(db, pkg, &avg) {
                continue;
            }

            let aff = Affected {
                package: pkg.to_string(),
                cves: Vec::new(),
                kind: Vec::new(),
                severity: avg.severity,
                status: Status::Unknown,
                fixed: None,
            };

            let aff = affected.entry(pkg.as_str()).or_insert(aff);
            aff.severity = aff.severity.max(avg.severity);
            aff.cves.extend(avg.issues.clone());
            aff.kind.push(avg.kind.clone());
            aff.status = avg.status;
            if aff.fixed.as_ref().map(|f| Version::new(f.as_str()))
                < avg.fixed.as_ref().map(|f| Version::new(f.as_str()))
            {
                aff.fixed = avg.fixed.clone();
            }

            aff.kind.sort_unstable();
            aff.kind.dedup()
        }
    }

    print_all_affected(&options, &affected, db);
}

fn get_avg_json() -> String {
    let mut avgs = String::new();
    info!("Downloading AVGs...");
    let avgs_url = format!("{}/issues/all.json", WEBSITE);

    let mut easy = Easy::new();
    easy.fail_on_error(true)
        .expect("curl::Easy::fail_on_error failed");
    easy.follow_location(true)
        .expect("curl::Easy::follow_location failed");
    easy.url(&avgs_url).expect("curl::Easy::url failed");
    let mut transfer = easy.transfer();
    transfer
        .write_function(|data| {
            avgs.push_str(str::from_utf8(data).expect("str conversion failed"));
            Ok(data.len())
        })
        .expect("write_function failed");
    if transfer.perform().is_err() {
        println!(
            "Cannot fetch data from {}, please check your network connection!",
            WEBSITE
        );
        exit(1)
    }
    drop(transfer);
    avgs
}

fn get_required_by(db: Db, pkg: &str) -> Vec<String> {
    db.pkg(pkg)
        .unwrap()
        .required_by()
        .iter()
        .map(|s| s.to_string())
        .collect()
}

fn get_required_by_recursive(db: Db, pkg: &str) -> Vec<String> {
    let mut pkgs = Vec::new();
    let mut seen = HashSet::new();
    _get_required_by_recursive(db, pkg, &mut pkgs, &mut seen);
    pkgs
}

fn _get_required_by_recursive(
    db: alpm::Db,
    pkg: &str,
    pkgs: &mut Vec<String>,
    seen: &mut HashSet<String>,
) {
    let new_pkgs = db.pkg(pkg).unwrap().required_by();
    seen.insert(pkg.to_string());

    for pkg in &new_pkgs {
        if !seen.contains(pkg) {
            _get_required_by_recursive(db, pkg, pkgs, seen)
        }
    }

    pkgs.extend(new_pkgs);
}

/// Given a package and an `avg::AVG`, returns true if the system is affected
fn system_is_affected(db: Db, pkg: &str, avg: &Avg) -> bool {
    let pkg = if let Ok(pkg) = db.pkg(pkg) {
        info!(
            "Found installed version {} for package {}",
            pkg.version(),
            pkg.name(),
        );
        pkg
    } else {
        debug!("Package {} not installed", pkg);
        return false;
    };

    if let Some(ref fixed) = avg.fixed {
        info!("Comparing with fixed version {}", fixed);
        pkg.version() < fixed
    } else {
        true
    }
}

/// Print a single Affected
fn print_affected(options: &Options, t: &mut term::StdoutTerminal, aff: &Affected, db: Db) {
    match aff.fixed {
        Some(ref v) if aff.status != Status::Vulnerable => {
            // Quiet option
            if options.quiet >= 1 {
                write_with_colours(
                    t,
                    &aff.package,
                    options,
                    Some(aff.severity.to_color()),
                    None,
                );

                if options.quiet == 1 {
                    write!(t, ">=").expect("term::write failed");
                    write_with_colours(t, v, options, Some(term::color::GREEN), None);
                }
            } else {
                match options.format {
                    Some(ref f) => {
                        print_affected_formatted(t, aff, options, f, db);
                    }
                    None => {
                        print_affected_colored(t, aff, options, db);
                    }
                }
            }

            writeln!(t).expect("term::writeln failed");
        }

        _ if !options.upgradable_only => {
            if options.quiet > 0 {
                write_with_colours(
                    t,
                    &aff.package,
                    options,
                    Some(aff.severity.to_color()),
                    None,
                );
            } else if let Some(ref f) = options.format {
                print_affected_formatted(t, aff, options, f, db);
            } else {
                print_affected_colored(t, aff, options, db);
            }

            writeln!(t).expect("term::writeln failed");
        }
        _ => (),
    }
}

// Print a list of Affected
fn print_all_affected(options: &Options, affected: &BTreeMap<&str, Affected>, db: Db) {
    let fake_term = TermInfo {
        names: vec![],
        bools: HashMap::new(),
        numbers: HashMap::new(),
        strings: HashMap::new(),
    };

    let mut t = match term::stdout() {
        Some(x) => x,
        None => Box::new(TerminfoTerminal::new_with_terminfo(io::stdout(), fake_term))
            as Box<StdoutTerminal>,
    };

    for aff in affected.values() {
        print_affected(options, t.as_mut(), aff, db);
    }
}

/// Prints "Package {pkg} is affected by {issues}. {severity}!" colored
fn print_affected_colored(t: &mut term::StdoutTerminal, aff: &Affected, options: &Options, db: Db) {
    // Bold package
    write!(t, "Package ").expect("term::write failed");
    write_with_colours(t, &aff.package, options, None, Some(term::Attr::Bold));
    // Normal "is affected by {issues}"
    write!(t, " is affected by {}. ", aff.kind.join(", ")).expect("term::write failed");
    if options.show_cve {
        write!(t, "({}). ", aff.cves.join(",")).expect("term::write failed");
    }

    if options.recursive != 0 {
        let required_by = if options.recursive == 1 {
            get_required_by(db, &aff.package)
        } else {
            get_required_by_recursive(db, &aff.package)
        };

        if !required_by.is_empty() {
            write!(t, "It's required by {}. ", required_by.join(", ")).expect("term::write failed");
        }
    }

    // Colored severit
    write_with_colours(
        t,
        &aff.severity.to_string(),
        options,
        Some(aff.severity.to_color()),
        None,
    );
    write!(t, "!").expect("term::write failed");

    if let Some(ref version) = aff.fixed {
        if aff.status == Status::Fixed {
            // Print: Update to {}!
            write!(t, " Update to at least ").expect("term::write failed");
            write_with_colours(t, version, options, Some(color::GREEN), Some(Attr::Bold));
            write!(t, "!").expect("term::write failed");
        } else if aff.status == Status::Testing && options.show_testing {
            // Print: Update to {} from the testing repos!"
            write!(t, " Update to at least").expect("term::write failed");
            write_with_colours(t, version, options, Some(color::GREEN), Some(Attr::Bold));
            write!(t, " from the testing repos!").expect("term::write failed");
        }
    }
}

/// Prints output formatted as the user wants
fn print_affected_formatted(
    t: &mut term::StdoutTerminal,
    aff: &Affected,
    options: &Options,
    f: &str,
    db: Db,
) {
    let mut chars = f.chars().peekable();

    while let Some(c) = chars.next() {
        match c {
            '%' => match chars.peek() {
                Some('%') => {
                    write!(t, "%").expect("term::write failed");
                    chars.next();
                }
                Some('r') => {
                    write!(
                        t,
                        "{}",
                        get_required_by(db, &aff.package).join(",").as_str()
                    )
                    .expect("term::write failed");
                    chars.next();
                }
                Some('n') => {
                    write_with_colours(
                        t,
                        &aff.package,
                        options,
                        Some(aff.severity.to_color()),
                        None,
                    );
                    chars.next();
                }
                Some('c') => {
                    write!(t, "{}", aff.cves.join(",")).expect("term::write failed");
                    chars.next();
                }
                Some('v') => {
                    if let Some(ref version) = aff.fixed {
                        if aff.status == Status::Fixed
                            || (aff.status == Status::Testing && options.show_testing)
                        {
                            write_with_colours(
                                t,
                                version,
                                options,
                                Some(color::GREEN),
                                Some(Attr::Bold),
                            );
                        }
                    }
                    chars.next();
                }
                Some('t') => {
                    if !aff.kind.is_empty() {
                        write!(t, "{}", aff.kind.join(", ")).expect("term::write failed");
                    }
                    chars.next();
                }
                Some(x) => {
                    debug!("Unknown placeholder {}", x);
                    chars.next();
                }
                None => {}
            },
            x => {
                write!(t, "{}", x).expect("term::write failed");
            }
        }
    }
}

fn write_with_colours(
    t: &mut term::StdoutTerminal,
    text: &str,
    options: &Options,
    color: Option<term::color::Color>,
    attribute: Option<term::Attr>,
) {
    let show_colors = match options.color {
        Color::Always => true,
        Color::Never => false,
        Color::Auto => t.supports_color() && atty::is(Stream::Stdout),
    };

    if show_colors {
        if let Some(c) = color {
            t.fg(c).expect("term::fg failed");
        }
        if let Some(a) = attribute {
            t.attr(a).expect("term::attr failed");
        }
    }

    write!(t, "{}", text).expect("term::write failed");

    if show_colors {
        t.reset().expect("term::stdout failed");
    }
}

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

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

        const ROOT_DIR: &str = "/";
        const DB_PATH: &str = "/var/lib/pacman";

        #[test]
        fn test_system_is_affected() {
            let pacman = alpm::Alpm::new(ROOT_DIR, DB_PATH).expect("Alpm::new failed");
            let db = pacman.localdb();

            let avg1 = Avg {
                issues: vec!["CVE-1".to_string(), "CVE-2".to_string()],
                fixed: Some("2000.0.0".to_string()),
                severity: enums::Severity::Unknown,
                status: enums::Status::Unknown,
                packages: Vec::new(),
                kind: String::new(),
            };

            assert_eq!(false, system_is_affected(db, "filesystem", &avg1));

            let avg2 = Avg {
                issues: vec!["CVE-1".to_string(), "CVE-2".to_string()],
                fixed: Some("3009.0.0".to_string()),
                severity: enums::Severity::Unknown,
                status: enums::Status::Unknown,
                packages: Vec::new(),
                kind: String::new(),
            };

            assert!(system_is_affected(db, "filesystem", &avg2));
        }
    }
}
<|endoftext|>


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

#[macro_use]
extern crate clap;
extern crate hyper;
extern crate pulldown_cmark;
extern crate rayon;
extern crate url;
extern crate walkdir;

mod document;
mod error;

use clap::{Arg, App};
use document::{Document, Error, Event};
use error::{DocumentError, DocumentLocation, LinkError, LocatedDocumentError};
use hyper::client::Client;
use hyper::header::UserAgent;
use hyper::status::StatusCode;
use rayon::prelude::*;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs::File;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::process::exit;
use std::sync::Arc;
use url::{ParseError, Url};
use walkdir::WalkDir;

macro_rules! fail {
    ($($print:expr),+) => {
        {
            let _ = writeln!(&mut std::io::stderr(), $($print),+);
            exit(1);
        }
    };
}

struct LinkContext {
    target: String,
    text: String,
    line: usize,
    file: PathBuf,
}

impl LinkContext {
    fn new_error(self, error: LinkError) -> LocatedDocumentError {
        LocatedDocumentError {
            location: DocumentLocation {
                path: self.file,
                line: self.line,
            },
            error: DocumentError::Link {
                text: self.text,
                target: self.target,
                error: error,
            },
        }
    }
}

fn main() {
    let matches = App::new("Marker")
        .version(crate_version!())
        .arg(Arg::with_name("root")
            .short("r")
            .long("root")
            .help("The path to the root of the documentation to be checked")
            .takes_value(true))
        .arg(Arg::with_name("skip-http")
            .long("skip-http")
            .help("Skip validation of HTTP[S] URLs"))
        .get_matches();

    let skip_http = matches.is_present("skip-http");
    let root = Path::new(matches.value_of("root").unwrap_or("."));
    let mut links = Vec::new();
    let mut errors = Vec::new();

    for entry in WalkDir::new(root).into_iter().filter_map(|entry| {
        let entry = entry.unwrap_or_else(|error| {
            fail!("Failed to walk directory: {}", error);
        });
        if entry.file_type().is_file() && entry.path().extension() == Some(OsStr::new("md")) {
            Some(entry)
        } else {
            None
        }
    }) {
        let contents = {
            let mut file = File::open(entry.path()).unwrap_or_else(|error| {
                fail!("Failed to open file ({}): {}",
                      entry.path().display(),
                      error);
            });
            let mut text = String::new();
            if let Err(error) = file.read_to_string(&mut text) {
                fail!("Failed to read file ({}): {}",
                      entry.path().display(),
                      error);
            };
            text
        };

        for event in Document::new(&contents) {
            match event.event {
                Event::Link { target, text } => {
                    links.push(LinkContext {
                        target: target,
                        text: text,
                        line: event.line,
                        file: entry.path().to_path_buf(),
                    })
                }
                Event::Error(Error::ReferenceBroken { target, text }) => {
                    errors.push(LinkContext {
                            target: target,
                            text: text,
                            line: event.line,
                            file: entry.path().to_path_buf(),
                        }
                        .new_error(LinkError::ReferenceBroken))
                }
            }
        }
    }

    let mut urls = HashMap::new();
    for link in links {
        match Url::parse(&link.target) {
            Ok(_) if skip_http => {}
            Ok(mut url) => {
                url.set_fragment(None);
                urls.entry(url).or_insert(Vec::new()).push(link)
            }
            Err(ParseError::RelativeUrlWithoutBase) => {
                if let Err(error) = check_path(&link.target, &link.file) {
                    errors.push(link.new_error(error))
                }
            }
            Err(error) => errors.push(link.new_error(LinkError::UrlMalformed(error))),
        }
    }

    for (result, links) in urls.into_par_iter()
        .map(|(url, links)| (check_url(url), links))
        .collect::<Vec<_>>() {
        if let Err(error) = result {
            for link in links {
                errors.push(link.new_error(error.clone()))
            }
        }
    }

    for error in &errors {
        println!("{}", error);
    }

    if errors.len() > 0 {
        exit(1)
    }
}

fn check_url(url: Url) -> Result<(), LinkError> {
    if url.scheme() != "http" && url.scheme() != "https" {
        return Ok(());
    }

    let client = Client::new();
    let agent = UserAgent(format!("marker/{}", crate_version!()));

    let res = client.head(url.clone()).header(agent.clone()).send().and_then(|resp| {
        if resp.status == StatusCode::MethodNotAllowed {
            client.get(url.clone()).header(agent.clone()).send()
        } else {
            Ok(resp)
        }
    });
    match res {
        Ok(resp) => {
            match resp.status {
                StatusCode::Ok => Ok(()),
                status => Err(LinkError::HttpStatus(status)),
            }
        }
        Err(error) => return Err(LinkError::HttpError(Arc::new(error))),
    }
}

fn check_path(target: &str, file: &Path) -> Result<(), LinkError> {
    let path = Path::new(OsStr::new(target.split('#').next().expect("string")));
    if path.is_absolute() {
        Err(LinkError::PathAbsolute)
    } else if !file.parent().expect("non-root file path").join(path).exists() {
        Err(LinkError::PathNonExistant)
    } else {
        Ok(())
    }
}
<commit_msg>main: print out errors as they are encountered<commit_after>// Copyright 2016 Alex Crawford
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#[macro_use]
extern crate clap;
extern crate hyper;
extern crate pulldown_cmark;
extern crate rayon;
extern crate url;
extern crate walkdir;

mod document;
mod error;

use clap::{Arg, App};
use document::{Document, Error, Event};
use error::{DocumentError, DocumentLocation, LinkError, LocatedDocumentError};
use hyper::client::Client;
use hyper::header::UserAgent;
use hyper::status::StatusCode;
use rayon::prelude::*;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fs::File;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::process::exit;
use std::sync::Arc;
use url::{ParseError, Url};
use walkdir::WalkDir;

macro_rules! fail {
    ($($print:expr),+) => {
        {
            let _ = writeln!(&mut std::io::stderr(), $($print),+);
            exit(1);
        }
    };
}

macro_rules! printerror {
    ($error:expr, $flag:expr) => {
        {
            println!("{}", $error);
            $flag = true;
        }
    };
}

struct LinkContext {
    target: String,
    text: String,
    line: usize,
    file: PathBuf,
}

impl LinkContext {
    fn new_error(self, error: LinkError) -> LocatedDocumentError {
        LocatedDocumentError {
            location: DocumentLocation {
                path: self.file,
                line: self.line,
            },
            error: DocumentError::Link {
                text: self.text,
                target: self.target,
                error: error,
            },
        }
    }
}

fn main() {
    let matches = App::new("Marker")
        .version(crate_version!())
        .arg(Arg::with_name("root")
            .short("r")
            .long("root")
            .help("The path to the root of the documentation to be checked")
            .takes_value(true))
        .arg(Arg::with_name("skip-http")
            .long("skip-http")
            .help("Skip validation of HTTP[S] URLs"))
        .get_matches();

    let skip_http = matches.is_present("skip-http");
    let root = Path::new(matches.value_of("root").unwrap_or("."));
    let mut links = Vec::new();
    let mut found_error = false;

    for entry in WalkDir::new(root).into_iter().filter_map(|entry| {
        let entry = entry.unwrap_or_else(|error| {
            fail!("Failed to walk directory: {}", error);
        });
        if entry.file_type().is_file() && entry.path().extension() == Some(OsStr::new("md")) {
            Some(entry)
        } else {
            None
        }
    }) {
        let contents = {
            let mut file = File::open(entry.path()).unwrap_or_else(|error| {
                fail!("Failed to open file ({}): {}",
                      entry.path().display(),
                      error);
            });
            let mut text = String::new();
            if let Err(error) = file.read_to_string(&mut text) {
                fail!("Failed to read file ({}): {}",
                      entry.path().display(),
                      error);
            };
            text
        };

        for event in Document::new(&contents) {
            match event.event {
                Event::Link { target, text } => {
                    links.push(LinkContext {
                        target: target,
                        text: text,
                        line: event.line,
                        file: entry.path().to_path_buf(),
                    })
                }
                Event::Error(Error::ReferenceBroken { target, text }) => {
                    printerror!(LinkContext {
                                        target: target,
                                        text: text,
                                        line: event.line,
                                        file: entry.path().to_path_buf(),
                                    }
                                    .new_error(LinkError::ReferenceBroken),
                                found_error)
                }
            }
        }
    }

    let mut urls = HashMap::new();
    for link in links {
        match Url::parse(&link.target) {
            Ok(_) if skip_http => {}
            Ok(mut url) => {
                url.set_fragment(None);
                urls.entry(url).or_insert(Vec::new()).push(link)
            }
            Err(ParseError::RelativeUrlWithoutBase) => {
                if let Err(error) = check_path(&link.target, &link.file) {
                    printerror!(link.new_error(error), found_error)
                }
            }
            Err(error) => printerror!(link.new_error(LinkError::UrlMalformed(error)), found_error),
        }
    }

    for (result, links) in urls.into_par_iter()
        .map(|(url, links)| (check_url(url), links))
        .collect::<Vec<_>>() {
        if let Err(error) = result {
            for link in links {
                printerror!(link.new_error(error.clone()), found_error)
            }
        }
    }

    if found_error {
        exit(1)
    }
}

fn check_url(url: Url) -> Result<(), LinkError> {
    if url.scheme() != "http" && url.scheme() != "https" {
        return Ok(());
    }

    let client = Client::new();
    let agent = UserAgent(format!("marker/{}", crate_version!()));

    let res = client.head(url.clone()).header(agent.clone()).send().and_then(|resp| {
        if resp.status == StatusCode::MethodNotAllowed {
            client.get(url.clone()).header(agent.clone()).send()
        } else {
            Ok(resp)
        }
    });
    match res {
        Ok(resp) => {
            match resp.status {
                StatusCode::Ok => Ok(()),
                status => Err(LinkError::HttpStatus(status)),
            }
        }
        Err(error) => return Err(LinkError::HttpError(Arc::new(error))),
    }
}

fn check_path(target: &str, file: &Path) -> Result<(), LinkError> {
    let path = Path::new(OsStr::new(target.split('#').next().expect("string")));
    if path.is_absolute() {
        Err(LinkError::PathAbsolute)
    } else if !file.parent().expect("non-root file path").join(path).exists() {
        Err(LinkError::PathNonExistant)
    } else {
        Ok(())
    }
}
<|endoftext|>


<commit_before>extern crate ww;

use ww::map::{Map, MapRoomId};
use ww::room::Room;
use ww::direction::Direction::*;
use ww::command::Command;

use RoomId::*;

const MAX_ROOMS: usize = 20;

#[derive(Debug, Copy, Clone)]
enum RoomId {
  Hallway = 1,
  AudienceChamber = 2,
}

// Ideally we'd actually just get rid of MapRoomId and add a constraint
// to Map<T> requiring that T be type-castable as usize, but I don't
// know how to do that, so...
impl MapRoomId for RoomId {
  fn room_id(self) -> usize { self as usize }
}

fn main() {
  let mut rooms = [Room::new(); MAX_ROOMS];
  let mut map = Map::new(&mut rooms);

  map.room(Hallway).describe(
    "Hallway",
    "You are in the hallway. \
     There is a door to the south. \
     Through the windows to the north you can see a secret herb garden."
  );
  map.room(AudienceChamber).describe(
    "Audience Chamber",
    "This is the audience chamber. \
     There is a window to the west. By looking to the right \
     through it you can see the entrance to the castle. \
     Doors leave this room to the north, east, and south."
  );

  map.connect(Hallway, South, AudienceChamber);

  println!("Werewolves and Wanderer\n");

  let mut curr_room = Hallway;
  let mut show_desc = true;

  loop {
    if show_desc {
      println!("{}", map.room(curr_room).description);
      show_desc = false;
    }

    match Command::get() {
      Command::Go(dir) => {
        if let Some(room) = map.room(curr_room).get_exit(dir) {
          curr_room = room;
          show_desc = true;
        } else {
          println!("You can't go that way.");
        }
      },
      Command::Look => { show_desc = true; }
      Command::Quit => { break; }
    }
  }

  println!("Farewell.");
}
<commit_msg>Factor out build_world() function.<commit_after>extern crate ww;

use ww::map::{Map, MapRoomId};
use ww::room::Room;
use ww::direction::Direction::*;
use ww::command::Command;

use RoomId::*;

const MAX_ROOMS: usize = 20;

#[derive(Debug, Copy, Clone)]
enum RoomId {
  Hallway = 1,
  AudienceChamber = 2,
}

// Ideally we'd actually just get rid of MapRoomId and add a constraint
// to Map<T> requiring that T be type-castable as usize, but I don't
// know how to do that, so...
impl MapRoomId for RoomId {
  fn room_id(self) -> usize { self as usize }
}

fn build_world(map: &mut Map<RoomId>) {
  map.room(Hallway).describe(
    "Hallway",
    "You are in the hallway. \
     There is a door to the south. \
     Through the windows to the north you can see a secret herb garden."
  );

  map.room(AudienceChamber).describe(
    "Audience Chamber",
    "This is the audience chamber. \
     There is a window to the west. By looking to the right \
     through it you can see the entrance to the castle. \
     Doors leave this room to the north, east, and south."
  );

  map.connect(Hallway, South, AudienceChamber);
}

fn main() {
  let mut rooms = [Room::new(); MAX_ROOMS];
  let mut map = Map::new(&mut rooms);

  build_world(&mut map);

  println!("Werewolves and Wanderer\n");

  let mut curr_room = Hallway;
  let mut show_desc = true;

  loop {
    if show_desc {
      println!("{}", map.room(curr_room).description);
      show_desc = false;
    }

    match Command::get() {
      Command::Go(dir) => {
        if let Some(room) = map.room(curr_room).get_exit(dir) {
          curr_room = room;
          show_desc = true;
        } else {
          println!("You can't go that way.");
        }
      },
      Command::Look => { show_desc = true; }
      Command::Quit => { break; }
    }
  }

  println!("Farewell.");
}
<|endoftext|>


<commit_before>//! # Hexdino
//!
//! A hex editor with vim like keybindings written in Rust.

#![doc(html_logo_url = "https://raw.githubusercontent.com/Luz/hexdino/master/logo.png")]

use std::io::prelude::*;
use std::fs::OpenOptions;
use std::io::SeekFrom;
use std::path::Path;
use std::error::Error;
use std::env;

mod draw;
mod find;
use draw::draw;
use find::FindSubset;

extern crate ncurses;
use ncurses::*;

// extern crate getopts;
// use getopts::Options;

#[derive(PartialEq, Copy, Clone)]
pub enum Mode {
    Command,
    Replace,
    TypeCommand,
    Insert,
    TypeSearch,
    SearchIt,
    SearchItHex,
}

fn main() {
    let mut buf = vec![];
    let mut cursorpos: usize = 0;
    // 0 is left nibble, 1 is right nibble, 2 is ascii
    let mut cursorstate: usize = 0;
    // 0 = display data from first line of file
    let mut screenoffset: usize = 0;
    const SPALTEN: usize = 16;
    let mut command = String::new();

    // start ncursesw
    initscr();
    let screenheight: usize = getmaxy(stdscr) as usize;
    // ctrl+z and fg works with this
    cbreak();
    noecho();
    start_color();
    init_pair(1, COLOR_GREEN, COLOR_BLACK);

    printw("Welcome to Hexdino.\nPlease find the any-key on your keyboard and press it.\n");

    let args: Vec<_> = env::args().collect();
    // let program = args[0].clone();
    // let mut opts = Options::new();
    // opts.optopt("f", "", "set file name", "NAME");
    // opts.optflag("h", "help", "print this help menu");
    //
    // let matches = match opts.parse(&args[1..]) {
    // Ok(m) => { m }
    // Err(f) => { panic!(f.to_string()) }
    // };
    // if matches.opt_present("h") {
    // let brief = format!("Usage: {} FILE [options]", program);
    // print!("{}", opts.usage(&brief));
    // return;
    // }
    // let path = if !matches.free.is_empty() {
    // matches.free[0].clone()
    // } else {
    // let brief = format!("Usage: {} FILE [options]", program);
    // print!("{}", opts.usage(&brief));
    // return;
    // };

    let path = if args.len() > 1 {
        printw(&format!("Opening file {}", args[1]));
        args[1].clone()
    } else {
        printw(&format!("No file specified. Trying to open foo.txt"));
        "foo.txt".into()
    };

    let path = Path::new(&path);
    let display = path.display();

    getch();
    clear();

    if !has_colors() {
        endwin();
        println!("Your terminal does not support color!\n");
        return;
    }

    // TODO in this order: 1)disp files 2) hjkl movement 3) edit file by 'r' 4) save file 5) edit file by 'x, i' 6) search by '/'

    let mut file = match OpenOptions::new().read(true).write(true).create(true).open(&path) {
        Err(why) => panic!("couldn't open {}: {}", display, Error::description(&why)),
        Ok(file) => file,
    };

    file.read_to_end(&mut buf).ok().expect("File could not be read.");

    let mut mode = Mode::Command;

    draw(&buf,
         cursorpos,
         SPALTEN,
         mode,
         &command,
         cursorstate,
         screenoffset);

    let mut key;
    //    key = getch() as u8;
    //    printw(&format!("{:?}", key));

    let mut quitnow = 0;
    while quitnow == 0 {
        key = getch() as u8;
        if mode == Mode::Command {
            match key as char {
                'h' => {
                    if cursorstate == 2 {
                        // ascii mode
                        if cursorpos > 0 {
                            // not at start
                            cursorpos -= 1; // go left
                        }
                    } else if cursorstate == 1 {
                        // hex mode, right nibble
                        cursorstate = 0; // left nibble
                    } else if cursorstate == 0 {
                        // hex mode, left nibble
                        if cursorpos > 0 {
                            // not at start
                            cursorstate = 1; // right nibble
                            cursorpos -= 1; // go left
                        }
                    }
                }
                'j' => {
                    if cursorpos + SPALTEN < buf.len() {
                        // not at end
                        cursorpos += SPALTEN; // go down
                    } else {
                        // when at end
                        if buf.len() != 0 {
                            // Suppress underflow
                            cursorpos = buf.len() - 1; // go to end
                        }
                    }
                }
                'k' => {
                    if cursorpos >= SPALTEN {
                        cursorpos -= SPALTEN;
                    }
                }
                'l' => {
                    if cursorstate == 2 {
                        // ascii mode
                        if cursorpos < buf.len() - 1 {
                            // not at end
                            cursorpos += 1; // go right
                        }
                    } else if cursorstate == 0 {
                        // hex mode, left nibble
                        cursorstate = 1; // right nibble
                    } else if cursorstate == 1 {
                        // hex mode, right nibble
                        if cursorpos < buf.len() - 1 {
                            // not at end
                            cursorstate = 0; // left nibble
                            cursorpos += 1; // go right
                        }
                    }
                }
                '0' => {
                    cursorpos -= cursorpos % SPALTEN; // jump to start of line
                    if cursorstate == 1 {
                        // hex mode, right nibble
                        cursorstate = 0; // left nibble
                    }
                }
                '$' => {
                    if cursorpos - (cursorpos % SPALTEN) + (SPALTEN - 1) < buf.len() {
                        // check if no overflow
                        cursorpos = cursorpos - (cursorpos % SPALTEN) + (SPALTEN - 1); // jump to end of line
                    } else {
                        cursorpos = buf.len() - 1 // jump to end of line
                    }
                    if cursorstate == 0 {
                        // hex mode, left nibble
                        cursorstate = 1; // right nibble
                    }
                }
                'r' => {
                    mode = Mode::Replace;
                }
                'x' => {
                    if buf.len() > 0 {
                        // remove the current char
                        buf.remove(cursorpos);
                        if cursorpos >= buf.len() {
                            cursorpos = buf.len() - 1;
                        }
                    }
                }
                'i' => {
                    mode = Mode::Insert;
                }
                ':' => {
                    command.clear(); // delete old command
                    mode = Mode::TypeCommand;
                }
                'J' => {
                    if cursorstate == 2 {
                        // ascii mode
                        cursorstate = 0; // hex mode, left nibble
                    } else {
                        cursorstate = 2; // jump to ascii
                    }
                }
                '?' => {
                    // TODO
                    command.push_str("No helpfile yet");
                }
                '/' => {
                    // TODO
                    command.clear();
                    mode = Mode::TypeSearch;
                }
                _ => (),
            }

            // Always move screen when cursor leaves screen
            if cursorpos > (screenheight + screenoffset - 1) * SPALTEN - 1 {
                screenoffset += 1; // move screen
            }
            if cursorpos < screenoffset * SPALTEN {
                screenoffset -= 1; // move screen
            }

        } else if mode == Mode::Replace && cursorstate == 2 {
            // r was pressed so replace next char in ascii mode
            match key {
                c @ 32...126 => buf[cursorpos] = c,
                _ => (),
            }
            mode = Mode::Command;
        } else if mode == Mode::Replace {
            let mask = if cursorstate == 0 { 0x0F } else { 0xF0 };
            let shift = if cursorstate == 0 { 4 } else { 0 };
            // Change the selected nibble
            if let Some(c) = (key as char).to_digit(16) {
                buf[cursorpos] = buf[cursorpos] & mask | (c as u8) << shift;
            }
            mode = Mode::Command;
        } else if mode == Mode::TypeCommand {
            match key {
                c @ 32...126 => {
                    command.push(c as char);
                }
                27 => {
                    command.clear();
                    mode = Mode::Command;
                }
                10 => {
                    // Enter pressed, compute command!
                    if (command == "w".to_string()) || (command == "wq".to_string()) {
                        file.seek(SeekFrom::Start(0))
                            .ok()
                            .expect("Filepointer could not be set to 0");
                        file.write_all(&mut buf).ok().expect("File could not be written.");
                        if command == "wq".to_string() {
                            quitnow = 1;
                        }
                        command.clear();
                        mode = Mode::Command;
                    } else if command == "q".to_string() {
                        quitnow = 1;
                    } else {
                        command.clear();
                        command.push_str("Bad_command!");
                        mode = Mode::Command;
                    }
                }
                _ => (),
            }
        } else if mode == Mode::Insert {
            if cursorstate == 0 {
                // Left nibble
                match key {
                    c @ 65... 70 => // A-F
                        {buf.insert(cursorpos, (c-55)<<4 ); cursorstate = 1;},
                    c @ 97...102 => // a-f
                        {buf.insert(cursorpos, (c-87)<<4 ); cursorstate = 1;},
                    c @ 48... 57 => // 0-9
                        {buf.insert(cursorpos, (c-48)<<4 ); cursorstate = 1;},
                    27 => {mode = Mode::Command;},
                    _ => ()
                }
            } else if cursorstate == 1 {
                // Right nibble
                match key {
                    c @ 65...70 => // A-F
                        { buf[cursorpos] = buf[cursorpos]&0xF0 | (c-55); cursorstate = 0; cursorpos+=1; },
                    c @ 97...102 => // a-f
                        { buf[cursorpos] = buf[cursorpos]&0xF0 | (c-87); cursorstate = 0; cursorpos+=1; },
                    c @ 48...57 => // 0-9
                        { buf[cursorpos] = buf[cursorpos]&0xF0 | (c-48); cursorstate = 0; cursorpos+=1; },
                    27 => {mode = Mode::Command;},
                    _ => ()
                }
            } else if cursorstate == 2 {
                // Ascii
                match key {
                    c @ 32...126 => {
                        buf.insert(cursorpos, c);
                        cursorpos += 1;
                    }
                    27 => {
                        mode = Mode::Command;
                    }
                    _ => (),
                }
            }
        } else if mode == Mode::TypeSearch {
            //            if cursorstate == 2 { // Ascii search
            match key {
                c @ 32...126 => {
                    command.push(c as char);
                }
                27 => {
                    command.clear();
                    mode = Mode::Command;
                }
                10 => {
                    // Enter pressed, compute command!
                    if cursorstate == 2 {
                        mode = Mode::SearchIt;
                    } else {
                        mode = Mode::SearchItHex;
                    }
                }
                _ => (),
            }
            //            }

            if mode == Mode::SearchIt {
                let search = command.as_bytes();
                cursorpos = buf.find_subset(&search).unwrap_or(cursorpos);
                mode = Mode::Command;
                // command.push_str("Bad_command!");
            }
            if mode == Mode::SearchItHex {
                // TODO
                mode = Mode::Command;
                command.push_str("Hex search not yet implemented!");
            }
        }
        draw(&buf,
             cursorpos,
             SPALTEN,
             mode,
             &command,
             cursorstate,
             screenoffset);
    }

    refresh();
    endwin();
}
<commit_msg>Fix 3 panics and simplify insert mode hex<commit_after>//! # Hexdino
//!
//! A hex editor with vim like keybindings written in Rust.

#![doc(html_logo_url = "https://raw.githubusercontent.com/Luz/hexdino/master/logo.png")]

use std::io::prelude::*;
use std::fs::OpenOptions;
use std::io::SeekFrom;
use std::path::Path;
use std::error::Error;
use std::env;

mod draw;
mod find;
use draw::draw;
use find::FindSubset;

extern crate ncurses;
use ncurses::*;

// extern crate getopts;
// use getopts::Options;

#[derive(PartialEq, Copy, Clone)]
pub enum Mode {
    Command,
    Replace,
    TypeCommand,
    Insert,
    TypeSearch,
    SearchIt,
    SearchItHex,
}

fn main() {
    let mut buf = vec![];
    let mut cursorpos: usize = 0;
    // 0 is left nibble, 1 is right nibble, 2 is ascii
    let mut cursorstate: usize = 0;
    // 0 = display data from first line of file
    let mut screenoffset: usize = 0;
    const SPALTEN: usize = 16;
    let mut command = String::new();

    // start ncursesw
    initscr();
    let screenheight: usize = getmaxy(stdscr) as usize;
    // ctrl+z and fg works with this
    cbreak();
    noecho();
    start_color();
    init_pair(1, COLOR_GREEN, COLOR_BLACK);

    printw("Welcome to Hexdino.\nPlease find the any-key on your keyboard and press it.\n");

    let args: Vec<_> = env::args().collect();
    // let program = args[0].clone();
    // let mut opts = Options::new();
    // opts.optopt("f", "", "set file name", "NAME");
    // opts.optflag("h", "help", "print this help menu");
    //
    // let matches = match opts.parse(&args[1..]) {
    // Ok(m) => { m }
    // Err(f) => { panic!(f.to_string()) }
    // };
    // if matches.opt_present("h") {
    // let brief = format!("Usage: {} FILE [options]", program);
    // print!("{}", opts.usage(&brief));
    // return;
    // }
    // let path = if !matches.free.is_empty() {
    // matches.free[0].clone()
    // } else {
    // let brief = format!("Usage: {} FILE [options]", program);
    // print!("{}", opts.usage(&brief));
    // return;
    // };

    let path = if args.len() > 1 {
        printw(&format!("Opening file {}", args[1]));
        args[1].clone()
    } else {
        printw(&format!("No file specified. Trying to open foo.txt"));
        "foo.txt".into()
    };

    let path = Path::new(&path);
    let display = path.display();

    getch();
    clear();

    if !has_colors() {
        endwin();
        println!("Your terminal does not support color!\n");
        return;
    }

    // TODO in this order: 1)disp files 2) hjkl movement 3) edit file by 'r' 4) save file 5) edit file by 'x, i' 6) search by '/'

    let mut file = match OpenOptions::new().read(true).write(true).create(true).open(&path) {
        Err(why) => panic!("couldn't open {}: {}", display, Error::description(&why)),
        Ok(file) => file,
    };

    file.read_to_end(&mut buf).ok().expect("File could not be read.");

    let mut mode = Mode::Command;

    draw(&buf,
         cursorpos,
         SPALTEN,
         mode,
         &command,
         cursorstate,
         screenoffset);

    let mut key;
    //    key = getch() as u8;
    //    printw(&format!("{:?}", key));

    let mut quitnow = 0;
    while quitnow == 0 {
        key = getch() as u8;
        if mode == Mode::Command {
            match key as char {
                'h' => {
                    if cursorstate == 2 {
                        // ascii mode
                        if cursorpos > 0 {
                            // not at start
                            cursorpos -= 1; // go left
                        }
                    } else if cursorstate == 1 {
                        // hex mode, right nibble
                        cursorstate = 0; // left nibble
                    } else if cursorstate == 0 {
                        // hex mode, left nibble
                        if cursorpos > 0 {
                            // not at start
                            cursorstate = 1; // right nibble
                            cursorpos -= 1; // go left
                        }
                    }
                }
                'j' => {
                    if cursorpos + SPALTEN < buf.len() {
                        // not at end
                        cursorpos += SPALTEN; // go down
                    } else {
                        // when at end
                        if buf.len() != 0 {
                            // Suppress underflow
                            cursorpos = buf.len() - 1; // go to end
                        }
                    }
                }
                'k' => {
                    if cursorpos >= SPALTEN {
                        cursorpos -= SPALTEN;
                    }
                }
                'l' => {
                    if cursorstate == 2 {
                        // ascii mode
                        if cursorpos + 1 < buf.len() {
                            // not at end
                            cursorpos += 1; // go right
                        }
                    } else if cursorstate == 0 {
                        // hex mode, left nibble
                        cursorstate = 1; // right nibble
                    } else if cursorstate == 1 {
                        // hex mode, right nibble
                        if cursorpos + 1 < buf.len() {
                            // not at end
                            cursorstate = 0; // left nibble
                            cursorpos += 1; // go right
                        }
                    }
                }
                '0' => {
                    cursorpos -= cursorpos % SPALTEN; // jump to start of line
                    if cursorstate == 1 {
                        // hex mode, right nibble
                        cursorstate = 0; // left nibble
                    }
                }
                '$' => {
                    if cursorpos - (cursorpos % SPALTEN) + (SPALTEN - 1) < buf.len() {
                        // check if no overflow
                        cursorpos = cursorpos - (cursorpos % SPALTEN) + (SPALTEN - 1); // jump to end of line
                    } else {
                        cursorpos = buf.len() - 1 // jump to end of line
                    }
                    if cursorstate == 0 {
                        // hex mode, left nibble
                        cursorstate = 1; // right nibble
                    }
                }
                'r' => {
                    mode = Mode::Replace;
                }
                'x' => {
                    if buf.len() > 0 {
                        // remove the current char
                        buf.remove(cursorpos);
                        if cursorpos >= buf.len() && cursorpos > 0 {
                            cursorpos -= 1;
                        }
                    }
                }
                'i' => {
                    mode = Mode::Insert;
                }
                ':' => {
                    command.clear(); // delete old command
                    mode = Mode::TypeCommand;
                }
                'J' => {
                    if cursorstate == 2 {
                        // ascii mode
                        cursorstate = 0; // hex mode, left nibble
                    } else {
                        cursorstate = 2; // jump to ascii
                    }
                }
                '?' => {
                    // TODO
                    command.push_str("No helpfile yet");
                }
                '/' => {
                    // TODO
                    command.clear();
                    mode = Mode::TypeSearch;
                }
                _ => (),
            }

            // Always move screen when cursor leaves screen
            if cursorpos > (screenheight + screenoffset - 1) * SPALTEN - 1 {
                screenoffset += 1; // move screen
            }
            if cursorpos < screenoffset * SPALTEN {
                screenoffset -= 1; // move screen
            }

        } else if mode == Mode::Replace && cursorstate == 2 {
            // r was pressed so replace next char in ascii mode
            match key {
                c @ 32...126 => buf[cursorpos] = c,
                _ => (),
            }
            mode = Mode::Command;
        } else if mode == Mode::Replace {
            let mask = if cursorstate == 0 { 0x0F } else { 0xF0 };
            let shift = if cursorstate == 0 { 4 } else { 0 };
            // Change the selected nibble
            if let Some(c) = (key as char).to_digit(16) {
                buf[cursorpos] = buf[cursorpos] & mask | (c as u8) << shift;
            }
            mode = Mode::Command;
        } else if mode == Mode::TypeCommand {
            match key {
                c @ 32...126 => {
                    command.push(c as char);
                }
                27 => {
                    command.clear();
                    mode = Mode::Command;
                }
                10 => {
                    // Enter pressed, compute command!
                    if (command == "w".to_string()) || (command == "wq".to_string()) {
                        file.seek(SeekFrom::Start(0))
                            .ok()
                            .expect("Filepointer could not be set to 0");
                        file.write_all(&mut buf).ok().expect("File could not be written.");
                        if command == "wq".to_string() {
                            quitnow = 1;
                        }
                        command.clear();
                        mode = Mode::Command;
                    } else if command == "q".to_string() {
                        quitnow = 1;
                    } else {
                        command.clear();
                        command.push_str("Bad_command!");
                        mode = Mode::Command;
                    }
                }
                _ => (),
            }
        } else if mode == Mode::Insert {
            if cursorstate == 0 {
                // Left nibble
                if let Some(c) = (key as char).to_digit(16) {
                    buf.insert(cursorpos, (c as u8) << 4); cursorstate = 1;
                }
                if key == 27 {mode = Mode::Command;};
            } else if cursorstate == 1 {
                // Right nibble
                if let Some(c) = (key as char).to_digit(16) {
                    buf[cursorpos] = buf[cursorpos]&0xF0 | c as u8; cursorstate = 0; cursorpos+=1;
                }
                if key == 27 {mode = Mode::Command;};
            } else if cursorstate == 2 {
                // Ascii
                match key {
                    c @ 32...126 => {
                        buf.insert(cursorpos, c);
                        cursorpos += 1;
                    }
                    27 => {
                        mode = Mode::Command;
                    }
                    _ => (),
                }
            }
        } else if mode == Mode::TypeSearch {
            //            if cursorstate == 2 { // Ascii search
            match key {
                c @ 32...126 => {
                    command.push(c as char);
                }
                27 => {
                    command.clear();
                    mode = Mode::Command;
                }
                10 => {
                    // Enter pressed, compute command!
                    if cursorstate == 2 {
                        mode = Mode::SearchIt;
                    } else {
                        mode = Mode::SearchItHex;
                    }
                }
                _ => (),
            }
            //            }

            if mode == Mode::SearchIt {
                let search = command.as_bytes();
                cursorpos = buf.find_subset(&search).unwrap_or(cursorpos);
                mode = Mode::Command;
                // command.push_str("Bad_command!");
            }
            if mode == Mode::SearchItHex {
                // TODO
                mode = Mode::Command;
                command.push_str("Hex search not yet implemented!");
            }
        }
        draw(&buf,
             cursorpos,
             SPALTEN,
             mode,
             &command,
             cursorstate,
             screenoffset);
    }

    refresh();
    endwin();
}
<|endoftext|>


<commit_before>#![feature(collections)]
#![feature(core)]
#![feature(env)]
#![feature(fs)]
#![feature(int_uint)]
#![feature(io)]
#![feature(path)]
#![feature(std_misc)]
#![feature(unsafe_destructor)]
extern crate getopts;
extern crate nanomsg;
extern crate protobuf;
extern crate sodiumoxide;
extern crate time;
extern crate uuid;
extern crate "rustc-serialize" as rustc_serialize;

mod balance;
mod block;
mod blocktree;
mod crypto;
mod error;
mod app;
mod service;
mod simples_pb;
mod staking;
mod store;
mod tx;
mod wallet;

use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::str::FromStr;

use getopts::Options;
use protobuf::Message;
use rustc_serialize::base64::{self, FromBase64, ToBase64};

use block::GenesisBuilder;
use blocktree::BlockTreeStore;
use crypto::{slice_to_pk, slice_to_sk};
use error::{SimplesResult, SimplesError};
use service::{RpcService, SimplesService};
use simples_pb::HashedBlock;
use store::RocksStore;
use wallet::{load_proto_from_file, WalletExt};

fn create_genesis_block_from_cmdline(tx_strs: &[String])
                                     -> SimplesResult<HashedBlock>
{
    let mut builder = GenesisBuilder::new();
    for tx_str in tx_strs.iter() {
        let transfer_parts: Vec<&str> = tx_str.split_str(":").collect();
        if transfer_parts.len() != 2 {
            return Err(SimplesError::new(
                "A genesis transfer needs to be specified as ADDR:AMOUNT"));
        };

        let maybe_destination =
            slice_to_pk(&try!(FromBase64::from_base64(transfer_parts[0])));
        if maybe_destination.is_err() {
            return Err(SimplesError::new(&format!(
                "Could not parse \"{}\" as an address.", transfer_parts[0])));
        }
        let destination = maybe_destination.unwrap();

        let maybe_amount = FromStr::from_str(transfer_parts[1]);
        if maybe_amount.is_err() {
            return Err(SimplesError::new(&format!(
                "Could not parse \"{}\" as an amount.", transfer_parts[1])));
        }
        let amount: u64 = maybe_amount.unwrap();
        builder.add_transfer(destination, amount);
    }
    Ok(builder.build())
}

fn create_block_store(block_db: &str, genesis: Option<HashedBlock>) ->
    SimplesResult<BlockTreeStore<RocksStore>> {
    BlockTreeStore::new(try!(RocksStore::new(block_db)), genesis)
}

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(brief.as_slice()));
}

const DEFAULT_ENDPOINT:&'static str = "tcp://127.0.0.1:13337";

fn main() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();
    let daemon_description = format!(
        "Daemonize, run simples in the background. Pass endpoint where
        to bind (default={})", DEFAULT_ENDPOINT);
    let mut opts = Options::new();
    opts.optflag("h", "help", "Show this help menu.");
    opts.optflagopt("d", "", &daemon_description, "ENDPOINT");
    opts.optopt("f", "", "Load wallet file.", "FILE");
    opts.optmulti("t", "", "Specify a transfer.", "SRC:DEST:AMOUNT:OP_NUM");
    opts.optmulti("p", "", "Specify a peer.", "ENDPOINT");
    opts.optopt("", "add", "Add a pre-existing address to the wallet.",
                "[NAME:]ADDR|SKEY");
    opts.optflagopt("", "new", "Create and add a new address to the wallet.",
                    "NAME");
    opts.optflagopt("", "ls", "List all addresses contained by the wallet.",
                    "PATTERN");
    opts.optopt("g", "", "Set genesis block from file.", "PATH");
    opts.optopt("", "blocktree", "Specify blocktree database.", "PATH");
    opts.optopt("", "new-genesis", "Create a genesis block and write it to file.
Use multiple times to specify genesis transactions.", "PATH");
    opts.optmulti("", "gtx", "Use with --create-genesis. The argument can be
used multiple times to specify genesis transactions.", "ADDR:AMOUNT");
    let matches = match opts.parse(args.tail()) {
        Ok(m) => { m }
        Err(f) => {
            println!("{}", f);
            return;
        }
    };
    if matches.opt_present("h") {
        print_usage(program.as_slice(), opts);
        return;
    }

    let rpc_endpoint = matches.opt_str("d")
        .unwrap_or(String::from_str(DEFAULT_ENDPOINT));
    let mut wallet = simples_pb::Wallet::new();
    if matches.opt_present("f") {
        let wallet_file = matches.opt_str("f").unwrap();
        println!("Reading wallet from file: {}", wallet_file);
        wallet = wallet::load_from_file(&wallet_file).unwrap();

        if matches.opt_present("new") {
            let key = match matches.opt_str("new") {
                Some(name) => wallet.generate_new_key(&name),
                None => {
                    let name = wallet.generate_name();
                    wallet.generate_new_key(&name)
                }};
            println!("Created new address: {}",
                     key.get_public_key().to_base64(base64::STANDARD));
            wallet::save_to_file(&wallet_file, &wallet).unwrap()
        }
        if matches.opt_present("ls") {
            let pattern = matches.opt_str("ls").unwrap_or(String::new());
            if &pattern[..] != "" {
                let _: Vec<()> = wallet.search_keys(&pattern).iter()
                    .map(|wkey| { println!("{}", wallet::pretty_format(*wkey)); })
                    .collect();
            } else {
                let _: Vec<()> = wallet.get_keys().iter().map(
                    |wkey| println!("{}", wallet::pretty_format(wkey))).collect();
            }
        }
    }
    let mut peers = vec![];
    if matches.opt_present("p") {
        peers.push_all(&matches.opt_strs("p"));
    }
    if matches.opt_present("t") {
        let mut tx_builder = tx::TransactionBuilder::new();
        for transfer_str in matches.opt_strs("t").iter() {
            let transfer_parts: Vec<&str> =
                transfer_str.split_str(":").collect();
            if transfer_parts.len() != 4 {
                println!("You need to specify the transfer you wish \
                          to make as SOURCE:DESTINATION:AMOUNT:OP_NUM.");
                return;
            }
            let source = transfer_parts[0];
            let destination = transfer_parts[1];
            let amount: u64 = FromStr::from_str(transfer_parts[2]).unwrap();
            let op_number: u32 = FromStr::from_str(transfer_parts[3]).unwrap();

            let source_keys = wallet.search_keys(&source);
            let destination_keys = wallet.search_keys(&destination);
            if source_keys.len() == 0 {
                println!(
                    "The wallet doesn't contain the source address \"{}\"",
                    source);
                return;
            } else if destination_keys.len() == 0{
                println!(
                    "The wallet doesn't contain the destination address \"{}\"",
                    destination);
                return;
            } else if source_keys.len() > 1 || destination_keys.len() > 1 {
                println!(
                    "The wallet contains multiple addresses that match \"{}\":",
                    source);
                let _: Vec<()> = source_keys.iter().map(|wkey| {
                    println!("{}", wallet::pretty_format(*wkey))}).collect();
                return;
            }
            let source_sk =
                slice_to_sk(source_keys[0].get_secret_key()).unwrap();
            let source_pk =
                slice_to_pk(source_keys[0].get_public_key()).unwrap();
            let destination_pk =
                slice_to_pk(destination_keys[0].get_public_key()).unwrap();
            tx_builder
                .add_transfer(&source_sk, &source_pk, &destination_pk,
                              amount, op_number)
                .set_bounty(&source_sk, &source_pk, 1);
        }
        let transaction = tx_builder.build().unwrap();
        println!("{}", protobuf::text_format::print_to_string(&transaction));
        if peers.len() == 0 {
            println!("ERROR: No peers were specified.");
            return;
        }
        for peer in peers.iter() {
            let mut client = service::Client::new(peer).unwrap();
            let mut request = simples_pb::PubTransactionRequest::new();
            request.set_transaction(transaction.clone());
            let response = client.pub_transaction(request).ok().unwrap();
            println!("Response status: {:?}", response);
        }
    }
    if matches.opt_present("new-genesis") {
        let genesis_path = matches.opt_str("new-genesis").unwrap();
        println!("Writing new genesis block to \"{}\"", genesis_path);
        let maybe_genesis =
            create_genesis_block_from_cmdline(&matches.opt_strs("gtx"));
        if maybe_genesis.is_err() {
            println!("{}", maybe_genesis.unwrap_err());
            return;
        }
        let genesis_block = maybe_genesis.unwrap();
        let mut genesis_out =
            File::create(&Path::new(&genesis_path)).unwrap();
        let genesis_bytes = genesis_block.write_to_bytes().unwrap();
        let write_result = genesis_out.write_all(&genesis_bytes);
        if write_result.is_err() {
            println!("Could not write genesis to file \"{}\"", genesis_path);
            return;
        }
    }
    if matches.opt_present("d") {
        let blocktree_path = matches.opt_str("blocktree")
            .unwrap_or(String::from_str("block.rdb"));

        let genesis_block: Option<HashedBlock> = match matches.opt_str("g") {
            Some(path) => {
                let maybe_genesis = load_proto_from_file(&path);
                if maybe_genesis.is_err() {
                    println!("ERROR: Could not read genesis block from file {}", path);
                    return;
                }
                Some(maybe_genesis.unwrap())
            },
            None => None
        };

        let blocktree;
        match create_block_store(&blocktree_path, genesis_block) {
            Ok(inner_blocktree) => { blocktree = inner_blocktree; },
            Err(err) => {
                println!("FATAL: Could not instantiate a blocktree from {}.\n{}",
                         blocktree_path, err);
                return;
            }
        };

        let service =
            SimplesService::new(blocktree).unwrap();
        let mut app =
            app::Application::new(rpc_endpoint, service, peers, wallet).unwrap();
        app.run().unwrap();
    }
}
<commit_msg>Renamed Key -> Keypair.<commit_after>#![feature(collections)]
#![feature(core)]
#![feature(env)]
#![feature(fs)]
#![feature(int_uint)]
#![feature(io)]
#![feature(path)]
#![feature(std_misc)]
#![feature(unsafe_destructor)]
extern crate getopts;
extern crate nanomsg;
extern crate protobuf;
extern crate sodiumoxide;
extern crate time;
extern crate uuid;
extern crate "rustc-serialize" as rustc_serialize;

mod balance;
mod block;
mod blocktree;
mod crypto;
mod error;
mod app;
mod service;
mod simples_pb;
mod staking;
mod store;
mod tx;
mod wallet;

use std::env;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::str::FromStr;

use getopts::Options;
use protobuf::Message;
use rustc_serialize::base64::{self, FromBase64, ToBase64};

use block::GenesisBuilder;
use blocktree::BlockTreeStore;
use crypto::{slice_to_pk, slice_to_sk};
use error::{SimplesResult, SimplesError};
use service::{RpcService, SimplesService};
use simples_pb::HashedBlock;
use store::RocksStore;
use wallet::{load_proto_from_file, WalletExt};

fn create_genesis_block_from_cmdline(tx_strs: &[String])
                                     -> SimplesResult<HashedBlock>
{
    let mut builder = GenesisBuilder::new();
    for tx_str in tx_strs.iter() {
        let transfer_parts: Vec<&str> = tx_str.split_str(":").collect();
        if transfer_parts.len() != 2 {
            return Err(SimplesError::new(
                "A genesis transfer needs to be specified as ADDR:AMOUNT"));
        };

        let maybe_destination =
            slice_to_pk(&try!(FromBase64::from_base64(transfer_parts[0])));
        if maybe_destination.is_err() {
            return Err(SimplesError::new(&format!(
                "Could not parse \"{}\" as an address.", transfer_parts[0])));
        }
        let destination = maybe_destination.unwrap();

        let maybe_amount = FromStr::from_str(transfer_parts[1]);
        if maybe_amount.is_err() {
            return Err(SimplesError::new(&format!(
                "Could not parse \"{}\" as an amount.", transfer_parts[1])));
        }
        let amount: u64 = maybe_amount.unwrap();
        builder.add_transfer(destination, amount);
    }
    Ok(builder.build())
}

fn create_block_store(block_db: &str, genesis: Option<HashedBlock>) ->
    SimplesResult<BlockTreeStore<RocksStore>> {
    BlockTreeStore::new(try!(RocksStore::new(block_db)), genesis)
}

fn print_usage(program: &str, opts: Options) {
    let brief = format!("Usage: {} [options]", program);
    print!("{}", opts.usage(brief.as_slice()));
}

const DEFAULT_ENDPOINT:&'static str = "tcp://127.0.0.1:13337";

fn main() {
    let args: Vec<String> = env::args().collect();
    let program = args[0].clone();
    let daemon_description = format!(
        "Daemonize, run simples in the background. Pass endpoint where
        to bind (default={})", DEFAULT_ENDPOINT);
    let mut opts = Options::new();
    opts.optflag("h", "help", "Show this help menu.");
    opts.optflagopt("d", "", &daemon_description, "ENDPOINT");
    opts.optopt("f", "", "Load wallet file.", "FILE");
    opts.optmulti("t", "", "Specify a transfer.", "SRC:DEST:AMOUNT:OP_NUM");
    opts.optmulti("p", "", "Specify a peer.", "ENDPOINT");
    opts.optopt("", "add", "Add a pre-existing address to the wallet.",
                "[NAME:]ADDR|SKEY");
    opts.optflagopt("", "new", "Create and add a new address to the wallet.",
                    "NAME");
    opts.optflagopt("", "ls", "List all addresses contained by the wallet.",
                    "PATTERN");
    opts.optopt("g", "", "Set genesis block from file.", "PATH");
    opts.optopt("", "blocktree", "Specify blocktree database.", "PATH");
    opts.optopt("", "new-genesis", "Create a genesis block and write it to file.
Use multiple times to specify genesis transactions.", "PATH");
    opts.optmulti("", "gtx", "Use with --create-genesis. The argument can be
used multiple times to specify genesis transactions.", "ADDR:AMOUNT");
    let matches = match opts.parse(args.tail()) {
        Ok(m) => { m }
        Err(f) => {
            println!("{}", f);
            return;
        }
    };
    if matches.opt_present("h") {
        print_usage(program.as_slice(), opts);
        return;
    }

    let rpc_endpoint = matches.opt_str("d")
        .unwrap_or(String::from_str(DEFAULT_ENDPOINT));
    let mut wallet = simples_pb::Wallet::new();
    if matches.opt_present("f") {
        let wallet_file = matches.opt_str("f").unwrap();
        println!("Reading wallet from file: {}", wallet_file);
        wallet = wallet::load_from_file(&wallet_file).unwrap();

        if matches.opt_present("new") {
            let key = match matches.opt_str("new") {
                Some(name) => wallet.generate_new_key(&name),
                None => {
                    let name = wallet.generate_name();
                    wallet.generate_new_key(&name)
                }};
            println!("Created new address: {}",
                     key.get_public_key().to_base64(base64::STANDARD));
            wallet::save_to_file(&wallet_file, &wallet).unwrap()
        }
        if matches.opt_present("ls") {
            let pattern = matches.opt_str("ls").unwrap_or(String::new());
            if &pattern[..] != "" {
                let _: Vec<()> = wallet.search_keys(&pattern).iter()
                    .map(|wkey| { println!("{}", wallet::pretty_format(*wkey)); })
                    .collect();
            } else {
                let _: Vec<()> = wallet.get_keypairs().iter().map(
                    |wkey| println!("{}", wallet::pretty_format(wkey))).collect();
            }
        }
    }
    let mut peers = vec![];
    if matches.opt_present("p") {
        peers.push_all(&matches.opt_strs("p"));
    }
    if matches.opt_present("t") {
        let mut tx_builder = tx::TransactionBuilder::new();
        for transfer_str in matches.opt_strs("t").iter() {
            let transfer_parts: Vec<&str> =
                transfer_str.split_str(":").collect();
            if transfer_parts.len() != 4 {
                println!("You need to specify the transfer you wish \
                          to make as SOURCE:DESTINATION:AMOUNT:OP_NUM.");
                return;
            }
            let source = transfer_parts[0];
            let destination = transfer_parts[1];
            let amount: u64 = FromStr::from_str(transfer_parts[2]).unwrap();
            let op_number: u32 = FromStr::from_str(transfer_parts[3]).unwrap();

            let source_keys = wallet.search_keys(&source);
            let destination_keys = wallet.search_keys(&destination);
            if source_keys.len() == 0 {
                println!(
                    "The wallet doesn't contain the source address \"{}\"",
                    source);
                return;
            } else if destination_keys.len() == 0{
                println!(
                    "The wallet doesn't contain the destination address \"{}\"",
                    destination);
                return;
            } else if source_keys.len() > 1 || destination_keys.len() > 1 {
                println!(
                    "The wallet contains multiple addresses that match \"{}\":",
                    source);
                let _: Vec<()> = source_keys.iter().map(|wkey| {
                    println!("{}", wallet::pretty_format(*wkey))}).collect();
                return;
            }
            let source_sk =
                slice_to_sk(source_keys[0].get_secret_key()).unwrap();
            let source_pk =
                slice_to_pk(source_keys[0].get_public_key()).unwrap();
            let destination_pk =
                slice_to_pk(destination_keys[0].get_public_key()).unwrap();
            tx_builder
                .add_transfer(&source_sk, &source_pk, &destination_pk,
                              amount, op_number)
                .set_bounty(&source_sk, &source_pk, 1);
        }
        let transaction = tx_builder.build().unwrap();
        if peers.len() == 0 {
            println!("ERROR: No peers were specified.");
            return;
        }
        for peer in peers.iter() {
            let mut client = service::Client::new(peer).unwrap();
            let mut request = simples_pb::PubTransactionRequest::new();
            request.set_transaction(transaction.clone());
            let response = client.pub_transaction(request).ok().unwrap();
            println!("Response status: {:?}", response);
        }
    }
    if matches.opt_present("new-genesis") {
        let genesis_path = matches.opt_str("new-genesis").unwrap();
        println!("Writing new genesis block to \"{}\"", genesis_path);
        let maybe_genesis =
            create_genesis_block_from_cmdline(&matches.opt_strs("gtx"));
        if maybe_genesis.is_err() {
            println!("{}", maybe_genesis.unwrap_err());
            return;
        }
        let genesis_block = maybe_genesis.unwrap();
        let mut genesis_out =
            File::create(&Path::new(&genesis_path)).unwrap();
        let genesis_bytes = genesis_block.write_to_bytes().unwrap();
        let write_result = genesis_out.write_all(&genesis_bytes);
        if write_result.is_err() {
            println!("Could not write genesis to file \"{}\"", genesis_path);
            return;
        }
    }
    if matches.opt_present("d") {
        let blocktree_path = matches.opt_str("blocktree")
            .unwrap_or(String::from_str("block.rdb"));

        let genesis_block: Option<HashedBlock> = match matches.opt_str("g") {
            Some(path) => {
                let maybe_genesis = load_proto_from_file(&path);
                if maybe_genesis.is_err() {
                    println!("ERROR: Could not read genesis block from file {}", path);
                    return;
                }
                Some(maybe_genesis.unwrap())
            },
            None => None
        };

        let blocktree;
        match create_block_store(&blocktree_path, genesis_block) {
            Ok(inner_blocktree) => { blocktree = inner_blocktree; },
            Err(err) => {
                println!("FATAL: Could not instantiate a blocktree from {}.\n{}",
                         blocktree_path, err);
                return;
            }
        };

        let service =
            SimplesService::new(blocktree).unwrap();
        let mut app =
            app::Application::new(rpc_endpoint, service, peers, wallet).unwrap();
        app.run().unwrap();
    }
}
<|endoftext|>